UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Applying two different animation modifiers to the same view modifier

Forums > SwiftUI

I have had some interesting findings while working with animations in SwiftUI. From what I have found so far it is still a bit tricky to do animations in sequence if the animation involves changing a view modifier in two different ways. For example I want to offset a view in the x direction and then after one 1 second in the y direction. It seems like though you can only apply one animation modifier to the offset modifier as only the second animation is respected. Any thoughts?

struct MyView: View {

    @State private var isVerticallyExpanded = false
    @State private var isHorizontallyExpanded = true

    var body: some View {
        VStack {
            ZStack {
                ForEach(0..<3){ index in

                    Circle()
                        .stroke(Color.blue)
                        .frame(width: 50, height: 50)
                        .offset(x: isHorizontallyExpanded ? 10 * CGFloat(index) : 0)
                        .animation(.default)
                        .offset(y: isVerticallyExpanded ? 60 * CGFloat(index) : 0)
                        .animation(.default.delay(2.0))
                }
            }
            Button(action: {
                isHorizontallyExpanded.toggle()
                isVerticallyExpanded.toggle()
            }, label: {
                Text("Animate")
            })
        }
    }
}

I am attempting to animate the circle to move horizontially and then vertically on a button tap. I am also trying to make it so it will do the animation in reverse when it is tapped so that is my movtiation for using state variables. I have seem other places that using DispatchQueue.main.asyncAfter can have the same affect but not handle correctly if the user taps the button again before the first animation is complete.

2      

Is this more like what you wanted?

struct MyView: View {

    @State private var isVerticallyExpanded = false
    @State private var isHorizontallyExpanded = true

    var body: some View {
        VStack {
            ZStack {
                ForEach(0..<3){ index in

                    Circle()
                        .stroke(Color.blue)
                        .frame(width: 50, height: 50)
                        .offset(x: isHorizontallyExpanded ? 10 * CGFloat(index) : 0)
                        .offset(y: isVerticallyExpanded ? 60 * CGFloat(index) : 0)
                }
            }
            Button(action: {
                withAnimation(.default) {
                    isHorizontallyExpanded.toggle()
                }
                withAnimation(.default.delay(1.0)) {
                    isVerticallyExpanded.toggle()
                }
            }, label: {
                Text("Animate")
            })
        }
    }
}

2      

fileprivate struct ShiftEffect: GeometryEffect {
    var animationData: CGFloat
    var horizontalShift: CGFloat
    var verticalShift: CGFloat

    init(animationAmount: CGFloat, horizontalShift: CGFloat, verticalShift: CGFloat) {
        self.animationData = animationAmount
        self.horizontalShift = horizontalShift
        self.verticalShift = verticalShift
    }

    var animatableData: CGFloat {
        get { return animationData }
        set { animationData = newValue }
    }

    func effectValue(size: CGSize) -> ProjectionTransform {
        var xOffset: CGFloat
        var yOffset: CGFloat

        if animationData < 0.5 {
            xOffset = CGFloat(horizontalShift - (horizontalShift * 2 * animationData))
            yOffset = 0
        } else {
            yOffset = CGFloat(verticalShift * (animationData - 0.5))
            xOffset = 0
        }
        return ProjectionTransform(CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: xOffset, ty: yOffset))
    }
}

struct ContentView: View {
    @State var isShifted = false
    var body: some View {
        Rectangle()
            .frame(width: 100, height: 100)
            .modifier(
                ShiftEffect(
                    animationAmount: isShifted
                        ? 1.0 : 0.0,
                    horizontalShift: -150,
                    verticalShift: 150
                )
            )
            .animation(.easeInOut(duration: 3.0))
            .onTapGesture {
                isShifted.toggle()
            }
    }
}

So it seems like you can't apply two of the same view modifier (offset in this case) as SwiftUI doesnt know how to handle that. So I created a custom geometry effect to achieve this. This resource was very helpful: https://swiftui-lab.com/swiftui-animations-part2/

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.