7

I have a UIImageView which i want to rotate 180 degrees, taking up 1 second, then i want to wait 1 second at this position, then rotate 180 degrees back to the original position taking up 1 second.

How do i accomplish this? I've tried a 100 approaches and it keeps snapping back instead of rotating back

EDIT: I forgot to add i need this to repeat indefinitely

mikkelam
  • 517
  • 1
  • 7
  • 15

2 Answers2

14

All you need to do is to create a keyFrame animations. It is designed to chain multiple animations together, and in the first keyframe rotate your UIImageView subclass by PI, and in the second one transform it back to identity.

let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
        
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: { 
    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
        self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
    }
            
    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) { 
        self.imageView.transform = .identity 
    }
})

Outcome:

enter image description here

EDIT: Here is an example how to make it run indefinitely. I suppose your image is in a viewController, and you hold some reference to your imageView.

So for example, on viewDidAppear, call a function what triggers the animation, and than in the completion block of the animation, just call the same function again.

class ViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.runRotateAnimation()
    }
    
    func runRotateAnimation() {
        let rotateForwardAnimationDuration: TimeInterval = 1
        let rotateBackAnimationDuration: TimeInterval = 1
        let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
        
        UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: { 
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
                self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
            }
            
            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) { 
                self.imageView.transform = .identity 
            }
        }) { (isFinished) in
            // To loop it continuosly, just call the same function from the completion block of the keyframe animation
            self.runRotateAnimation()
        }
    }
}
emrcftci
  • 3,355
  • 3
  • 21
  • 35
dirtydanee
  • 6,081
  • 2
  • 27
  • 43
  • Thank you for your reply! This works 1 time, what if i need it to repeat? I've updated my question – mikkelam Sep 03 '17 at 09:20
  • THanks! It seems like this will pollute the stack quite a lot over time, is this of no concern? – mikkelam Sep 03 '17 at 10:40
  • what do you mean `pollute the stack`? Every recursive function will pollute the stack, no matter what you do. If you are concerned about performance, than take a look at `CABasicAnimation`, what is a more light weight anumation with supported `repeatCount` property. – dirtydanee Sep 03 '17 at 10:51
  • Yes, every recursive function will fill the stack, but this one continuous forever, usually one does not want that. If you have multiple of these running with short delays it can fill the stack up. I will try and convert this one to a loop instead. Thanks! – mikkelam Sep 03 '17 at 10:53
  • it will make no difference, running an animation like this forever will cause performance issues. Like i mentioned, you need a different approach. – dirtydanee Sep 03 '17 at 11:00
9

You can perform the second animation in the completionHandler presented on UIView.animate

let duration = self.transitionDuration(using: transitionContext)

let firstAnimDuration = 0.5
UIView.animate(withDuration: firstAnimDuration, animations: {
    /* Do here the first animation */
}) { (completed) in

   let secondAnimDuration = 0.5
   UIView.animate(withDuration: secondAnimDuration, animations: { 
       /* Do here the second animation */
   })
}

Now you could have another problem.

If you rotate your view with the CGAffineTransform and for every animation you assign a new object of this type to your view.transform, you will lose the previous transform operation

So, according to this post: How to apply multiple transforms in Swift, you need to concat the transform operation

Example with 2 animation block

This is an example to made a rotation of 180 and returning back to origin after 1 sec:

let view = UIView.init(frame: CGRect.init(origin: self.view.center, size: CGSize.init(width: 100, height: 100)))
view.backgroundColor = UIColor.red
self.view.addSubview(view)

var transform = view.transform
transform = transform.rotated(by: 180)

UIView.animate(withDuration: 2, animations: {
    view.transform = transform
}) { (completed) in

    transform = CGAffineTransform.identity
    UIView.animate(withDuration: 2, delay: 1, options: [], animations: { 
        view.transform = transform
    }, completion: nil)
}

Example of .repeat animation and .autoreverse

The .animate method give you the ability to set some animation options. In particular the structure UIViewAnimationOptions contains:

  1. .repeat, which repeat indefinitely your animation block
  2. .autoreverse, which restore your view to the original status

With this in mind you could do this:

var transform = view.transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [.repeat, .autoreverse], animations: {
     self.myView.transform = transform
})

But you need a delay between the two animations, so you need to do this trick:

Example of recursive animation and a delay of 1 sec

Just create a method inside your ViewController which animate your view. In the last completionHandler, just call the method to create a infinite loop.

Last you need to call the method on viewDidAppear to start the animation.

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

    self.animation()
}


func animation() {
    var transform = view.transform
    transform = transform.rotated(by: 180)

    UIView.animate(withDuration: 2, delay: 0, options: [], animations: {

        self.myView.transform = transform

    }) { bool in
        transform = CGAffineTransform.identity

        UIView.animate(withDuration: 2, delay: 1, options: [], animations: {

            self.myView.transform = transform

        }, completion: { bool in
            self.animation()
        })
    }
}
Giuseppe Sapienza
  • 4,101
  • 22
  • 23