New in SwiftUI 3: FocusState in SwiftUI 3 and iOS 15

DevTechie Inc
Apr 6, 2022

@FocusState is a new property wrapper introduced in iOS 15 in conjunction with focused modifier. FocusState this property wrapper provides a way to read and write current focus positions within the view hierarchy.

The way this property wrapper works is when focus enters the view being modified, the wrapped value for the property wrapper updates to match a given prototype value and when focus leaves the view, wrapped value resets to false or nil. Depending upon the focus state you can take actions on entered data.

Better way to understand this is by example so let’s dive right in🤿

Let’s create a simple login form with username and password field, when focus is lost on each field we can perform validation on data. Check it out here(notice use of FocusState and focused modifier):

struct SimpleFocusState: View {
    
    @State private var username = ""
    @State private var password = ""
    @FocusState private var isUsernameFocused: Bool
    @FocusState private var isPasswordFocused: Bool
    
    var body: some View {
        Form {
            Section(header: Text("Sign In")) {
                TextField("Username", text: $username)
                    .focused($isUsernameFocused)
                    .onChange(of: isUsernameFocused) { newUsernameState in
                        if !newUsernameState {
                            checkUsernameAvailability(username: username)
                        }
                    }
                
                SecureField("Password", text: $password)
                    .focused($isPasswordFocused)
                    .onChange(of: isPasswordFocused) { newPasswordState in
                        if !newPasswordState {
                            checkPasswordIsValid(password: password)
                        }
                    }
                
                HStack {
                    Button("Sign In"){ }
                    Spacer()
                    Button("Cancel") { }
                }
            }
        }
        
    }
    
    private func checkUsernameAvailability(username: String) {
        print("\(username) available? \(Bool.random())")
    }
    
    private func checkPasswordIsValid(password: String) {
        if password.count > 8 {
            print("Password is valid")
        } else {
            print("Try better password please.")
        }
        
    }
}
Notice how we are using onchange observer to observe changes for those focusState values. Newly changed value is passed as part of closure argument which we can use to apply our logic.

We can also use focusState to move through our form fields. Let’s say we have a long registration form with many fields, we can have our keyboard’s submit label to move to next field.

Time to write some code 👩‍💻 👨‍💻 🧑‍💻

struct FocusOnSignup: View {
    enum Field: Hashable {
        case name
        case username
        case password
        case confimPassword
    }
    
    @State private var name = ""
    @State private var username = ""
    @State private var password = ""
    @State private var confimPassword = ""
    
    @FocusState private var focusedField: Field?
    
    var body: some View {
        Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
                .submitLabel(.next)
            
            TextField("Username", text: $username)
                .focused($focusedField, equals: .username)
                .submitLabel(.next)
            
            TextField("password", text: $password)
                .focused($focusedField, equals: .password)
                .submitLabel(.next)
            
            TextField("Confim Password", text: $confimPassword)
                .focused($focusedField, equals: .confimPassword)
                .submitLabel(.next)
            
        }
        .onSubmit {
            if focusedField == .name {
                focusedField = .username
            } else if focusedField == .username {
                focusedField = .password
            } else if focusedField == .password {
                focusedField = .confimPassword
            } else if focusedField == .confimPassword {
                signUp()
            }
        }
    }
    
    private func signUp() {
        print(name, username, password, confimPassword)
    }}
There is a lot going on so let’s unpack.

In order to better manage state we will create Hashable enum. This enum will represent all possible fields on our form.

enum Field: Hashable {
        case name
        case username
        case password
        case confimPassword
}
Next, we will create four state properties to track state of values being filled by the user:

@State private var name = ""@State private var username = ""@State private var password = ""@State private var confimPassword = ""
We can create FocusState variable next, which will keep value of current focus field:

@FocusState private var focusedField: Field?
Next comes our form , each field in form has focused modifier which takes the focus when next button in keyboard is pressed.

Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
                .submitLabel(.next)
            
            TextField("Username", text: $username)
                .focused($focusedField, equals: .username)
                .submitLabel(.next)
            
            TextField("password", text: $password)
                .focused($focusedField, equals: .password)
                .submitLabel(.next)
            
            TextField("Confim Password", text: $confimPassword)
                .focused($focusedField, equals: .confimPassword)
                .submitLabel(.next)
            
}
To experiment how this works, let’s swap values of username and password like below and run your app again:

Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
                .submitLabel(.next)
            
            TextField("Username", text: $username)
                .focused($focusedField, equals: .password)
                .submitLabel(.next)
            
            TextField("password", text: $password)
                .focused($focusedField, equals: .username)
                .submitLabel(.next)
            
            TextField("Confim Password", text: $confimPassword)
                .focused($focusedField, equals: .confimPassword)
                .submitLabel(.next)
            
        }
Output:

Notice how our focus jumps from name to password and than to username.

Last bit of code is focused on what happens when onSubmit (which is labeled next in our keyboard) is pressed.

.onSubmit {
    if focusedField == .name {
        focusedField = .username
    } else if focusedField == .username {
        focusedField = .password
    } else if focusedField == .password {
        focusedField = .confimPassword
    } else if focusedField == .confimPassword {
        signUp()
    }
}
Here we are trying to move our focus to next textfield depending upon where it is, which also shows that we can alter value of focusField variable programmatically.

Apart from checking for validation or moving through long form, focusState can be used to dismiss keyboard.

When we use keyboard type modifier to launch certain types of keyboards such as number pad, decimal pad or phone pad, we might find ourselves in a situation where there is no way to dismiss the keyboard. Well fear not, iOS 15’s focusStateis to rescue. We can modify focusState to dismiss the keyboard. Let’s look at an example.

struct DismissKeyboardExample: View {
    
    @FocusState private var isPhonePadOpen: Bool
    @State private var phoneNumber = ""
    
    var body: some View {
        Form {
            TextField("Phone number", text: $phoneNumber)
                .focused($isPhonePadOpen)
                .keyboardType(.phonePad)
            
            Button("Close") {
                isPhonePadOpen = false
            }
        }
    }
}
With that, we have reached the end of this article. Thank you once again for reading, if you liked it, don’t forget to subscribe our newsletter.