2017-09-14 26 views
2

我想使用一個按鈕作爲切換 - 單擊一次&圖像無限旋轉。再次單擊,圖像停止,再次單擊,重新啓動。正確的方法來停止無限旋轉的圖像?以及如何實現removeAllAnimations?

我發現這個答案在獲得動畫繼續幫助: Rotate a view for 360 degrees indefinitely in Swift?

不過,我如何停止的事情不清楚。我已經實現了&以下的代碼,但它似乎有效,但我很好奇這是否是停止動畫的正確方法,或者是否有其他首選方法。此外 - 我的旋轉一直持續到完成,但我想知道是否可以在按下按鈕時凍結位置處的旋轉(我在下面的第二次嘗試中試過.removeAllAnimations(),但這似乎不起作用所有。

@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) 
        } 
    } 

這確實工作。我也想知道,如果它會是可能停止動畫中旬 - 旋轉。它的成立方式,動畫那張滿180度停止之前,我已經也嘗試在spinPressed操作中添加一個removeAnimation,並且在rotateView中刪除stopRotation檢查,但這似乎不起作用 - 旋轉繼續&只是在再次按下spinPressed時變得更快(參見下文):

    @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) 
        } 
    } 

確認第一種方法是否合理,這是值得歡迎的。如果有一種方法可以停止旋轉中間旋轉,那也是受歡迎的(也可以讓我直接思考我對removeAllAnimations的錯誤思考)。

謝謝! JG

+0

也許檢查'finished'塊中'stopRotation'標誌的狀態?我使用的實現直接使用核心動畫,因此您可以通過提供給圖層的動畫關鍵字來控制動畫 – MadProgrammer

回答

1

有幾個方法可以做到你問什麼:

  1. 如果支持的iOS 10+,您可以使用UIViewPropertyAnimator,它的動畫,你可以暫停和重啓(從恢復它被暫停):

    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 and start 360 degree animation 
    /// 
    /// This will fire off another animation when one 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. 您也可以使用UIKit Dynamics旋轉項目。然後,您可以刪除正在執行旋轉的UIDynamicItemBehavior,它只是停在原來的位置。它會自動將視圖transform保留在原來的位置。然後,繼續旋轉,只需添加一個UIDynamicItemBehavior的旋轉再次:

    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!) 
        } 
    } 
    

    這並不讓你輕鬆控制在時間上的旋轉速度,而是它是由angularVelocity決定的,但它是一個很好的簡單方法(並支持iOS 7.0及更高版本)。

  3. 停止動畫並將其留在停止位置的舊式方法是捕獲動畫的presentationLayer(顯示它在中間位置)。然後,您可以抓取當前狀態,停止動畫,並將變換設置爲presentationLayer報告的內容。

    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. 如果你想使用基於UIView塊動畫,你必須捕捉您停止動畫的角度,所以你從哪裏重新啓動動畫知道。訣竅是搶m12CATransform3Dm11

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

    因此,這產生了:

    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. 可以旋轉自己使用CADisplayLink,更新的角度,一些計算值的對象。然後停止旋轉就像使顯示鏈接無效一樣簡單,從而使其停在原來的位置。您可以通過簡單地將顯示鏈接添加回您的runloop來恢復動畫。

    這種技術給你一個很大的控制,但是是最不優雅的方法。

+0

令人難以置信的幫助 - 謝謝。回覆#1 - in func createAnimation()我想我正在理解relativeStartTime(0,1/3,2/3)和relativeDuration(1/3的時間,然後是完整的(1.0)1/3增量,全部(1.0)秒1/3增量)。不確定的持續時間在:UIView.animateKeyframes(withDuration:4,。似乎animateKeyFrames方法需要withDuration和延遲,但我不知道這與withDurations與runningPropertyAnimator中的一個交互如何設置UIView.animateKeyframes(withDuration:到0&這仍然似乎工作。重複最好的B/C其可讀性? – Gallaugher

+0

是的。'addKeyframe'持續時間表示爲整個關鍵幀動畫持續時間的百分比。'animateKeyFrames'持續時間被覆蓋,每當你包裹裏面其他動畫,最外面的動畫決定了持續時間。 – Rob

0

這第一種方法似乎是合理的。如果您想要對停止位置進行細粒度控制,請將動畫持續時間和旋轉弧度均分爲一個常量。你的完成hander會被更頻繁地調用來檢查是否應該繼續輪換。

let animationFrequency = 30.0 
func rotateView(targetView: UIView, duration: Double = 1.0/animationFrequency) { 
     UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: { 
      targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi/animationFrequency)) 
     }) { finished in 
      self.rotateView(targetView: targetView, duration: duration) 
     } 
    } 
0

我有到標籤(這是一個水印),我需要被顯示的股票期權時,「關閉」,以一個特定的標籤(或水印)無限「翻轉」。我通過一個計時器來完成所有這些工作 - 在翻轉之前保持特定的標籤瀏覽數秒。當你這樣做時,關閉計時器是一件簡單的事情。

public class FlipView:UIView { 

    private var labelWatermark:FlipLabel! 
    private var labelTap:FlipLabel! 

    let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromRight, .showHideTransitionViews] 
    var isLabel1 = true 
    var timer:Timer! 

    convenience public init(appName:String) { 

     self.init(frame: CGRect.zero) 

     labelTap = FlipLabel("Tap to remove", "Watermark") 
     self.addSubview(labelTap) 

     labelWatermark = FlipLabel("created with", appName) 
     self.addSubview(labelWatermark) 

     timer = Timer.scheduledTimer(
      timeInterval: 3.0, target: self, selector: #selector(flipViews), 
      userInfo: nil, repeats: true) 
     timer.fire() 

    } 

    internal func startFlip() { 
     timer = Timer.scheduledTimer(
      timeInterval: 3.0, target: self, selector: #selector(flipViews), 
      userInfo: nil, repeats: true) 
     timer.fire() 
    } 

    internal func stopFlip() { 
     timer.invalidate() 
     UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil) 
     isLabel1 = true 
    } 

    @objc func flipViews() { 
     if (isLabel1) { 
      UIView.transition(from: labelWatermark, to: labelTap, duration: 1, options: transitionOptions, completion: nil) 
      isLabel1 = false 
     } else { 
      UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil) 
      isLabel1 = true 
     } 
    } 
}