Search code examples
iosswiftswiftui

How to apply rotationEffect in SwiftUI in both clockwise & anticlockwise directions?


I'm trying to animate a bell icon with the rotation effect but it only animates with the specified angle one way:

struct ContentView: View {
    @State var alarmSet = false
    
    var body: some View {
        ZStack {
            Color.black
            VStack {
                Image(systemName: "bell.fill")
                    .font(.system(size: 250))
                    .foregroundColor(.white)
                    .rotationEffect(.degrees(reminderSet ? 10 : 0), anchor: .top)
                    .animation(.interpolatingSpring(mass: 0.5, stiffness: 170, damping: 2.5, initialVelocity: 0), value: alarmSet)
                Button(action: {
                        alarmSet.toggle()
                }, label: {
                    Text(alarmSet ? "Alarm Set" : "Wake Me")
                        .font(.largeTitle)
                        .foregroundStyle(.white)
                })
                .padding(.top, 20)
            }
            .background(.black)
        }
        .ignoresSafeArea()
    }
}

With the code above, the bell animates between the left side & center, I'd like to get it to

  1. swing with the same angle towards the right as well
  2. swing this way thrice in total and settle to the resting state. The interpolating spring animation causes the bell to swing more than thrice as it comes to its resting state. Any help is appreciated.

Solution

  • You can use a keyframeAnimator (iOS 17+)

    Image(systemName: "bell.fill")
        .font(.system(size: 250))
        .foregroundColor(.white)
        .keyframeAnimator(initialValue: Angle.zero, trigger: alarmSet) { content, value in
            content.rotationEffect(value, anchor: .top)
        } keyframes: { _ in
            KeyframeTrack {
                let spring = Spring(mass: 0.5, stiffness: 170, damping: 2.5)
                SpringKeyframe(Angle.degrees(10), duration: 0.05, spring: spring)
                // Consider putting this part in a for loop if there are a lot of bounces
                SpringKeyframe(Angle.degrees(-10), duration: 0.1, spring: spring)
                SpringKeyframe(Angle.degrees(10), duration: 0.1, spring: spring)
                SpringKeyframe(Angle.degrees(-10), duration: 0.1, spring: spring)
                SpringKeyframe(Angle.degrees(10), duration: 0.1, spring: spring)
                // the last frame should have start velocity 0 so that it goes back to the initial position
                SpringKeyframe(Angle.zero, duration: 0.05, spring: spring, startVelocity: Angle.zero)
            }
        }
    

    Also consider using some of the built-in springs.