Home > Software engineering >  Animate a view from the side in SwiftUI
Animate a view from the side in SwiftUI

Time:03-31

Say I have this view:

struct CircleView: View {
    var body: some View {
        Circle()
    }
}

In another view, I have something like this:

var body: some View {
    GeometryReader { geo in
        ZStack {
            //some other views
                
            if someState == .showCircle {
                CircleView()
            }
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .edgesIgnoringSafeArea(.all)
        }
    }

When someState becomes .showCircle, I want to have the CircleView animate in from the right side of the screen into a position where the left hemisphere is visible, so it's halfway on the screen, and dim the rest of the content in the ZStack. What's the best way to set up this kind of animation?

CodePudding user response:

You can use a .transition() modifier on the circle, and the .blur() modifier on the other views that are not the circle, combined with .animation().

Also, on the views that are "not circle", you need to add another modifier: .allowsHitTesting(), to avoid having the user interacting with the views that are blurred.

Remember that, when you trigger someState, you must use the .withAnimation() closure, otherwise the circle will not slide in.

Here's how the code looks like (I added some things on the ZStack just to provide an example). I also made the someState variable a boolean for convenience in the example, but in your case you can just check for the enum.

CircleView

struct CircleView: View {
    @Binding var someState: Bool
    var body: some View {
        Circle()
            .foregroundColor(.red)
            .overlay(Text("All is blur"))
            .onTapGesture {
                someState.toggle()
            }
        
        // These modifiers are necessary to animate the circle
            .transition(.move(edge: .trailing))
            .animation(.easeInOut, value: someState)
    }
}

Another view

    @State private var someState = false
    var body: some View {
        GeometryReader { geo in
            ZStack {
                VStack {
                    Button {
                        
                        // Without this closure, the circle does not slide in
                        withAnimation {
                            someState.toggle()
                        }
                    } label: {
                        Text("Show circle")
                    }
                    .padding()
                    
                    Spacer()
                    
                    Text("Bottom")
                        .padding()
                }
                
                // This modifier blurs the view that is not the circle
                .blur(radius: someState ? 5 : 0)
                
                // This modifier animates the blur effect
                .animation(.easeInOut, value: someState)
                
                // This is necessary to block the view from accepting user taps
                // when the circle is showing
                .allowsHitTesting(!someState)
                
                if someState {
                    CircleView(someState: $someState)

                        // Stop the circle half-way
                        .offset(x: UIScreen.main.bounds.width / 2, y: 0)
                }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .ignoresSafeArea()
        }
    }
  • Related