Search code examples
swiftswiftuicore-animation

SwiftUI rotationEffect() internal animation strange behavior and I can't disable it


I'm exploring how to handle infinite animation. I learn how to start it and trying to stop it manually. The best solution I find is to calculate current value of animated property and set it while stopping animation. Well, it works nice except one moment. It looks like rotationEffect() uses built in self animation to stop rotation. if you try start and stop animation immediately, you will se something like velocity physics. Something like .spring animation, but I used .linear. I tried to disable it by using withAnimation(.none), but failed. And if you stop rotation close to 90 degrees (but less then 90), it overshoot 90 degrees more. The only way to fix this I see is to build my own rotation modifier using CG framework.

Any other ideas?

here is the code:

import SwiftUI

class AnimationHandler: ObservableObject{
    @Published var isStarted: Bool = true
}

struct AnimationStopping: View {
    @ObservedObject var animationHandler = AnimationHandler()
    var body: some View {
        VStack{
            Spacer()
            AnimatedRectObservedObject(animationHandler: self.animationHandler)
            Spacer()
            Button(action: {
                self.animationHandler.isStarted.toggle()
            }){
                Text(self.animationHandler.isStarted ? "stop animation" : "start animation")
            }
        }
    }
}

struct AnimatedRectObservedObject: View{
    @State var startTime: Date = Date()
    @State var angle: Double = 0
    @ObservedObject var animationHandler: AnimationHandler
    var animation: Animation = Animation.linear(duration: 1).repeatForever(autoreverses: false)
    var body: some View{
       VStack{
            Rectangle()
                .fill(Color.green)
                .frame(width: 200, height: 200)
                .rotationEffect(Angle(degrees: angle))
                .animation(animationHandler.isStarted ? animation : .default)
            Spacer()
       }.onReceive(animationHandler.objectWillChange){
            let newValue = self.animationHandler.isStarted
            if newValue == false{
                 let timePassed = Date().timeIntervalSince(self.startTime)
                 let fullSecondsPassed = Double(Int(timePassed))
                 let currentStage = timePassed - fullSecondsPassed
                 let newAngle = self.angle - 90 + currentStage * 90
                withAnimation(.none){//not working:(((
                 self.angle = newAngle
                }
            }else {
                 self.startTime = Date()
                 self.angle += 90
             }
       }
       .onAppear{
            self.angle += 90
            self.startTime = Date()
        }
    }
}

update: Actually, custom modifier will not help. I use it, put print on it, get back actual animation time of it through @ObservableObject and watched this:

current animation position 0.15000057220458984
current animation position 0.15833377838134766
current animation position 0.16666698455810547
current animation position 0.17500019073486328
current animation position 0.1833333969116211
current animation position 0.1916666030883789
current animation position 0.19999980926513672
current animation position 0.20833301544189453
animated from 0.0 to 1.0 stopped at 0.20833301544189453
current animation position 0.21662975207982527
current animation position 0.22470279080698674
current animation position 0.2323109631133775
current animation position 0.23920465996798157
current animation position 0.24513085941998725
current animation position 0.24984809499710536
current animation position 0.25314617272852047
current animation position 0.25487106971286266
current animation position 0.25495114064688096
current animation position 0.25341608746384736
current animation position 0.25040891469689086
current animation position 0.24617629182102974
current animation position 0.2410420152482402
current animation position 0.23537338342976
current animation position 0.2295457124710083
current animation position 0.2239141315640154
current animation position 0.21879699627515947
current animation position 0.2144686846222612
current animation position 0.2111600160587841
current animation position 0.20906241873944964
current animation position 0.20833301544189453

Solution

  • the answer was simple:

    .animation(animationHandler.isStarted ? animation : .linear(duration: 0))