swift - Proper way to stop an infinitely rotating image? and how does one implement removeAllAnimations? -


i'd use button toggle – click once & image rotates indefinitely. click again, image stops, click again, restarts.

i found answer helpful in getting animation continue: rotate view 360 degrees indefinitely in swift?

however, i'm unclear on how stop things. i've implemented code below & seems work, curious if proper way stop animation, or if there another, preferred method. - rotation continues until finishing, i'm wondering if can freeze rotation @ location when button pressed (i've tried .removeallanimations() in second attempt below, doesn't seem work @ all.

    @iboutlet weak var imageview: uiimageview!     var stoprotation = true      func rotateview(targetview: uiview, duration: double = 1.0) {         if !stoprotation {             uiview.animate(withduration: duration, delay: 0.0, options: .curvelinear, animations: {                 targetview.transform = targetview.transform.rotated(by: cgfloat(double.pi))             }) { finished in                 self.rotateview(targetview: targetview, duration: duration)             }         }     }          @ibaction func spinpressed(_ sender: uibutton) {         stoprotation = !stoprotation         if !stoprotation {             rotateview(targetview: imageview)         }     } 

this work. wondering if it'd possible stop animation mid-spin. way it's set up, animation goes full 180 degrees before stopping. i've tried adding removeanimation in spinpressed action, , getting rid of stoprotation check inside rotateview, doesn't seem work – rotation continues & gets faster if spinpressed pressed again (see below):

    @iboutlet weak var imageview: uiimageview!     var stoprotation = true          func rotateview(targetview: uiview, duration: double = 1.0) {         uiview.animate(withduration: duration, delay: 0.0, options: .curvelinear, animations: {             targetview.transform = targetview.transform.rotated(by: cgfloat(double.pi))         }) { finished in             self.rotateview(targetview: targetview, duration: duration)         }     }          @ibaction func spinpressed(_ sender: uibutton) {         stoprotation = !stoprotation         if stoprotation {             imageview.layer.removeallanimations()         } else {             rotateview(targetview: imageview)         }     } 

a confirm if first approach sound welcome. , if there way stop rotation mid-spin, that'd welcome (as setting me straight on flawed thinking on removeallanimations).

thanks! jg

there couple of ways you're asking about:

  1. if supporting ios 10+, can use uiviewpropertyanimator, animations can pause , restart (resuming paused):

    private var animator: uiviewpropertyanimator?  @ibaction func didtapbutton(_ sender: any) {     guard let animator = animator else {         createanimation()         return     }      if animator.isrunning {         animator.pauseanimation()     } else {         animator.startanimation()     } }  /// create , start 360 degree animation /// /// fire off animation when 1 360° rotation finishes.  private func createanimation() {     animator = uiviewpropertyanimator.runningpropertyanimator(withduration: 4, delay: 0, options: .curvelinear, animations: {         uiview.animatekeyframes(withduration: 4, delay: 0, animations: {             uiview.addkeyframe(withrelativestarttime: 0, relativeduration: 1.0 / 3.0) {                 self.animatedview.transform = .init(rotationangle: .pi * 2 * 1 / 3)             }             uiview.addkeyframe(withrelativestarttime: 1.0 / 3.0, relativeduration: 1.0 / 3.0) {                 self.animatedview.transform = .init(rotationangle: .pi * 2 * 2 / 3)             }             uiview.addkeyframe(withrelativestarttime: 2.0 / 3.0, relativeduration: 1.0 / 3.0) {                 self.animatedview.transform = .identity             }         })     }, completion: { [weak self] _ in         self?.createanimation()     }) } 
  2. you can alternatively use uikit dynamics rotate item. can remove uidynamicitembehavior performing rotation , stops was. automatically leaves view transform was. then, resume rotation, add uidynamicitembehavior rotation again:

    private lazy var animator: uidynamicanimator = uidynamicanimator(referenceview: view) private var rotate: uidynamicitembehavior?  @ibaction func didtapbutton(_ sender: any) {     if let rotate = rotate {         animator.removebehavior(rotate)         self.rotate = nil     } else {         rotate = uidynamicitembehavior(items: [animatedview])         rotate?.allowsrotation = true         rotate?.angularresistance = 0         rotate?.addangularvelocity(1, for: animatedview)         animator.addbehavior(rotate!)     } } 

    this doesn't let control speed of rotation in terms of time, rather it’s dictated angularvelocity, it's nice simple approach (and supports ios 7.0 , later).

  3. the old-school approach stopping animation , leaving stopped capture presentationlayer of animation (which shows mid-flight). can grab current state, stop animation, , set transform presentationlayer reported.

    private var isanimating = false  @ibaction func didtapbutton(_ sender: any) {     if isanimating {         let transform = animatedview.layer.presentation()!.transform         animatedview.layer.removeallanimations()         animatedview.layer.transform = transform     } else {         let rotate = cabasicanimation(keypath: "transform.rotation")         rotate.byvalue = 2 * cgfloat.pi         rotate.duration = 4         rotate.repeatcount = .greatestfinitemagnitude         animatedview.layer.add(rotate, forkey: nil)     }      isanimating = !isanimating } 
  4. if want use uiview block based animation, have capture angle @ stopped animation, know restart animation. trick grab m12 , m11 of catransform3d:

    angle = atan2(transform.m12, transform.m11) 

    thus, yields:

    private var angle: cgfloat = 0 private var isanimating = false  @ibaction func didtapbutton(_ sender: any) {     if isanimating {         let transform = animatedview.layer.presentation()!.transform         angle = atan2(transform.m12, transform.m11)         animatedview.layer.removeallanimations()         animatedview.layer.transform = transform     } else {         uiview.animate(withduration: 4, delay: 0, options: .curvelinear, animations: {             uiview.animatekeyframes(withduration: 4, delay: 0, options: .repeat, animations: {                 uiview.addkeyframe(withrelativestarttime: 0, relativeduration: 1.0 / 3.0) {                     self.animatedview.transform = .init(rotationangle: self.angle + .pi * 2 * 1 / 3)                 }                 uiview.addkeyframe(withrelativestarttime: 1.0 / 3.0, relativeduration: 1.0 / 3.0) {                     self.animatedview.transform = .init(rotationangle: self.angle + .pi * 2 * 2 / 3)                 }                 uiview.addkeyframe(withrelativestarttime: 2.0 / 3.0, relativeduration: 1.0 / 3.0) {                     self.animatedview.transform = .init(rotationangle: self.angle)                 }             })         })     }      isanimating = !isanimating } 
  5. you can rotate object using cadisplaylink updates angle calculated value. stopping rotation simple invalidating display link, thereby leaving when stopped. can resume animation adding display link runloop.

    this sort of technique gives great deal of control, least elegant of approaches.


Comments

Popular posts from this blog

ZeroMQ on Windows, with Qt Creator -

unity3d - Unity SceneManager.LoadScene quits application -

python - Error while using APScheduler: 'NoneType' object has no attribute 'now' -