Search code examples
swiftuitextfield

How can I create a horizontal TextField with a "done" button to dismiss it in SwiftUI?


I have text fields in my app that need to have a vertical axis (multi-lined) so the use can enter long textual inputs and easily see what they're doing. I would like to have a done button that lets them dismiss the onscreen keyboard or better yet, jump to the next field if there is one. I would really love a pure SwiftUI solution

I have tried

  • Using .submitLabel(.done), this creates the done button but it adds a new line when pressed, rather than dismissing the keyboard.
  • Using the onCommit argument of TextField and passing in a function that dismisses the keyboard. This works but you can't use both the onCommit and axis arguments on a given TextField
  • Setting .lineLimit(nil), had no effect
  • Wrapping the textView in an HStack, which of couse had no effect

Solution

  • You can control the focus of a textfield (wether if it is active or not) with the @FocusState wrapper in combination with the .focused(_) modifier, like so:

    import SwiftUI
    
    struct ContentView: View {
        
        // @FocusState can change the focus of a textfield (if it is active or not)
        @FocusState var isEmail: Bool  
        @FocusState var isUsername: Bool  
        @FocusState var isPassword: Bool  
        
        @State var email: String = ""
        @State var username: String = ""
        @State var password: String = ""
        
        var body: some View {
            VStack {
                TextField("Email", text: $email, axis: .horizontal)
                    .submitLabel(.continue)
                    .focused($isEmail) // connects the `isEmail` focus state value to the email textfield 
                    .onSubmit() {
                        // on submit, when the user hits enter, the email focus gets removed and the username focus gets activated
                        isEmail = false
                        isUsername = true
                    }
                
                TextField("Username", text: $username, axis: .horizontal)
                    .submitLabel(.continue)
                    .focused($isUsername)
                    .onSubmit() {
                        isUsername = false
                        isPassword = true
                    }
                
                SecureField("Password", text: $password)
                    .submitLabel(.done)
                    .focused($isPassword)
                    .onSubmit() {
                        isPassword = false 
                    }
            }
            .textFieldStyle(.roundedBorder)
            .padding()
            .onAppear {
                isEmail = true // default focus when the view appears is the mail textfield
            }
        }
    }