UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Textfield Looses Focus on Animation

Forums > SwiftUI

In my app I am trying to achieve an animation where the navigationBarTitle disappears when we start searching for something. I've done that using the code below.

However I am noticing something super weird, as can be seen in the video, when I try to tap the textfield, the animation happens and it looses focus so the keyboard doesn't come up. This only happens the first ever time, the screen is rendered, subsequent times it works fine.

As can be seen in this video I added a delay to the onEditingChanged callback to debug and it looks like the textfield first gains focus and then the animation happens and then it looses focus. Could someone explain as to why this is happening and if there is a way to fix it?

struct SearchBar: View {
    @Binding var searchTerm: String
    @Binding var isEditing: Bool
    @Binding var hasRenderedOnce: Bool

    var executeSearch: (String) -> Void
    var resetSearch: () -> Void

    var body: some View {
        HStack {
            HStack {
                Image(systemName: "magnifyingglass")
                    .foregroundColor(Color.white)
                    .padding(.vertical)
                    .padding(.leading)
                TextField("Enter game name",
                          text: $searchTerm,
                          onEditingChanged: {
                            if $0 {
                                isEditing = true
                                hasRenderedOnce = true
                            }
                          },
                          onCommit: {
                            executeSearch(searchTerm)
                          }
                )
                if searchTerm.count > 0 {
                    Button {
                        searchTerm = ""
                    } label: {
                        Image(systemName: "multiply")
                            .foregroundColor(Color.white)
                    }.padding(.horizontal)
                    .padding(.vertical)
                }
            }
            .background(Color("BrandBackground"))
            .cornerRadius(10)

            if isEditing {
                Button {
                    searchTerm = ""
                    isEditing = false
                    resetSearch()
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                } label: {
                    Text("Cancel")
                        .font(.body)
                        .fontWeight(.bold)
                }.padding(.horizontal)
            }
        }.navigationBarHidden(isEditing)
        .if(hasRenderedOnce) {
            $0.animation(.easeIn(duration: 0.25))
        }
    }
}

2      

Solved this using

struct CustomTextField: UIViewRepresentable {

    class Coordinator: NSObject, UITextFieldDelegate {

        @Binding var text: String
        @Binding var inEditing: Bool
        @Binding var isFirstResponder: Bool
        var onSubmit: () -> Void

        init(text: Binding<String>, inEditing: Binding<Bool>, isFirstResponder: Binding<Bool>, onSubmit:@escaping () -> Void) {
            _text = text
            self.onSubmit = onSubmit
            _inEditing = inEditing
            _isFirstResponder = isFirstResponder
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            isFirstResponder = false
            self.onSubmit()
            return true
        }
    }

    @Binding var text: String
    @Binding var inEditing: Bool
    @Binding var isFirstResponder: Bool
    var onSubmit: () -> Void

    func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        return textField
    }

    func makeCoordinator() -> CustomTextField.Coordinator {
        return Coordinator(text: $text, inEditing: $inEditing, isFirstResponder: $isFirstResponder, onSubmit: onSubmit)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
        uiView.text = text
        if isFirstResponder {
            uiView.becomeFirstResponder()
        } else {
            uiView.resignFirstResponder()
        }
    }
}

with the usage being something like

struct SearchBar: View {
    @Binding var searchTerm: String
    @Binding var isEditing: Bool
    @Binding var hasRenderedOnce: Bool
    @State var showKeyboard: Bool = false

    var executeSearch: (String) -> Void
    var resetSearch: () -> Void

    var body: some View {
        HStack {
            HStack {
                Image(systemName: "magnifyingglass")
                    .foregroundColor(Color.white)
                    .padding(.vertical)
                    .padding(.leading)
                CustomTextField(text: $searchTerm,
                                inEditing: $isEditing,
                                isFirstResponder: $showKeyboard) {
                    showKeyboard = false
                    executeSearch(searchTerm)
                }.frame(height: 50)
                .onTapGesture {
                    showKeyboard = true
                    isEditing = true
                    hasRenderedOnce = true
                }
                if searchTerm.count > 0 {
                    Button {
                        searchTerm = ""
                    } label: {
                        Image(systemName: "multiply")
                            .foregroundColor(Color.white)
                    }.padding(.horizontal)
                    .padding(.vertical)
                }
            }
            .background(Color("BrandBackground"))
            .cornerRadius(10)

            if isEditing {
                Button {
                    searchTerm = ""
                    isEditing = false
                    showKeyboard = false
                    resetSearch()
                } label: {
                    Text("Cancel")
                        .font(.body)
                        .fontWeight(.bold)
                }.padding(.horizontal)
            }
        }.navigationBarHidden(isEditing)
        .if(hasRenderedOnce) {
            $0.animation(.easeIn(duration: 0.25))
        }
    }
}

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.