Search code examples
iosswiftswiftui

Animate parent view height without moving subviews


I’m trying to figure out how to change the height of a SwiftUI view with animation, without affecting the position of its subviews. I have an HStack containing some text objects. I want to animate the height of the view from 0 to 40 like so: animation changing view height

Note that the text objects shouldn't move as the parent view height changes (they should be anchored to the top of the parent view). I’ve tried the standard transitions such as: .transition(.scale(scale: 0, anchor: UnitPoint.top)). But that scales the entire view, including the subviews.

Here’s the view code:

struct SampleView: View {
    
    var body: some View {
        
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                
                let strings = ["Text 1", "Text 2", "Text 3"]
                ForEach(strings, id: \.self) { string in
                    Text(string)
                }
            }
            .foregroundStyle(Color.white)
        }
        .background(Color.orange)
    }
}

An instance of this SampleView is created when a button action withAnimation block updates a variable.

Any suggestions?


Solution

  • You can create a custom transition that scales a mask of the view.

    struct RevealTransition: Transition {
        func body(content: Content, phase: TransitionPhase) -> some View {
            content.mask {
                Rectangle()
                    .scaleEffect(y: phase.isIdentity ? 1 : 0, anchor: .top)
            }
        }
    }
    
    extension AnyTransition {
        static var reveal: AnyTransition { AnyTransition(RevealTransition()) }
    }
    

    Example usage:

    @State private var show = false
    var body: some View {
        VStack {
            if show {
                SampleView()
                    .transition(.reveal)
            }
            Button("Animate") {
                show.toggle()
            }
        }
        .animation(.default, value: show)
    }
    

    If you want the view to disappear in a different way, edit the body as needed, or use an asymmetric transition.