transition animations for subviews in SwiftUI

61 Views Asked by At

I've been researching this for ages and cannot find any answers on here or online that addresses how to create custom transitions for when a subview appears/disappears.

What I'm trying to do... I have a badge view TestBadgeView and a TestBadgeInsideHStackView, which as the name suggests, is just a HStack with an embedded TestBadgeView. On the preview screen, using TestTransitionView, I have a textfield that allows updating of the text that is displayed inside the badge. The badge at the middle of the screen is always visible but can be hidden/made visible using the Show/Hide Me button. That transition uses a scale animation by calling .transition(.scale) modifier. For the badge at the top right of the screen (to the right of the text badge--->, which is inside the TestBadgeInsideHStackView, I'm trying to get that badge to only display when the user types in hello world into the text field. That works but I can't get the transition animation to work. This is a simplified version of my code but I'm ultimately trying to get the parent view to pass data down to a subview which then, based on the logic is either visible or not, but that visibility should be animated with a transition. Any ideas?

You should be able to cut and paste the code below to test/trial.

import SwiftUI

struct TestTransitionView: View {
    
    @State var showView: Bool = true
    @State var text: String = "hello"
    
    var body: some View {
        
            VStack {
                               
                TextField("textfield", text: $text)
                    .padding()
                    .border(.gray)
                    .padding()
                
                TestBadgeInsideHStackView(shouldShowBadge: text == "hello world") {
                    Text(text.uppercased())
                }
                
                HStack {
    
                    if showView {
                        Text(text.uppercased())
                            .padding(.horizontal, 10)
                            .padding(.vertical, 7)
                            .background(Rectangle()
                                .fill(.gray).opacity(0.1)
                                .cornerRadius(6)
                            )
                            .font(.caption)
                            .fontWeight(.bold)
                            .transition(.scale)
                    }
                }
                .frame(height: 100)
                .padding()
                
                Spacer()
                Button("Show/Hide Me") {
                    withAnimation {
                        showView.toggle()
                    }
                }.padding(50)
            }
    }
}


struct TestBadgeInsideHStackView<Content: View> : View {
    let content: Content
    private var shouldShowBadge: Bool
    
    init(shouldShowBadge: Bool, @ViewBuilder content: () -> Content) {
        self.content = content()
        self.shouldShowBadge = shouldShowBadge
    }
    
    var body: some View {
        HStack {
            Text("badge ---> ")
            Spacer()
            if shouldShowBadge {
                TestBadgeView {
                    content
                }
                .transition(.scale)
            }
        }
        .frame(height: 100)
        .padding()
    }
}


struct TestBadgeView<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
        
    var body: some View {
        content
            .padding(.horizontal, 10)
            .padding(.vertical, 7)
            .background(Rectangle()
                .fill(.gray).opacity(0.1)
                .cornerRadius(6)
            )
            .font(.caption)
            .fontWeight(.bold)
            .transition(.scale)
    }
}



#Preview {
    TestTransitionView()
}

1

There are 1 best solutions below

1
Benzy Neez On

The flag showView is being toggled withAnimation, but changes to the text field are not associated with any form of animation. This is why the transition is not animated when a change is triggered due to the text being edited.

Here are two ways to fix:

  1. Add .animation() to the binding passed to the TextField:
TextField("textfield", text: $text.animation())
  1. Or, add an .animation modifier to the HStack in TestBadgeInsideHStackView:
// TestBadgeInsideHStackView

var body: some View {
    HStack {
        // content as before
    }
    .animation(.easeInOut, value: shouldShowBadge) // <- ADDED
    // other modifiers as before
}

BTW, I would suggest defining both the properties in TestBadgeInsideHStackView using private let:

private let content: Content
private let shouldShowBadge: Bool