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

2

5

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)
      }
}
Statuary answered 21/1, 2022 at 21:39 Comment(0)
O
6

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()
    }
}
Olga answered 22/1, 2022 at 9:45 Comment(2)
Lovely. As usual with SwiftUI, I wish I understood why it worked.Rehnberg
This is brilliant, had same problem, your solution is excellent. I was able to do it without the animation modifier.Room
S
1

Here a way for you:

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

    var body: some View {
        
        VStack(spacing: 0) {
            Spacer()
            
            ZStack {
                Color.green.opacity(0.25).cornerRadius(20)
                Text("Second View")
            }
            .frame(width: 300, height: 300)
            .offset(y: isSecondViewPresented ? 0 : 300)
            .clipShape(RoundedRectangle(cornerRadius: 20))
            
            
            ZStack {
                Color.black.opacity(0.1).cornerRadius(20)
                Text("First View")
            }
            .frame(width: 300, height: 150)
            
            Button("Animate In / Out") {
                isSecondViewPresented.toggle()
            }
            .padding()
        }
        .animation(.easeInOut, value: isSecondViewPresented)
        
    }

}

enter image description here

Sharlenesharline answered 21/1, 2022 at 22:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.