2017-09-14 28 views
2

トグルでボタンを使用したいのですが、一度クリックすると&イメージが無期限に回転します。もう一度クリックすると画像が停止し、もう一度クリックすると再起動します。無限に回転する画像を停止する適切な方法はありますか?どのようにremoveAllAnimationsを実装していますか?

私は、アニメーションの継続を得ることにこの回答が役に立ったと評価: Rotate a view for 360 degrees indefinitely in Swift?

しかし、私は物事を停止する方法については不明です。 &以下のコードを実装しましたが、これはうまくいくようですが、これがアニメーションを停止する適切な方法であるのか、別の方法があるのか​​不思議です。また、私のローテーションは終了するまで続きますが、ボタンを押したときの位置で回転をフリーズすることができますか(私は.removeAllAnimations()を2回目の試みで試しましたが、すべて。

@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'フラグの状態をチェックしているのでしょうか?私が使っている実装はCore Animationを直接使用しているので、層に供給されたアニメーションキーを使ってアニメーションをコントロールすることができます – 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のダイナミクスを使用することができます。ローテーションを実行していた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ブロックベースのアニメーションを使用する場合は、あなたがアニメーションを再起動する場所を知っているので、あなたがアニメーションを停止する角度をキャプチャする必要があります。したがって

    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を使用してオブジェクトを回転させることができますトリックはグラブm12CATransform3Dm11です。その後、回転を停止することは、表示リンクを無効にすることと同じくらい簡単であり、それによって、停止したときの位置に戻すことができます。ランディングに表示リンクを追加するだけで、アニメーションを再開できます。

    この種の技法は、あなたに多大な制御を与えますが、アプローチの中で最もエレガントではありません。

+0

非常に助かりました - ありがとう。私はrelativeStartTime(0,1/3,2/3)、およびrelativeDuration(時間の1/3、次にフル(1.0)1/3の増分)を理解していると思います、フル(1.0)秒の1/3の増分)。 UIView.animateKeyframes(withDuration:4 ,. animateKeyFramesメソッドはwithDurationとdelayを必要とするようですが、このwithDurationsがrunningPropertyAnimatorでどのようにやりとりするのかよくわかりません。UIView.animateKeyframes(withDuration:これはまだ動作しているようですが、これはうまくいくと思います。 – Gallaugher

+0

'addKeyframe'の持続時間は、キーフレームアニメーション全体の持続時間のパーセントで表されます。' animateKeyFrames'の継続時間は、他のアニメーション。一番外側のアニメーションは、期間を指定します。 – Rob

0

この第1のアプローチは妥当と思われる。停止位置を細かく制御するには、アニメーションの継続時間と回転ラジアンの両方を一定量で除算します。完了ハンドラーは、ローテーションを継続するかどうかを確認するためにより頻繁に呼び出されます。

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

株式オプションが表示されているとき、私は私は1つの特定のラベル(または透かし)を「オフ」する必要がありますラベル(それは透かしだ)への無限の「フリップ」を持っています。私はこれをタイマーで行っています。特定のラベルを数秒間見たままにしておきます。これを行うと、タイマーをオフにするのは簡単なことです。

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