Combineを使ってTextFieldの入力のバリデーションチェック
入力フォームのバリデーションチェックを、Combineで実行します。
開発環境
- macOS 11
- Xcode 12 beta
View側の実装
全体のソースコード。
struct CreateUserView: View {
@StateObject var vm = CreateUserViewModel()
var body: some View {
NavigationView {
Form {
TextField("Username", text: $vm.username)
TextField("Password", text: $vm.password)
TextField("Password Again", text: $vm.passwordAgain)
Button(action: {}, label: {
Text("Submit")
})
.disabled(!vm.readyToCreate)
Section(header: Text("DEBUG")) {
Text(vm.readyToCreate ? "Ready to create" : "Waiting input")
}
}
.navigationTitle("Create User")
}
}
}
Formでリストを構築し、ユーザー名のTextFieldと、パスワードと二回目パスワードのSecureFieldを入れる。
入力された情報は、ViewModelに持たせて、そこでバリデーションチェックを行う。
readyToCreateは、一回目パスワードと二回目パスワードが一致する、かつユーザー名の空ではないときにtrueになる。
Submitボタンは、バリデーションチェック結果のreadyToCreateがTrueの場合のみ活性化になる。
デバッグのため、下にreadyToCreateの値を表示する。
※Xcode 12 RC/iOS14.2 でSecureFieldがバグっていて上手く表示されないこともある。その時はTextFieldに変えてください
ViewModel側の実装
全体のソースコード。
class CreateUserViewModel: ObservableObject {
@Published var username = ""
@Published var password = ""
@Published var passwordAgain = ""
@Published var readyToCreate = false
private var anyCancellable = Set<AnyCancellable>()
init() {
$username
.combineLatest($password, $passwordAgain)
.map {
let username = $0.0
let password = $0.1
let passwordAgain = $0.2
guard username.count > 0, password.count > 0, passwordAgain.count > 0 else { return false }
guard password == passwordAgain else { return false }
return true
}
.assign(to: \.readyToCreate, on: self)
.store(in: &anyCancellable)
}
}
①ストリームを合成する
$username
.combineLatest($password, $passwordAgain)
combineLatestを使って$username、$passwordと$passwordAgainの3つのストリームを結合させる。
どちらかが新しい値が出力されたとき、3ストリームの一番新しい値がタプルにとして再出力される。
combineLatestの詳しい説明は、こちらを参考ください。
②バリデーションチェック
.map {
let username = $0.0
let password = $0.1
let passwordAgain = $0.2
guard username.count > 0, password.count > 0, passwordAgain.count > 0 else { return false }
guard password == passwordAgain else { return false }
return true
}
mapを使ってusername、passwordとpasswordAgainの文字数をチェックする。
$passwordと$passwordAgainが一致するのをチェックする。
両方チェックOKのみtrueを出力する。それ以外はfalseを出力する。
③画面更新
.assign(to: \.readyToCreate, on: self)
mapで出力されたパスワードチェックの結果をreadyToCreateに設置する。
readyToCreateが更新されると、View側も更新される。
④AnyCancellable
.store(in: &anyCancellable)
AnyCancellableをストアする。
まとめ
SwiftUIとCombineを使えば、簡単にバリデーションチェックを作れます。
両方ともiOS13以降しか使えないのがネックですが、今後は大きく期待できると思います。
(みんなFlutterで盛り上がっているみたいですが。。。)