Search code examples
iosswiftswiftuiswiftui-animation

How to clip a view while using a SwiftUI .move transition / animation


I'm trying to animate in a view and make it appear as if it's a sort of drawer opening from another view. This is all fine except if the first view is not opaque. It appears that you can see the animating view the moment it begins animating. Is there a way to clip this so it appears that the view is growing from the top of the bottom view?

Even without opacity this is an issue if where you're animating in from isn't a covered (demoed in second gif)

Animation example Other animation example

Sample Code:

struct ContentView: View {
    @State private var showingSecondView: Bool = false
    

    var body: some View {
        VStack(spacing: 0) {
            Spacer()
            if showingSecondView {
                ZStack {
                    Color.green.opacity(0.25)
                    Text("Second View")
                }
                .frame(width: 300, height: 300)
                .transition(.move(edge: .bottom))
            }
            ZStack {
                Color.black.opacity(1)
                Text("First View")
            }
            .frame(width: 300, height: 300)
            Button("Animate In / Out") {
                showingSecondView.toggle()
            }
            .padding()
        }
        .animation(.easeInOut, value: showingSecondView)
      }
}

Solution

  • It is possible to do by clipping exact container of 'drawer'. Here is a demo of possible approach.

    Tested with Xcode 13.2 / iOS 15.2 (Simulator slow animation is ON for better demo)

    demo

    var body: some View {
        VStack(spacing: 0) {
            Spacer()
            VStack {
                if showingSecondView {
                    ZStack {
                        Color.green.opacity(0.25)
                        Text("Second View")
                    }
                    .transition(.move(edge: .bottom))
                } else {
                    Color.clear // << replacement for transition visibility
                }
            }
            .frame(width: 300, height: 300)
            .animation(.easeInOut, value: showingSecondView)  // << animate drawer !!
            .clipped()            // << clip drawer area
            
            ZStack {
                Color.black.opacity(0.2)
                Text("First View")
            }
            .frame(width: 300, height: 300)
    
            Button("Animate In / Out") {
                showingSecondView.toggle()
            }
            .padding()
        }
    }