Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FocusState changes in SwiftUI cause the keyboard to bounce

Tags:

ios

swift

swiftui

I'm making a sign-in interface for iOS in SwiftUI. The user should be able to easily switch from the username text field to the password text field by tapping the "next" button on the software keyboard. It's working well but the keyboard always bounces a little when switching between the two text fields for some reason.

Edit: As suggested in this answer I've added a Spacer into the VStack to make it fill the available space. The text fields aren't bouncing anymore but the keyboard unfortunately still is. I've updated the code and the GIF to reflect my changes.

recording of the issue

After googling a little it seemed like this wasn't a very common issue. This question seemed to be similar to what happens to me but following the answer and wrapping the text fields in a ScrollView or a GeometryReader did not change anything at all. This is my code:

struct AuthenticationView: View {
  @State var userName: String = ""
  @State var userAuth: String = ""
  
  @FocusState var currentFocus: FocusObject?
  enum FocusObject: Hashable { case name, auth }
  
  var body: some View {
    VStack(spacing: 8) {
      TextField("Username", text: $userName)
        .focused($currentFocus, equals: .name)
        .padding(8).background(Color.lightGray)
        .cornerRadius(8).padding(.bottom, 8)
        .textInputAutocapitalization(.never)
        .onSubmit { currentFocus = .auth }
        .autocorrectionDisabled(true)
        .keyboardType(.asciiCapable)
        .textContentType(.username)
        .submitLabel(.next)
      
      SecureField("Password", text: $userAuth)
        .focused($currentFocus, equals: .auth)
        .padding(8).background(Color.lightGray)
        .cornerRadius(8).padding(.bottom, 16)
        .textInputAutocapitalization(.never)
        .onSubmit { currentFocus = nil }
        .autocorrectionDisabled(true)
        .keyboardType(.asciiCapable)
        .textContentType(.password)
        .submitLabel(.done)
      
      Spacer() // This fixes the text fields
      // But it does not fix the keyboard
    }.padding(32)
  }
}
like image 543
Neofox Avatar asked Sep 05 '25 02:09

Neofox


1 Answers

There’s no proper solution to avoid this bouncing keyboard using pure SwiftUI. The issue arises because the TextField automatically loses focus when the user presses the return/next button. Although we reset the focus to the next TextField, it seems the process isn’t “fast enough” for the UI. However, the most important thing to note is that SwiftUI is built on UIKit.

It’s quite easy to access UITextField, the UIKit component that the SwiftUI TextField is based on. We can do this manually, but there's a great SwiftUI library called Introspect that simplifies this process in few lines of code.

Instead of using regular onSubmit modifier, we can create new customSubmit modifier that will be immune to SwiftUI keyboard glitch.

Check out the full implementation below with an example.

import SwiftUIIntrospect

fileprivate struct TextFieldSubmit: ViewModifier {

    // private class that conforms UITextFieldDelegate
    private class TextFieldKeyboardBehavior: UIView, UITextFieldDelegate {
        
        var submitAction: (() -> Void)?
        
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            submitAction?() // called when keyboard return button is pressed
            return false //cancel default behavior of return button
        }
    }
    
    //instance to UITextFieldDelegate
    private var textFieldKeyboardBehavior = TextFieldKeyboardBehavior()
    
    init(submitAction: @escaping () -> Void) {
        self.textFieldKeyboardBehavior.submitAction = submitAction
    }
    
    func body(content: Content) -> some View {
        content.introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { textField in
            //UITextField reached with Introspect
            textField.delegate = textFieldKeyboardBehavior
        }
    }
}


//make the modifier publicly available for use with TextField.
extension TextField {
    func customSubmit(submitAction: @escaping (() -> Void)) -> some View {
        self.modifier(TextFieldSubmit(submitAction: submitAction))
    }
}

//make the modifier publicly available for use with SecureField.
extension SecureField {
    func customSubmit(submitAction: @escaping (() -> Void)) -> some View {
        self.modifier(TextFieldSubmit(submitAction: submitAction))
    }
}



// example of use
TextField("Username", text: $username)
    .customSubmit { // new
        currentFocus = .auth 
    }

For a detailed insight into how it works, you can read my blog post:
SwiftUI TextField: Handling Keyboard the Right Way

like image 67
Ivo Leko Avatar answered Sep 07 '25 19:09

Ivo Leko