SwiftUIでアルファベットと数字だけ入力できるTextField

2 min read読了の目安(約1900字

SwiftUIアプリでIDを入力してもらう際にアルファベット数字だけが入力できるTextFieldが必要だったので自作しました。

コードはこちらです。

struct ContentView: View {
    @State var username = ""
    var body: some View {
        TextField("[A-Za-z0-9]", text: $username)
            .autocapitalization(.none)
            .keyboardType(.asciiCapable)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding()
            .onChange(of: username, perform: filter)
    }

    func filter(value: String) {
        let validCodes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        let sets = CharacterSet(charactersIn: validCodes)
        username = String(value.unicodeScalars.filter(sets.contains).map(Character.init))
    }
}

onChange(of:perform:)ofで指定した引数が変化すると(上記の例ではusername)、performに指定したclosureが呼び出されます(filter)。

用意した文字セット(validCodes)からCharacterSetを生成。引数のvalueに対してfilterを実行して文字セットに含まれない文字を除外したのちmapで再度Stringに戻しています(少しまどろっこしい)。

余談

当初は以下のような実装でした。

func filter(value: String) {
    let sets = CharacterSet.lowercaseLetters
        .union(CharacterSet.uppercaseLetters)
        .union(CharacterSet.decimalDigits)
    username = String(value.unicodeScalars.filter(sets.contains).map(Character.init))
}
  • CharacterSet.lowercaseLettersが小文字
  • CharacterSet.uppercaseLettersが大文字
  • CharacterSet.decimalDigitsが数字

に該当します。当初はこれで良いと思ったのですがlowercaseLettersuppercaseLettersにはラテン文字全般が含まれていてスルーされてしまいました(ダイアクリティカルマークが付いた文字、áなど)。

参考情報

https://developer.apple.com/documentation/swiftui/view/onchange(of:perform:)

onChange(of:perform:)
Adds a modifier for this view that fires an action when a specific value changes.

https://developer.apple.com/documentation/foundation/characterset

CharacterSet
A set of Unicode character values for use in search operations.