0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

iOSで簡易コーポレートサイト(アプリ)を作成する #2 -バリデーション編-

Last updated at Posted at 2023-03-13

概要

Swift/SwiftUIで再現したウェブサイトのお問合せフォームに動的なバリデーションを実装していきます。

バリデーション

バリデーションを作成するにあたって、ViewModelを作成して切り分けます。
「File > New > File..」を選択し、「Swift File」を選択して「ContactFormViewModel.swift」を作成します。
000001.jpg
下記のように記述します。
PublishedはObservableObjectプロトコルに準拠したクラス内のプロパティを監視するのでクラスに継承させます。

ContactFormViewModel.swift
import Foundation

class ContactFormViewModel:ObservableObject{
    var name = ""
    var email = ""
    var contactBody = ""
}

ContactFormView.swiftでContactFormViewModelから値を取得するように編集します。

ContactFormView.swift
	import SwiftUI

	struct ContactFormView: View {
	    
+	    @ObservedObject var CFVM = ContactFormViewModel()

-	    @State var name = ""
-	    @State var email = ""
-       @State var contactBody = ""

	    var body: some View {
	        VStack{
	            Text("Contact").font(.title)
	                            
	            VStack{
	                Text("お問合わせフォーム").font(.headline).foregroundColor(.white)
	            }
	            .frame(maxWidth:.infinity)
	            .frame(height:100)
	            .background(.blue)
	            
	            VStack{
	                Text("お問合わせ内容を入力してください。").padding()
	                Group{
-	                    TextField("お名前",text:$name)
+	                    TextField("お名前",text:$CFVM.name)
	                        .keyboardType(.default)
	                        .textFieldStyle(RoundedBorderTextFieldStyle())
	                        .frame(maxWidth: .infinity)
	                        
	                    Text("お名前は必須です。")
	                        .multilineTextAlignment(.leading)
	                        .frame(maxWidth:.infinity,alignment: .leading)
	                        .padding(.leading, 10.0)
	                        .foregroundColor(.red)
	                        .font(.caption2)
	                    
	                }.padding(.bottom, 10.0)
	                
	                Group{
-	                    TextField("email",text:$email)
+	                    TextField("email",text:$CFVM.email)
	                        .keyboardType(.emailAddress)
	                        .textInputAutocapitalization(.never)
	                        .textFieldStyle(RoundedBorderTextFieldStyle())
	                        .frame(maxWidth: .infinity)
	                      
	                    Text("emailは必須かつemailの形式で入力してください。")
	                        .multilineTextAlignment(.leading)
	                        .frame(maxWidth:.infinity,alignment: .leading)
	                        .padding(.leading, 10.0)
	                        .foregroundColor(.red)
	                        .font(.caption2)
	                }.padding(.bottom, 10.0)
	                
	                Group{
-	                    TextField("問合わせ内容",text:$contactBody)
+	                    TextField("問合わせ内容",text:$CFVM.contactBody)
	                        .keyboardType(.emailAddress)
	                        .textFieldStyle(RoundedBorderTextFieldStyle())
	                        .frame(maxWidth: .infinity)
	                      
	                    Text("問合せ内容は必須かつ1文字以上10文字以内で入力して下さい。")
	                        .multilineTextAlignment(.leading)
	                        .frame(maxWidth:.infinity,alignment: .leading)
	                        .padding(.leading, 10.0)
	                        .foregroundColor(.red)
	                        .font(.caption2)
	                }.padding(.bottom, 10.0)
	                
	                Button(action:{
	                    
	                }){
	                    Text("送信")
	                        .frame(minWidth: 160)
	                        .foregroundColor(.white)
	                        .padding(12)
	                        .background(Color.accentColor)
	                        .cornerRadius(8)
	                }

	                Spacer()
	                    
	            }.frame(maxWidth:.infinity).frame(height:500)
	                            
	        }
	    }
	}

	struct ContactFormView_Previews: PreviewProvider {
	    static var previews: some View {
	        ContactFormView()
	    }
	}

name必須バリデーション

ContactFormViewModel.swiftに記述していきます。
nameが空ならfalse、それ以外はtrueを返すfunctionを作成します。
nameを監視するためPublished属性をつけます。

ContacFormViewModel.swift
    import Foundation

     class ContactFormViewModel:ObservableObject{
-       var name = ""
+       @Published  var name = ""
        var email = ""
        var contactBody = ""
        
+       //MARK: -Validation Function
+       func isNameValid() -> Bool{
+           return name != ""
+       }
+       
    }
コピペ用
import Foundation

class ContactFormViewModel:ObservableObject{
    @Published  var name = ""
    var email = ""
    var contactBody = ""
    
    //MARK: -Validation Function
    func isNameValid() -> Bool{
        return name != ""
    }
}

次に、isCompleteという変数を設定します。
isCompleteはボタンを制御するのに使います。
作成したisNameValidがfalseだったらisCompleteという変数にfalse、それ以外はtrueを入れるようにします。

ContactFormViewModel.swift
    import Foundation

    class ContactFormViewModel:ObservableObject{
        @Published var name = ""
        var email = ""
        var contactBody = ""
        
        //MARK: -Validation Founction
        func isNameValid() -> Bool{
            return name != ""
        }
        
+       var isComplete:Bool{
+           if !isNameValid() {
+               return false
+           }
+           return true
+       }

    }
コピペ用
var isComplete:Bool{
    if !isNameValid() {
        return false
    }
    return true
}

次に、名前のエラーメッセージを設定します。

ContactFormViewModel.swift
    import Foundation

    class ContactFormViewModel:ObservableObject{
        @Published var name = ""
        var email = ""
        var contactBody = ""
        
        //MARK: -Validation Founction
        func isNameValid() -> Bool{
            return name != ""
        }
        
        var isComplete:Bool{
            if !isNameValid() {
                return false
            }
            return true
        }
        
+       // MARK: - Validation Prompt
+       var namePrompt:String{
+           if isNameValid(){
+               return ""
+           }else{
+               return "お名前は必須です。"
+           }
+       }
    }

コピペ用
// MARK: - Validation Prompt
var namePrompt:String{
    if isNameValid(){
        return ""
    }else{
        return "お名前は必須です。"
    }
}

ContactFormにViewに適用させてみます。
nameのエラーのテキストを変数に変えるのみです。

ContactFormView.swift
    Group{
        TextField("氏名",text:$CFVM.name)
            .keyboardType(.default)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(maxWidth: .infinity)

-       Text("お名前は必須です。")           
+       Text(CFVM.namePrompt)
            .multilineTextAlignment(.leading)
            .frame(maxWidth:.infinity,alignment: .leading)
            .padding(.leading, 10.0)
            .foregroundColor(.red)
            .font(.caption2)
        
    }

また、ボタンをバリデーションエラーの間は押せないようにします。
isCompleteがfalseの間は、opacityを0.5、disabledに設定します。

ContactFormView.swift
    Button(action:{
        
    }){
        Text("送信")
            .frame(minWidth: 160)
            .foregroundColor(.white)
            .padding(12)
            .background(Color.accentColor)
            .cornerRadius(8)
    }
+   .opacity(CFVM.isComplete ? 1 : 0.5)
+   .disabled(!CFVM.isComplete)
コピペ用
.opacity(CFVM.isComplete ? 1 : 0.5)
.disabled(!CFVM.isComplete)

動的挙動を確認すると、氏名が未入力の間はエラーメッセージが出てボタンが押せず、入力すると動的にエラーメッセージが消えてボタンも押せるようになっていることを確認します。

emailと問合せ内容はまだ静的にエラーメッセージが出ている状態です。

000010.jpg 000020.jpg

emailバリデーション

同様の要領でemailバリデーションを設定します。
バリデーションは正規表現を使います。

ContactFormViewModel.swift
    import Foundation

    class ContactFormViewModel:ObservableObject{
        @Published var name = "" 
+       @Published  var email = ""
-       var email = ""
        var contactBody = ""
        
        //MARK: -Validation Function
        func isNameValid() -> Bool{
            return name != ""
        }
        
+       func isEmailValid() -> Bool{
+           let emailTest = NSPredicate(format:"SELF MATCHES %@","^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+$")
+           return emailTest.evaluate(with: email)
+       }
        
        var isComplete:Bool{
-           if !isNameValid() {
+           if !isNameValid() ||
+               !isEmailValid() {
                return false
            }
            return true
        }
        
        // MARK: - Validation Prompt           
        var namePrompt:String{
            if isNameValid(){
                return ""
            }else{
                return "氏名は必須です。"
            }
        }
        
+       var emailPrompt:String{
+           if isEmailValid(){
+               return ""
+           }else{
+               return "emailアドレスは必須かつemailの形式で入力してください。"
+           }
+       }
    }
コピペ用
import Foundation

class ContactFormViewModel:ObservableObject{
    
    @Published var name = ""
    @Published var email = ""
    var contactBody = ""
    
    //MARK: -Validation Function
    func isNameValid() -> Bool{

        return name != ""
    }
    
    func isEmailValid() -> Bool{
        let emailTest = NSPredicate(format:"SELF MATCHES %@","^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+$")
        return emailTest.evaluate(with: email)
    }
    
    var isComplete:Bool{
        if !isNameValid() ||
            !isEmailValid() {

            return false
        }
        return true
    }
    
    // MARK: - Validation Prompt
    var namePrompt:String{
        if isNameValid(){
            return ""
        }else{
            return "お名前は必須です。"
        }
    }
    
    var emailPrompt:String{
        if isEmailValid(){
            return ""
        }else{
            return "emailアドレスは必須かつemailの形式で入力してください。"
        }
    }
}

ContactFormViewのemailバリデーションメッセージ表示部分を編集します。

ContactFormView.swift
    Group{
        TextField("email",text:$CFVM.email)
            .keyboardType(.emailAddress)
            .textInputAutocapitalization(.never)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(maxWidth: .infinity)
          
-       Text("emailは必須かつemailの形式で入力してください。")
+       Text(CFVM.emailPrompt)
            .multilineTextAlignment(.leading)
            .frame(maxWidth:.infinity,alignment: .leading)
            .padding(.leading, 10.0)
            .foregroundColor(.red)
            .font(.caption2)
    }

バリデーションが同様に動くか確認します。

Bodyバリデーション

同様に設定していきます。

ContactFormViewModel.swift

    import Foundation

    class ContactFormViewModel:ObservableObject{
        @Published var name = ""
        @Published var email = ""
-       var contactBody = ""
+       @Published var contactBody = ""
        
        //MARK: -Validation Function
        func isNameValid() -> Bool{
            return name != ""
        }
        
        func isEmailValid() -> Bool{
            let emailTest = NSPredicate(format:"SELF MATCHES %@","^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+$")
            return emailTest.evaluate(with: email)
        }
        
+       func isContactBodyValid() -> Bool{
+           let contactBodyTest = NSPredicate(format:"SELF MATCHES %@","^.{1,10}$")
+           return contactBodyTest.evaluate(with: contactBody)
+       }
        
        var isComplete:Bool{
            if !isNameValid() ||
-               !isEmailValid(){
+               !isEmailValid() ||
+               !isContactBodyValid(){
                return false
            }
            return true
        }
        
        // MARK: - Validation Prompt
            
        var namePrompt:String{
            if isNameValid(){
                return ""
            }else{
                return "氏名は必須です。"
            }
        }
        
        var emailPrompt:String{
            if isEmailValid(){
                return ""
            }else{
                return "Emailアドレスは必須かつEmailの形式で入力してください。"
            }
        }
        
+       var contactBodyPrompt:String{
+           if isContactBodyValid(){
+               return ""
+           }else{
+               return "お問い合わせ内容は1文字以上10文字以下で入力してください。"
+           }
+       }
    }

コピペ用
import Foundation

class ContactFormViewModel:ObservableObject{
    
    @Published var name = ""
    @Published var email = ""
    @Published var contactBody = ""
    
    //MARK: -Validation Function
    func isNameValid() -> Bool{

        return name != ""
    }
    
    func isEmailValid() -> Bool{
        let emailTest = NSPredicate(format:"SELF MATCHES %@","^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+$")
        return emailTest.evaluate(with: email)
    }
    
    func isContactBodyValid() -> Bool{
        let contactBodyTest = NSPredicate(format:"SELF MATCHES %@","^.{1,10}$")
        return contactBodyTest.evaluate(with: contactBody)
    }
    
    var isComplete:Bool{
        if !isNameValid() ||
            !isEmailValid() ||
            !isContactBodyValid(){

            return false
        }
        return true
    }
    
    // MARK: - Validation Prompt
    var namePrompt:String{
        if isNameValid(){
            return ""
        }else{
            return "お名前は必須です。"
        }
    }
    
    var emailPrompt:String{
        if isEmailValid(){
            return ""
        }else{
            return "emailアドレスは必須かつemailの形式で入力してください。"
        }
    }
    
    var contactBodyPrompt:String{
        if isContactBodyValid(){
            return ""
        }else{
            return "お問い合わせ内容は1文字以上10文字以下で入力してください。"
        }
    }

}

ContactFormViewのbodyバリデーションメッセージ表示部分を編集します。

ContactFormView.swift
    Group{
        TextField("問い合わせ",text:$CFVM.contactBody)
            .keyboardType(.emailAddress)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(maxWidth: .infinity)

-       Text("問合せ内容は必須かつ1文字以上10文字以内で入力して下さい。")    
+       Text(CFVM.contactBodyPrompt)
            .multilineTextAlignment(.leading)
            .frame(maxWidth:.infinity,alignment: .leading)
            .padding(.leading, 10.0)
            .foregroundColor(.red)
            .font(.caption2)
    }

動作確認

エラーメッセージとボタンの色が動的に表示されたり消えたりすることを確認します。
000030.jpg
000040.jpg
000050.jpg

バリデーションが完成しました。

最終的なコード

ContactFormViewModel

ContactFormViewModel
import Foundation

class ContactFormViewModel:ObservableObject{
    
    @Published var name = ""
    @Published var email = ""
    @Published var contactBody = ""
    
    //MARK: -Validation Function
    func isNameValid() -> Bool{

        return name != ""
    }
    
    func isEmailValid() -> Bool{
        let emailTest = NSPredicate(format:"SELF MATCHES %@","^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+$")
        return emailTest.evaluate(with: email)
    }
    
    func isContactBodyValid() -> Bool{
        let contactBodyTest = NSPredicate(format:"SELF MATCHES %@","^.{1,10}$")
        return contactBodyTest.evaluate(with: contactBody)
    }
    
    var isComplete:Bool{
        if !isNameValid() ||
            !isEmailValid() ||
            !isContactBodyValid(){

            return false
        }
        return true
    }
    
    // MARK: - Validation Prompt
    var namePrompt:String{
        if isNameValid(){
            return ""
        }else{
            return "お名前は必須です。"
        }
    }
    
    var emailPrompt:String{
        if isEmailValid(){
            return ""
        }else{
            return "emailアドレスは必須かつemailの形式で入力してください。"
        }
    }
    
    var contactBodyPrompt:String{
        if isContactBodyValid(){
            return ""
        }else{
            return "お問い合わせ内容は1文字以上10文字以下で入力してください。"
        }
    }

}

ContactFormView

ContactFormView
import SwiftUI

struct ContactFormView: View {
    @ObservedObject var CFVM = ContactFormViewModel()
    
    var body: some View {
        VStack{
            Text("Contact").font(.title)
                            
            VStack{
                Text("お問合わせフォーム").font(.headline).foregroundColor(.white)
            }
            .frame(maxWidth:.infinity)
            .frame(height:100)
            .background(.blue)
            
            VStack{
                Text("お問合わせ内容を入力してください。").padding()
                Group{
                    TextField("お名前",text:$CFVM.name)
                        .keyboardType(.default)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .frame(maxWidth: .infinity)
                        
                    Text(CFVM.namePrompt)
                        .multilineTextAlignment(.leading)
                        .frame(maxWidth:.infinity,alignment: .leading)
                        .padding(.leading, 10.0)
                        .foregroundColor(.red)
                        .font(.caption2)
                    
                }.padding(.bottom, 10.0)
                
                Group{
                    TextField("email",text:$CFVM.email)
                        .keyboardType(.emailAddress)
                        .textInputAutocapitalization(.never)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .frame(maxWidth: .infinity)
                      
                    Text(CFVM.emailPrompt)
                        .multilineTextAlignment(.leading)
                        .frame(maxWidth:.infinity,alignment: .leading)
                        .padding(.leading, 10.0)
                        .foregroundColor(.red)
                        .font(.caption2)
                }.padding(.bottom, 10.0)
                
                Group{
                    TextField("問合わせ内容",text:$CFVM.contactBody)
                        .keyboardType(.emailAddress)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .frame(maxWidth: .infinity)
                      
                    Text(CFVM.contactBodyPrompt)
                        .multilineTextAlignment(.leading)
                        .frame(maxWidth:.infinity,alignment: .leading)
                        .padding(.leading, 10.0)
                        .foregroundColor(.red)
                        .font(.caption2)
                }.padding(.bottom, 10.0)
                
                Button(action:{
                      
                }){
                    Text("送信")
                        .frame(minWidth: 160)
                        .foregroundColor(.white)
                        .padding(12)
                        .background(Color.accentColor)
                        .cornerRadius(8)
                }
                .opacity(CFVM.isComplete ? 1 : 0.5)
                .disabled(!CFVM.isComplete)

                Spacer()
                    
            }.frame(maxWidth:.infinity).frame(height:500)
                            
        }
    }
}

struct ContactFormView_Previews: PreviewProvider {
    static var previews: some View {
        ContactFormView()
    }
}

関連コンテンツ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?