Quick Guide To Property Animators

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

Basic color animation

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

Multiple animations

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

Reversing animation

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()

Spring animation

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()

Bezier curve animation

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()

Key frame animation

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: