NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

【SwiftUI】TextFieldやTextEditorで入力した文字数の制限やカウントを表示する方法

概要

テキストの文字数をリアルタイムでカウントして表示したり、入力文字数の制限をする方法についてCombineフレームワークとonChangeを使用した手法をそれぞれ紹介したいと思います。

環境

この記事は以下のバージョン環境のもと作成されたものです。
【Xcode】14.0.1
【iOS】16.0
【macOS】Monterey バージョン 12.5

Combineでの実装

Combineでの実装方法は以下の通りです。
Combineフレームワークを使用しますので必ずimport CombineでImportして下さい。

struct HogeView: View {
    @State private var hogeText = ""
    @State private var hogeTextCount = 0
    let maximumCharacters = 10
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("テキスト入力")
                .padding(.leading)
            TextEditor(text: $hogeText)
                .frame(height: 400)
                .background {
                    Rectangle()
                        .stroke(lineWidth: 2)
                }
                .padding()
                .onReceive(Just(hogeText)) { inputText in
                    if inputText.count > maximumCharacters {
                        hogeText = String(inputText.prefix(maximumCharacters))
                    }
                    hogeTextCount = inputText.count <= maximumCharacters ? inputText.count : maximumCharacters
                }
            HStack {
                Spacer()
                Text("\(hogeTextCount) / \(maximumCharacters)")
            }
            .padding(.trailing)
        }
    }
}

onReceiveを使用して実装します。onReceiveドキュメントに以下の様に記載されています。
このビューが特定のパブリッシャーによって発行されたデータを検出したときに実行するアクションを追加します。
引用元: https://developer.apple.com/documentation/swiftui/grid/onreceive(_:perform:)
onReceiveの引数にJustを使用します。Justは各サブスクライバーに 1 回だけ出力を発行して終了するパブリッシャーなので、hogeTextが変更される度に一回だけクロージャー内の処理が実行されます。
後は入力したテキストに対して定義した最大の文字を超えるとprefixによって取得した最大の文字数を代入して、テキストの表示制御を行なっています。
文字数のカウントに関してはinputText.count <= maximumCharacters ? inputText.count : maximumCharactersの部分で入力したテキストの文字数を取得して表示しています。
表示するだけだと上記で定義した最大の文字数を超えてしまうので、超えないように三項演算子で処理を行なっています。
ただViewModelの様なclassでプロパティやメソッドを管理すると取り回しが面倒になります。

onChangeでの実装

onChangeでの実装もほぼonReceiveでの実装と変わりないです。
onReceiveの部分を以下に置き換えるだけです。

.onChange(of: hogeText) { inputText in
    if inputText.count > maximumCharacters {
        hogeText = String(inputText.prefix(maximumCharacters))
    }
    hogeTextCount = inputText.count <= maximumCharacters ? inputText.count : maximumCharacters
}

onChangeは以下の通りです。
特定の値が変更されたときにアクションを起動する、このビューの修飾子を追加します。
引用元: https://developer.apple.com/documentation/swiftui/view/onchange(of:perform:)
使い方はシンプルでonChange(of: xxx)のxxxの部分を監視して、変更されるとクロージャー内の処理を実行します。
onReceiveより分かりやすいです。
またclassでプロパティやメソッドを管理する際も簡単に行えます。

class HogeViewModel: ObservableObject {
    @Published var hogeText = ""
    @Published private(set) var hogeTextCount = 0
    let maximumCharacters = 10

    func hogeTextCountConverter(inputText: String) {
        hogeText = hogeTextUpperLimitConverter(inputText: inputText)
        hogeTextCount = hogeTextCountUpdater(inputText: inputText)
    }

    private func hogeTextUpperLimitConverter(inputText: String) -> String {
        if inputText.count > maximumCharacters {
            return String(inputText.prefix(maximumCharacters))
        } else {
            return inputText
        }
    }

    private func hogeTextCountUpdater(inputText: String) -> Int {
        return inputText.count <= maximumCharacters ? inputText.count : maximumCharacters
    }
}
struct HogeView: View {
    @StateObject private var viewModel = HogeViewModel()
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("テキスト入力")
                .padding(.leading)
            TextEditor(text: $viewModel.hogeText)
                .frame(height: 400)
                .background {
                    Rectangle()
                        .stroke(lineWidth: 2)
                }
                .padding()
                .onChange(of: viewModel.hogeText) { inputText in
                    viewModel.hogeTextCountConverter(inputText: inputText)
                }
            HStack {
                Spacer()
                Text("\(viewModel.hogeTextCount) / \(viewModel.maximumCharacters)")
            }
            .padding(.trailing)
        }
    }
}

まとめ

時と場合によって使い分けていただければと思います。ただSwiftUIで実装するならonChangeを使用した方が圧倒的に楽な気がします。
また記入したかった文字は「今日もいい天気で気持ちがいい」だったので入力できる文字数を増やしたいと思います。

執筆者岡優志

iOSエンジニア
iOSを専門とし、モバイルアプリの開発を行なっています。

Twitter