Chapters

Hide chapters

iOS Animations by Tutorials

Seventh Edition · iOS 15 · Swift 5.5 · Xcode 13

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

23. Intermediate Animations With UIViewPropertyAnimator
Written by Marin Todorov

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

You’ve already tried some animations with UIViewPropertyAnimator, and have started improving the Widgets project user experience by adding delightful animations to the interface. You’ve also looked into creating basic and keyframe animations and saw that using the UIViewPropertyAnimator class isn’t difficult at all! More importantly you’ve tackled some issues that aren’t as straightforward when using the UIView.animate(withDuration:...) set of APIs — for example, checking if an animation is currently running, conditionally adding animations and completions, and abstracting animations into standalone classes.

If you successfully completed the challenges from the previous chapter, just re-open the project and keep working on it. Otherwise, you can use the starter project provided for this chapter:

Let’s see how you can give your animations this extra something by using custom timings!

Custom Animation Timing

Throughout this book, you’ve been using the four built-in curves: linear, ease in, ease out, and ease in out. By now there isn’t much left to say about those that hasn’t been said already, so through most of this chapter you are going to focus on custom curves. If by any chance, you skipped over the earlier chapters where built-in curves are explained, have a quick detour to Chapter 3, “Getting Started With View Animations” and search for the Animation Easing section.

Assuming you have a solid grasp of what the four built-in curves are, let’s have a look at using one in an animation.

Built-in Timing Curves

Currently, when you activate the search bar you fade in a blur view on top of the widgets. In this example, you are going to remove that fade animation and animate the blur effect itself.

func blurAnimations(_ blurred: Bool) -> () -> Void {
  return {

  }
}
self.blurView.effect = blurred ? 
  UIBlurEffect(style: .dark) : nil
self.tableView.transform = blurred ? 
  CGAffineTransform(scaleX: 0.75, y: 0.75) : .identity
self.tableView.alpha = blurred ? 0.33 : 1.0
func blurAnimations(_ blurred: Bool) -> () -> Void {
  return {
    self.blurView.effect = 
      blurred ? UIBlurEffect(style: .dark) : nil
    self.tableView.transform = blurred ? 
      CGAffineTransform(scaleX: 0.75, y: 0.75) : .identity
    self.tableView.alpha = blurred ? 0.33 : 1.0
  }
}
blurView.effect = UIBlurEffect(style: .dark)
blurView.alpha = 0
func toggleBlur(_ blurred: Bool) {
  UIViewPropertyAnimator(
    duration: 0.55, 
    curve: .easeOut, 
    animations: blurAnimations(blurred))
    .startAnimation()
}

Custom Bézier Curves

Sometimes when you would like to be very specific about the timing of your animations, using these curves to simply “slow down at start” or “slow down about the end” isn’t enough.

func toggleBlur(_ blurred: Bool) {
  UIViewPropertyAnimator(duration: 0.55,
    controlPoint1: CGPoint(x: 0.57, y: -0.4),
    controlPoint2: CGPoint(x: 0.96, y: 0.87),
    animations: blurAnimations(blurred))
    .startAnimation()
}

Spring Animations

There is another convenience initializer — UIViewPropertyAnimator(duration:dampingRatio:animations:) — for defining spring driven animations.

Custom Timing Providers

Meet the fourth and last initializer you’re going to cover here: UIViewPropertyAnimator(duration:timingParameters:).

Providing Damping and Velocity

Even if you’re using a custom timing provider, you can still chose to go the easy way and provide just the damping ratio and initial velocity as you do when using the convenience initializer. The code would look like this:

let spring = UISpringTimingParameters(
  dampingRatio: 0.5, 
  initialVelocity: CGVector(dx: 1.0, dy: 0.2))

let animator = UIViewPropertyAnimator(
  duration: 1.0, 
  timingParameters: spring)

Custom Springs

If you would like to be more specific about your spring, you can use a different initializer on UISpringTimingParameters that lets you specify the spring’s mass, stiffness, and damping, much like you did for your layer animations earlier in the book.

let spring = UISpringTimingParameters(
  mass: 10.0, 
  stiffness: 5.0, 
  damping: 30, 
  initialVelocity: CGVector(dx: 1.0, dy: 0.2))

let animator = UIViewPropertyAnimator(
  duration: 1.0, 
  timingParameters: spring)

Auto Layout Animations

Phew! That was a rather lengthy theoretical part of the chapter, so I’m sure you’re excited to write some code and give few animations a try.

@discardableResult
static func animateConstraint(
  view: UIView, 
  constraint: NSLayoutConstraint, 
  by amount: CGFloat
) -> UIViewPropertyAnimator {

}
let spring = UISpringTimingParameters(dampingRatio: 0.2)
let animator = UIViewPropertyAnimator(
  duration: 2.0, 
  timingParameters: spring)

animator.addAnimations {
  constraint.constant += amount
  view.layoutIfNeeded()
} 
return animator
dateTopConstraint.constant -= 100
view.layoutIfNeeded()

AnimatorFactory.animateConstraint(
  view: view, 
  constraint: dateTopConstraint, 
  by: 100)
  .startAnimation()

self.showsMore.toggle()
let animations = {
  self.widgetHeight.constant = self.showsMore ? 230 : 130
  if let tableView = self.tableView {
    tableView.beginUpdates()
    tableView.endUpdates()
    tableView.layoutIfNeeded()
  }
}
let spring = UISpringTimingParameters(
  mass: 30, 
  stiffness: 1000, 
  damping: 300, 
  initialVelocity: CGVector(dx: 5, dy: 0))

toggleHeightAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: spring)
toggleHeightAnimator?.addAnimations(animations)
toggleHeightAnimator?.startAnimation()

widgetView.expanded = showsMore
widgetView.reload()

Built-in View Transitions

To finish up this animation, you’ll have a look at using the built-in view transitions with UIViewPropertyAnimator. In Chapter 5, “Transitions”, you learned about the built-in view transitions you can use in iOS. Now you are going to use a cross-fade to change the title of the widget button from Show More to Show Less and vice versa.

let textTransition = {
  UIView.transition(
    with: sender, 
    duration: 0.25, 
    options: .transitionCrossDissolve,
    animations: {
      sender.setTitle(
        self.showsMore ? "Show Less" : "Show More", 
        for: .normal)
    },
    completion: nil)
}
toggleHeightAnimator?.addAnimations(animations)
toggleHeightAnimator?.addAnimations(textTransition, delayFactor: 0.5)
toggleHeightAnimator?.startAnimation()

Key Points

Challenges

Challenge 1: Additive Animations

When using UIView.animate(withDuration:...), adding animations to the same view property happens additively.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now