How to use auto-validating Text Fields in SwiftUI

Share on facebook
Share on twitter
Share on pinterest

Hello and welcome to a new SwiftUI tutorial! Today we’ll learn how to use an automatic validator for SwiftUI TextFields and SecureFields. By using auto-validating Text Fields, we can, for instance, automatically check if the user entered correct credentials without needing him to tap on a login button or something similar.

This is what we are going to create in this tutorial:

Setting up the Xcode project 🛠

To get started, create a new Xcode project. Make sure “Use SwiftUI” is selected. After creating the project, you can create some color sets in your Assets.xcassets folder for creating the neumorphic look as described in this tutorial.

Optionally, you can create some color sets for achieving the neumorphic look we’re going to use in this tutorial.

Next, click on File-New-File on your Xcode toolbar and create a new Swift file. Call this Constants. In this, we are going to store the correct pin the user needs to enter to get access to the home screen.

import Foundation

let pin = "1234"


That’s it! We’re ready to compose the UI of our app.

Composing the UI 🎨

We continue by composing the interface of our auto-validation app. For the look of this, we follow an approach called Neumorphism design. We made a whole tutorial about this design technique, so make sure you check it out.

First, we alter the background color of our ContentView. To do this, replace the default “Hello World” Text view with a ZStack and place a Rectangle inside it. Make sure the Rectangle is colored and covers the whole screen by using the following modifiers:

var body: some View {
        ZStack {
            Rectangle()
                .fill(Color("TextFieldColor"))
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
        }
    }


Note that we created the “TextFieldColor” in our Assets folder earlier, make sure you check it out this tutorial to see how you can do this.

On top of our background Rectangle, we put a VStack that contains the necessary Text views to instruct the user to enter his verification code.

ZStack {
            //...
            VStack(alignment: .leading) {
                Text("Please enter your verification code")
                    .font(.largeTitle)
                    .fontWeight(.semibold)
                    .padding(.top, 70)
                    .padding(.bottom, 20)
                Text("We have sent you a four-digit PIN to verificate your phone number.")
                    .lineLimit(2)
                    .fixedSize(horizontal: false, vertical: true)
                    .padding(.bottom, 20)
            }
        }


Now it’s time to create the Text Field. But before we can do this, we need to declare a @State property that hold’s the user’s input.

struct ContentView: View {
    
    @State var enteredPin = ""
    
    var body: some View {
        //...
    }
    
}


For hiding the user’s input we use a special type of a Text Field, a SecureField. We style this SecureField and set the numeric pad as the default keyboard type. Then, we push the VStack’s views up by using a Spacer.

VStack(alignment: .leading) {
                //...
                SecureField("PIN", text: $enteredPin)
                    .keyboardType(.numberPad)
                    .padding()
                    .background(Color("TextFieldColor"))
                    .cornerRadius(5.0)
                    .shadow(color: Color("LightShadow"), radius: 8, x: -8, y: -8)
                    .shadow(color: Color("DarkShadow"), radius: 8, x: 8, y: 8)
                Spacer()
            }


Finally, we apply some padding to the whole VStack:

VStack(alignment: .leading) {
                //...
            }
                .padding(30)


And that’s it! This is how your preview should look like now:

Creating the View Router 

Before moving on with implementing our Text Field auto-validating functionality, we need to think about what should happen when the user enters the right PIN. In this case, we want to present another view, for instance, the app’s home screen. Let’s create such a sample screen by clicking on File-New-File and creating a new SwiftUI view called HomeView. Inside the HomeView, we replace the “Hello World” Text with “Home”.

struct HomeView: View {
    var body: some View {
        Text("Home")
    }
}


But how can we navigate to this view when the user enters the right PIN?

Note: We made a tutorial about navigating independently in SwiftUI and we’re basically using the same technique as described in this, so make sure you check out.

First, we need to create our view router that manages which view should currently be presented to the user. For this purpose, create a new Swift file and call it ViewRouter. Make sure you import the SwiftUI framework. Inside this file, create a class also called ViewRouter and adopt the ObservableObject protocol so other views in our app can notice when something with the ViewRouter happens.

import Foundation
import SwiftUI


class ViewRouter: ObservableObject {
    
}


As said, our ViewRouter keeps track of which view should currently be shown. For this purpose, we create a @Published variable and call it currentPage. By default, we want to show our ContentView, which we will call “pin”. 

class ViewRouter: ObservableObject {
    
    @Published var currentPage = "pin"
    
}


Next, we need to set up our mother view that holds all views and shows either the ContentView or the HomeView. So let’s create a new SwiftUI view called MotherView. Inside this view, we observe the ViewRouter as an @EnvironmentObject, so every time the currentPage variable of the ViewRouter changes, we can show the corresponding view. We implement this functionality by writing:

struct MotherView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        VStack {
            if viewRouter.currentPage == "home" {
                Text("Home")
            } else if viewRouter.currentPage == "pin" {
                ContentView()
            }
        }
    }
}


Next, we need to set our MotherView as the root view when the app launches. To do this, we go to our SceneDelegate.swift file and adjust the root view inside the scene function as follows:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let viewRouter = ViewRouter()
        
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(viewRouter))
            self.window = window
            window.makeKeyAndVisible()
        }
    }


That’s it, now we’re capable of navigating to a new view which we will do when the user enters the correct PIN into the Text Field.

Again: If you didn’t understand what we just did with the ViewRouter, MotherView etc., please make sure you check out this tutorial where we explain everything in more detail.

Finally, we move the enteredPin @State of our ContentView into our ViewRouter class and turn it into a @Published property.

class ViewRouter: ObservableObject {
    
    @Published var enteredPin = ""
    
    //...
    
}

To bind our SecureField to the ViewRouter’s enteredPin property, we need to access it by using the @EnvironmentObject property wrapper inside our ContentView.

struct ContentView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        //...
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(ViewRouter())
    }
}


Now, we can bind our SecureField to the viewRouter’s enteredPin property.

SecureField("PIN", text: $viewRouter.enteredPin)
                    //...

Validating the Text Field ☑️

Every time the user enters something into the Text Field, we want to compare it to the PIN we stored in our Constants.swift file earlier. To achieve such auto-validating Text Fields, we must react every time the enteredPin property, which is bound to the SecureField, gets updated. In Swift, we do this by using the didSet property observer. 

We can add this observer by placing curly braces followed by the didSet closure behind our viewRouter’s enteredPin property.

@Published var enteredPin = "" {
        didSet {
            
        }
    }

The code we write inside the didSet curly braces gets executed every time the user modifies the enteredPin property by entering a character into the SecureField.

So every time the user enters a character we want to check if the enteredPin equals the stored pin. When this happens, we change the currentPage variable to “home” which causes the observing MotherView to eventually show the user the HomeView.

@Published var enteredPin = "" {
        didSet {
            if self.enteredPin == pin {
                self.currentPage = "home"
            }
        }
    }

So let’s see if that works. Run your app (in the regular simulator!) and enter the correct pin. Awesome! Once you entered the complete pin, the enteredPin’s didSet property observer automatically notices and tells the viewRouter to grant the user access to the home screen. Great! You just learned how to create auto-validating Text Fields in SwiftUI.

Implementing Phone Vibration and limiting the entered TextField Characters 📳

However, we want to prevent the user from entering a value that is more than four digits long. Additionally, we want to erase the Text Field’s content once the user entered a four digit pin that is wrong and give him haptic feedback by letting the phone vibrate.

To do this, we add an else-if statement to our didSet property observer, which gets executed once the user entered a pin that is four or more digits long which is wrong.

@Published var enteredPin = "" {
        didSet {
            if self.enteredPin == pin {
                self.currentPage = "home"
            } else if enteredPin.count >= 4 {
                
            }
        }
    }

Inside this statement, we reset the enteredPin and let the phone vibrate by writing the following code:

enteredPin = ""
           AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { return }


Let’s try this out by running the app again and entering a wrong code. You see that the SecureField’s content automatically gets emptied again. Of course, the vibration only works if you run the app on a real device.

Conclusion 🎊

That’s it! With the described technique you can create auto-validating Text Fields that automatically react and, for instance, grants access to another view when the user enters the correct input.

We’ve uploaded the whole source code of this app to GitHub.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

One reply on “How to use auto-validating Text Fields in SwiftUI”

Hey, very nice tutorial as usual! 💯
By the way, in the very last step, you forgot to tell us to import the AudioToolbox framework in the ViewRouter class in order to be able to use the AudioServicesPlayAlertSoundWithCompletion.

Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount