Search code examples
swiftuitransitionios-animations

Subview showing/hiding transition not working


Still a beginner in SwiftUI animations and struggling with this issue.

I have a custom search bar that resembles its native counterpart. Everything's working fine except for the inserting/removing views animations. Here's what's happening:

  1. The search bar slides left of right whether it's editing or not.
  2. If the user taps on the search bar, both X and Cancel should fade-in.
  3. If the Cancel button is pressed, both X and Cancel should fade-out.

Only step 1 is animating correctly, steps 2 and 3 are not.

Here's the code for the search bar component:

struct CustomSearchBar: View {
    var prompt: String
    @Binding var query: String
    @FocusState private var isEditing: Bool
    @State var onSubmit: () -> Void
    
    var body: some View {
        HStack(alignment: .center) {
            TextField(prompt, text: $query)
                .onSubmit(onSubmit)
                .focused($isEditing)
                .padding(8)
                .padding(.horizontal, 25)
                .background(Color(.systemGray6))
                .overlay(
                    HStack {
                        Image(systemName: "magnifyingglass")
                            .foregroundColor(.gray)
                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                            .padding(.leading, 8)
                        
                        if isEditing {
                            Button {
                                self.query = ""
                            } label: {
                                Image(systemName: "multiply.circle.fill")
                                    .foregroundColor(.gray)
                                    .padding([.leading, .trailing], 8)
                            }
                            .transition(.opacity)
                        }
                    }
                )
                .onTapGesture {
                    self.isEditing = true
                }
                .submitLabel(.search)
                .transition(.slide)
            
            if isEditing {
                Button {
                    self.isEditing = false
                    self.query = ""
                } label: {
                    Text("Cancel")
                        .foregroundColor(.red)
                }
                .padding(.trailing, 10)
                .transition(.opacity)
            }
        }
        .animation(.linear(duration: 0.3), value: isEditing)
    }
}

And how to use it:

struct Search: View {
    @State var query: String = ""
    @FocusState private var searchIsFocused: Bool

    var body: some View {
        CustomSearchBar(prompt: "Search", query: $query, onSubmit: { submittedSearch() })
            .focused($searchIsFocused)
    }

    func submittedSearch() {
        // do something with search
    }
}

I tried wrapping self.isEditing = false in withAnimation blocks but it results in no animation at all. I find it using the animation modifier makes more sense, but it's not detecting subview insertion/removal. I came across several examples and they all work out like what I posted but, unfortunately, I can't spot the error in my case...


Solution

  • Workaround, use @FocusState only for the focused modifier, and create a @State variable to control the animations.

    @FocusState private var focused: Bool {
        didSet {
            self.editing = self.focused
        }
    }
    @State private var editing = false