Chapters

Hide chapters

iOS Animations by Tutorials

Sixth Edition · iOS 13 · Swift 5.1 · Xcode 11

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 reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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, t
  imingParameters: 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: CGFloat) -> UIViewPropertyAnimator {

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

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

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

self.showsMore = !self.showsMore
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.

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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now