swiftswiftuikeyboarduibuttonvisual-glitch

SwiftUI button glitches on keyboard dismiss


I have a button on my view that, when pressed, calls a hideKeyboard function which is the following:

func hideKeyboard() {
    let resign = #selector(UIResponder.resignFirstResponder)
    UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}

However, when the button is pressed and the keyboard gets dismissed, a glitch occurs where the button stays in place while the view moves downward: https://giphy.com/gifs/Z7qCDpRSGoOb9CbVRQ

Reproducible example:

import SwiftUI


struct TestView: View {
    @State private var username: String = ""
    @State private var password: String = ""
    @State private var isAnimating: Bool = false

    var lightGrey = Color(red: 239.0/255.0,
                                      green: 243.0/255.0,
                                      blue: 244.0/255.0)
    
    var body: some View {
        ZStack() {
            VStack() {
                Spacer()
                Text("Text")
                    .font(.title)
                    .fontWeight(.semibold)
                    .padding(.bottom, 15)
                    .frame(maxWidth: .infinity, alignment: .leading)
                Text("Text")
                    .font(.subheadline)
                    .padding(.bottom)
                    .frame(maxWidth: .infinity, alignment: .leading)
                TextField("username", text: $username)
                    .padding()
                    .background(self.lightGrey)
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
                SecureField("password", text: $password)
                    .padding()
                    .background(self.lightGrey)
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
                Button(action: {
                    self.hideKeyboard()
                    login()
                })
                {
                    if isAnimating {
                        ProgressView()
                            .colorScheme(.dark)
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding()
                            .frame(maxWidth: .infinity)
                            .background(Color.green)
                            .cornerRadius(10.0)
                            .padding(.bottom, 20)
                    }
                    else {
                        Text("Text")
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding()
                            .frame(maxWidth: .infinity)
                            .background(Color.green)
                            .cornerRadius(10.0)
                            .padding(.bottom, 20)
                    }
                }
                .disabled(username.isEmpty || password.isEmpty || isAnimating)
                Text("Text")
                    .font(.footnote)
                    .frame(maxWidth: .infinity, alignment:.leading)
                Spacer()
            }
            .padding()
            .padding(.bottom, 150)
            .background(Color.white)
        }
    }
    
    func hideKeyboard() {
        let resign = #selector(UIResponder.resignFirstResponder)
        UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
    }
    
    func login() {
        isAnimating = true
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

Solution

  • Looks like a conflict between UIApplication.shared.sendAction and SwiftUI: hiding the keyboard starts the animation, but updating the isAnimating property resets it.

    Changing the order of calls solves the problem:

    Button(action: {
        login()
        self.hideKeyboard()
    })