0

I have an imageView and want it to rotate 360° all the time, but I found an issue which is that when the App enters background and then back to the foreground, the rotate animation will be stopped. And I don't want to re-call the function rotate360Degree() when the app back to the foreground, the reason is that I want the rotate-animation will start at the position where it left when entering background, instead of rotating from 0 again. But when I call the function resumeRotate(), it doesn't work.

The extension as follow:

extension UIImageView {
    // 360度旋转图片
    func rotate360Degree() {
        let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") // 让其在z轴旋转
        rotationAnimation.toValue = NSNumber(value: .pi * 2.0) // 旋转角度
        rotationAnimation.duration = 20 // 旋转周期
        rotationAnimation.isCumulative = true // 旋转累加角度
        rotationAnimation.repeatCount = MAXFLOAT // 旋转次数
        rotationAnimation.autoreverses = false
        layer.add(rotationAnimation, forKey: "rotationAnimation")
    }

    // 暂停旋转
    func pauseRotate() {

        layer.pauseAnimation()
    }

    // 恢复旋转
    func resumeRotate() {

        layer.resumeAnimation()


    }

}

Here is the layer Extension :

var pauseTime:CFTimeInterval!

extension CALayer {
    //暂停动画
    func pauseAnimation() {
        pauseTime = convertTime(CACurrentMediaTime(), from: nil)
        speed = 0.0
        timeOffset = pauseTime
    }
    //恢复动画
    func resumeAnimation() {
        // 1.取出时间
         pauseTime = timeOffset
        // 2.设置动画的属性
        speed = 1.0
        timeOffset = 0.0
        beginTime = 0.0
        // 3.设置开始动画
        let startTime = convertTime(CACurrentMediaTime(), from: nil) - pauseTime
        beginTime = startTime
    }
}

I can solve the above 'stopped' issue with CADisplayLink, but the animation will not rotate from the position where it left(rotate all the time). I wonder how to solve it with CADisplayLink? And how with the above core animation?

displayLink = CADisplayLink(target: self, selector: #selector(rotateImage))
displayLink.add(to: .current, forMode: .commonModes)

 func rotateImage(){

         let angle =  CGFloat(displayLink.duration * Double.pi / 18)

         artworkImageView.transform = artworkImageView.transform.rotated(by: angle)

    }
Ringo
  • 1,173
  • 1
  • 12
  • 25

1 Answers1

-1

You can do this with UIKit's higher-level block based animation api. If you want a continuously rotating view with 20.0 second duration. You can with a function like:

    func animateImageView()
    {
        UIView.animate(withDuration: 10.0, delay: 0.0, options: [.beginFromCurrentState, .repeat, .curveLinear], animations:
        { [unowned self] in
            var transform = self.imageView.transform
            transform = transform.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi))
            self.imageView.transform = transform
        },
        completion:
        { _ in
            UIView.animate(withDuration: 10.0, delay: 0.0, options: [.beginFromCurrentState, .curveLinear], animations:
            { [unowned self] in
                var transform = self.imageView.transform
                transform = transform.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi / 2.0))
                self.imageView.transform = transform
            })
        })
    }

This will just rotate the view by 180 degrees then once that is complete rotate it another 180 for 360 degree rotation. The .repeat option will cause it to repeat indefinitely. However, the animation will stop when the app is backgrounded. For that, we need to save the state of the presentation layer of the view being rotated. We can do that by storing the CGAffineTransform of the presentation layer and then setting that transform to the view being animated when the app comes back into the foreground. Here's an example with a UIImageView

class ViewController: UIViewController
{

    @IBOutlet weak var imageView: UIImageView!
    var imageViewTransform = CGAffineTransform.identity

    override func viewDidLoad()
    {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(self.didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
    }

    override func viewDidAppear(_ animated: Bool)
    {
        super.viewDidAppear(animated)
        animateImageView()
    }

    deinit
    {
        NotificationCenter.default.removeObserver(self)
    }

    func didEnterBackground()
    {
        imageViewTransform = imageView.layer.presentation()?.affineTransform() ?? .identity
    }

    func willEnterForeground()
    {
        imageView.transform = imageViewTransform
        animateImageView()
    }

    func animateImageView()
    {
        UIView.animate(withDuration: 10.0, delay: 0.0, options: [.beginFromCurrentState, .repeat, .curveLinear], animations:
        { [unowned self] in
            var transform = self.imageView.transform
            transform = transform.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi))
            self.imageView.transform = transform
        },
        completion:
        { _ in
            UIView.animate(withDuration: 10.0, delay: 0.0, options: [.beginFromCurrentState, .curveLinear], animations:
            { [unowned self] in
                var transform = self.imageView.transform
                transform = transform.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi / 2.0))
                self.imageView.transform = transform
            })
        })
    }
}

Which results in this:

enter image description here

beyowulf
  • 15,101
  • 2
  • 34
  • 40
  • @Hi beyowulf. Thanks for your detail replying.I will have a try with your solution.But the question is that the duration is not fixed for the application in the background or the foreground.It just relates to user's behavior.For example, the app is a music player, the imageView is for the artwork of the music, and user maybe listen to the music with the background mode for 30 minutes,2 hrs or more. – Ringo Aug 16 '17 at 23:59
  • I don’t understand you comment. The duration that the app is in the background can be whatever it doesn’t matter provided the app stays in memory. I thought the question was how can one animate a view so that when the app is backgrounded then resumed the animation continues as though it were never backgrounded. Is that not the case? – beyowulf Aug 17 '17 at 00:09
  • Oh, sorry, I misunderstood the digit 10 in your codes. It is the animation duration instead of the time for the app in the background. – Ringo Aug 17 '17 at 00:21