There have been at least three ways to animate views in iOS. In the early days we had the, now deprecated, begin/commit style. The block based API has been around since iOS 4 but is now discouraged. Since iOS 10 we have property animators which saw some updates in iOS 11. If you are familiar with the block-based API but have been avoiding the more complex property animators here is my quick guide comparing the two.
Last updated: Jan 16, 2020
Basic Animation
The simplest case, animating a view property. The only parameter is the duration, default options, no completion block. First the block-based version.
UIView.animate(withDuration: duration) {
self.redSquare.backgroundColor = .green
}
There are a number of ways to create a property animator. The closest to the block based API is to create a running animator:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: [], animations: {
self.redSquare.backgroundColor = .green
})
UIKit Timing Curve
If you want something other than the default UIKit timing curve include the UIViewAnimationOptions
parameter:
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear], animations: {
self.redSquare.backgroundColor = .green
})
The property animator API version also takes a UIViewAnimationOptions
but it ignores common options like autoreverse
and repeat
:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: [.curveLinear], animations: {
self.redSquare.backgroundColor = .green
})
The alternate property animator initializer gives you back an animator object that you need to start:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation()
Note also that the curve parameter is a UIViewAnimationCurve
that only supports the four common UIKit curves (.easeInOut
, .easeIn
, .easeOut
and .linear
).
Delaying The Start
The block based API has a delay parameter (in seconds):
UIView.animate(withDuration: duration, delay: 3.0, options: [.curveLinear], animations: {
self.redSquare.backgroundColor = .green
})
You can specify the delay directly using the runningPropertyAnimator
initializer or when working with an animator object start it with a delay:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation(afterDelay: 3.0)
Completion Handlers
The block-based API has a completion handler that has a boolean argument indicating if the animation completed:
UIView.animate(withDuration: duration, delay: 0, options: [], animations: {
self.redSquare.backgroundColor = .green
}, completion: { finished in
print("animation finished: \(finished)")
})
You can add as many completion handlers as you want to a property animator. The completion block takes a UIViewAnimationPosition
enum for the final position of the animation (.start
, .end
, .current
):
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.addCompletion { position in
if position == .end {
print("First completion")
}
}
animator.addCompletion { position in
if position == .end {
print("Second completion")
}
}
animator.startAnimation()
Adding Animations
One advantage of using property animators is that you can add animations to the animator object:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.addAnimations {
self.redSquare.layer.cornerRadius = 50.0
}
animator.startAnimation()
Adding an animation to a running animator starts it immediately (you can also add with a delay):
animator.addAnimations({
self.redSquare.alpha = 0.0
})
Reversing Animations
The block based API is convenient when you want to reverse an animation:
UIView.animate(withDuration: duration, delay: delay, options: [.autoreverse], animations: {
self.redSquare.backgroundColor = .green
}, completion: { _ in
self.redSquare.backgroundColor = .red
})
The autoreverse
option does not work with property animators. The easiest way is to start a second animation in the completion handler of the first animation:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: [.curveEaseInOut], animations: {
self.redSquare.backgroundColor = .green
}, completion: { _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: [.curveEaseInOut], animations: {
self.redSquare.backgroundColor = .red
})
})
Spring Animations
Animating the center of a view (using Auto Layout constraints). The lower the damping the more “springy” the animation becomes:
view.layoutIfNeeded()
centerConstraint.constant = 0.0
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: {
self.view.layoutIfNeeded()
})
Property animators support spring animations in iOS 11:
view.layoutIfNeeded()
centerConstraint.constant = 0.0
let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.2, animations: {
self.view.layoutIfNeeded()
})
animator.startAnimation()
Timing Curves
You are not limited to the built-in UIKit or spring timing curves with property animators. You can specify a custom bezier path. In this case the animation starts slow and only speeds up at the end:
let point1 = CGPoint(x: 1.0, y: 0.1)
let point2 = CGPoint(x: 1.0, y: 1.0)
view.layoutIfNeeded()
centerConstraint.constant = 0.0
let animator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) {
self.view.layoutIfNeeded()
}
animator.startAnimation()
An alternate initializer lets you specify the timing parameters:
let cubicTiming = UICubicTimingParameters(controlPoint1: point1, controlPoint2: point2)
let customAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: cubicTiming)
The spring timing curve is fully customizable:
let springTiming = UISpringTimingParameters(mass: 1, stiffness: 2, damping: 0.2, initialVelocity: 0)
let customAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: springTiming)
Key Frame Animations
Key frame animation allow you to setup complex animations controlling the start and duration of each animations. The animations do not have to be sequential. An example using the block-based API where we animate the center of the view. During the animation we start new animations to change the corner radius and change the background color:
view.layoutIfNeeded()
UIView.animateKeyframes(withDuration: duration, delay: animationDelay, animations: {
self.centerConstraint.constant = 0.0
self.view.layoutIfNeeded()
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
self.redSquare.layer.cornerRadius = 50.0
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.view.backgroundColor = .green
}
})
The relative start time and duration values are in the range 0-1 (not seconds). So the corner radius change starts 75% of the way through the animation and lasts 25% of the duration. The background change starts at 50% and lasts 50% of the overall duration.
The property animator version is almost the same. Add the key frames as animations to the property animator:
view.layoutIfNeeded()
let animation = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut)
animation.addAnimations {
UIView.animateKeyframes(withDuration: duration, delay: animationDelay, animations: {
self.centerConstraint.constant = 0.0
self.view.layoutIfNeeded()
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
self.redSquare.layer.cornerRadius = 50.0
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.view.backgroundColor = .green
}
})
}
animation.startAnimation()
Should You Stop Using Block Based Animations?
It is usually a good idea to pay attention to hints Apple gives about its frameworks. The Apple UIView
class documentation tells you that the block based methods are discouraged and to use UIViewPropertyAnimator
.
I doubt Apple will deprecate the block based API any time soon - but who knows? The ancient beginAnimations
and commitAnimations
were discouraged since iOS 4 and only deprecated in iOS 13.
Learn the property animator API and start using it but I don’t see a need to rush to migrate block-based animation code.
Playground
You can find a playground with the code snippets from this post in my GitHub repository:
Further Reading
Property animators can do a lot more than I have covered in this quick guide. For example, interactive animations that you can scrub back and forth with a gesture. See these past WWDC videos for details: