SwiftUIで登録フォームを作る

5 min read読了の目安(約4500字

今回はSwiftUIで登録フォームを作成します。
一般的な登録・ログインフォームでは、メールアドレスの形式になっているかとか、2つのパスワードが一致しているかとかのバリデーションを行います。SwiftUIでどのように実現するのかを解説していきます。

必要なフレームワーク

特にCombineフレームワークでは、以下のクラスをメインに使います。

  • Published
  • ObservableObject, ObservedObject

実装

ViewModelの作成

Viewに関連するロジックをViewModelとして実装します。SwiftUIに値を反映させたいので、ObservableObjectを継承しています。

import Foundation
import Combine

class ViewModel: ObservableObject {
    
    @Published var mail = "" //メールアドレスの入力値を格納
    @Published var pass = "" //パスワードの入力値を格納
    @Published var retype = "" //パスワードの再入力値を格納
    
    @Published var canSend = false  //登録できるかどうかのフラグ
    @Published var invalidMail = "" //無効なメールアドレスの時のエラー文言
    @Published var invalidPass = "" //無効なパスワードの時のエラー文言
    
    init() {
        
        //①登録できるかどうかの判定
        let mailValidation = $mail.map({ !$0.isEmpty && $0.isValidEmail }).eraseToAnyPublisher()
        let passValidation = $pass.map({ !$0.isEmpty }).eraseToAnyPublisher()
        let retypeValidation = $retype.map({ !$0.isEmpty }).eraseToAnyPublisher()
        let matchValidation = $pass.combineLatest($retype).map({ $0 == $1 }).eraseToAnyPublisher()        
        Publishers.CombineLatest4(mailValidation, passValidation, retypeValidation, matchValidation)
            .map({ [$0.0, $0.1, $0.2, $0.3] }) //4つの条件(bool)を1つの配列にまとめます
            .map({ $0.allSatisfy{ $0 } }) //配列になった4つの条件(bool)が全てtrueの時に、結果がtrueとなります
            .assign(to: &$canSend) //結果をcanSendに格納します。
        
        //②メールアドレスのチェック
        $mail.map({ $0.isEmpty || $0.isValidEmail ? "" : "enter valid mail address" }).assign(to: &$invalidMail)
        
        //③パスワードのチェック
        $pass.combineLatest($retype)
            .filter({ !$0.1.isEmpty && !$0.1.isEmpty })
            .map({ $0.0 == $0.1 ? "" : "must match password" })
            .assign(to: &$invalidPass)
                
    }
    
}

extension String {
    
    //メールアドレスの形式になっているかどうかの判定
    var isValidEmail: Bool {
        let emailRegEx = "[A-Z0-9a-z._+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
        let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
        return emailTest.evaluate(with: self)
     }
    
}

①登録できるかどうかの判定

ここの実装がやや複雑ですが、やりたいこととしては

  • メールアドレスが有効かどうか
  • 2つのパスワードが入力されているかどうか
  • 2つのパスワードが一致しているかどうか
    を判定(bool)し、全ての条件が揃ったかどうか(bool)をcanSendに格納しています。

mapを使い、それぞれStringからBoolに型変換をしています。CombineLatestを使うことで、2つ以上の条件を合成しています。これらの機能を使うことにより、複雑な条件を組むことができます。
CombineLatestについてはこちらの記事が参考になります。

https://qiita.com/shiz/items/cff44fe801243b8a5496#combinelatest

②メールアドレスのチェック

登録できるかとは別に、メールアドレスの形式のチェックを行います。不正なメールアドレスの形式の場合はinvalidMailに文言が入ります。

③パスワードのチェック

2つのパスワードが一致しているかどうかを判定し、一致していない場合はinvalidPassに文言が入ります。

Viewの作成

Viewを作成します。先程のViewModelObservedObjectとして持っています。
Viewのスタイルについては一部省略しています。

struct ContentView: View {
    
    //ViewModel
    @ObservedObject var viewModel: ViewModel = .init()
    
    var body: some View {
        VStack {
            Group {
                //メールアドレスを格納
                TextField.init("mail address", text: self.$viewModel.mail)                    .textContentType(.emailAddress)

                if !self.viewModel.invalidMail.isEmpty { //メールアドレスが不正な場合
                    Text(self.viewModel.invalidMail)
                        .foregroundColor(.red)
                }
            }
  
            Group {
                //パスワードを格納
                SecureField.init("password", text: self.$viewModel.pass)
                    .textContentType(.newPassword)

                //もう一つのパスワードを格納
                SecureField.init("retype password", text: self.$viewModel.retype)
                    .textContentType(.newPassword)

                if !self.viewModel.invalidPass.isEmpty { //パスワードが一致しない場合
                    Text(self.viewModel.invalidPass)
                        .foregroundColor(.red)
                }
            }
            
            Button("Register") {
                debugPrint("register")
            }.disabled(!self.viewModel.canSend) //登録できる場合はボタンが有効化される
            .foregroundColor(.blue)
            
            Spacer()
            
        }
    }
}

TextFieldSecureFieldにそれぞれ値を入力すると、入力する度にmail, pass, retypeの3つから登録できるかどうかcanSendを判定します。
canSendが変更されれば、Buttonの状態が変更されます。

スクリーンショット

サンプルコード

サンプルコードはこちらに置いてあります。

https://github.com/usk-sample/RegistFormSample