UIView Animations Tutorial: Practical Recipes

In this UIView animations tutorial, you’ll learn to build practical recipes for your iOS apps using the UIKit framework. By Nick Bonatsakis.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Curves

If you pay close attention to the animation we just coded, you’ll notice that the arrow starts slowly, then speeds up, then slows down again until it stops (you can increase the duration to see the effect better if it’s not jumping out at you).

What you’re seeing is the animation’s default curve. Curves are acceleration curves that describe the speed variations of the animation. Curves are defined by the UIView.AnimationOptions type, and there are four available:

  • .curveEaseInOut: Start slow, speed up, then slow down.
  • .curveEaseIn: Start slow, speed up, then suddenly stop.
  • .curveEaseOut: Start fast, then slow down until stop.
  • .curveLinear: Constant speed.

.curveEaseInOut is the default because the effect seems very “natural.” But of course, “natural” depends on the effect you’re creating.

To play with different curves, you can simply use the corresponding enum value as an “option” parameter. But it’s more fun to use this as a chance to try out some more animations. So you’re going to now add a “curve picker” to the app.

The sample project already includes a UITableView-based picker that will let you choose from the various animation curves. You could simply present it using the default iOS presentation mechanism, but that wouldn’t be much fun, would it? Instead, you’ll use a zoom animation to liven things up.

Open UIView+Animations.swift once more, and add the following method:

func addSubviewWithZoomInAnimation(_ view: UIView, duration: TimeInterval, 
                                   options: UIView.AnimationOptions) {
  // 1
  view.transform = view.transform.scaledBy(x: 0.01, y: 0.01)

  // 2
  addSubview(view)

  UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
    // 3
    view.transform = CGAffineTransform.identity
  }, completion: nil)
}

The above method adds a new view to the view hierarchy with a zoom animation; here’s a breakdown of how:

  1. First, you set the view’s transform to a scale of 0.01 for x and y, causing the view to start in at a size 1 percent of its regular size.
  2. Next, you add the view as a subview of the view this method is called on.
  3. Finally, to trigger the animation, you set the view’s transform to CGAffineTransform.identity within the animations block of UIView.animate(withDuration:delay:options:animations:completion:), causing the view to animate back to its true size.

Now that you’ve added the code to zoom in when adding a subview, it makes sense to be able to do the reverse: Zoom out from a view and then, remove it.

Add the following method to UIView+Animations.swift:

func removeWithZoomOutAnimation(duration: TimeInterval, 
                                options: UIView.AnimationOptions) {
  UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
    // 1
    self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
  }, completion: { _ in
    // 2
    self.removeFromSuperview()
  })
}

The above code is fairly similar to the first method, but here’s what’s going on in detail:

  1. Since you want to remove self from its superview, you immediately perform the animation, setting the transform to 0.01 for x and y, animating the scaling down of the view.
  2. Once the animation has completed, you can safely remove self from the hierarchy.

With these new methods in hand, you can now present the curve picker using the new zoom animation. Open ViewController.swift and modify the zoomIn method by adding the following code:

// 1
let pickerVC = AnimationCurvePickerViewController(style: .grouped)
// 2
pickerVC.delegate = self
// 3
pickerVC.view.bounds = CGRect(x: 0, y: 0, width: 280, height: 300)
// 4
pickerVC.view.center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)

// 5
addChild(pickerVC)
// 6
view.addSubviewWithZoomInAnimation(pickerVC.view, duration: 1.0, 
                                   options: selectedCurve.animationOption)
// 7
pickerVC.didMove(toParent: self)

This code runs whenever you tap Change Timing Function. There’s a lot going on here, so here’s a step by step breakdown:

  1. First, you initialize the picker view controller and assign it to pickerVC.
  2. Set the delegate for the picker to self so you can be notified when the user selects a new curve.
  3. Explicitly set the frame for the picker’s view to a fairly small size, as you don’t want it to occupy the entire screen.
  4. Position the picker view to be centered within the root view.
  5. Start the presentation of the picker by adding it as a child view controller of the current view controller.
  6. Add the picker’s view by calling the addSubviewWithZoomInAnimation(:duration:options:) to animate the presentation.
  7. Finally, you call didMove(toParent:) on the picker to complete the presentation.

To finish up the picker presentation, modify the animationCurvePickerViewController(_:didSelectCurve:) delegate method by adding the following code:

// 1
selectedCurve = curve
// 2
controller.willMove(toParent: nil)
// 3
controller.view.removeWithZoomOutAnimation(duration: 1.0, 
                                           options: selectedCurve.animationOption)
// 4
controller.removeFromParent()

iOS calls the code above whenever you choose a curve from the presented curve picker. Here’s how it works:

  1. First, you set the selectedCurve property to the curve the user has just selected so it will be used in all future UIView animations.
  2. Now, kick off the view controller removal by calling willMove(toParent:) passing nil to indicate that controller (the picker view controller) is about to be removed from its parent view controller.
  3. Next, to trigger the animation, you invoke removeWithZoomOutAnimation(duration:options:) on the picker controller’s view.
  4. Finally, you call removeFromParent() on the picker controller to remove it from its parent view controller and complete the removal.

This recipe can be useful if you want to bring up a pop-up control in your app, and, of course, the animation curves themselves can be useful for all sorts of effects.

Build & run the app, and tap Change Timing Function. Play around with the different animation curves. Whenever you change the selected curve, it applies to any UIView animations you see in the app.

Complex Animations

Another property of UIView you can animate is its alpha. This makes it really easy to create fade effects, which is another nice way to add or remove subviews.

The starter project includes a HUD-like view that you’re going to present with an alpha fade animation, and then dismiss with a funny “draining the sink” custom effect!

Once again, you’re going to add a new extension method to UIView, so open UIView+Animations.swift and add the following method:

func addSubviewWithFadeAnimation(_ view: UIView, duration: TimeInterval, options: UIView.AnimationOptions) {
  // 1
  view.alpha = 0.0
  // 2
  addSubview(view)

  UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
    // 3
    view.alpha = 1.0
  }, completion: nil)
}

Here’s a breakdown of how this animation works:

  1. To achieve a “fade-in” effect, you first set the input view’s alpha to 0.0 so the view starts off invisible.
  2. You then add the view as a subview.
  3. Finally, since the alpha property of UIView is another animatable property, simply setting it 1.0 within the animation block will result in a smooth animation from hidden to visible. It will, therefore, fade in.

Now that the fade-in animation is all set, you’re going to begin implementing a new animation. It’s called the “draining the sink” animation and will look like the view is spinning down a drain! Add the following method again in UIView+Animations.swift:

func removeWithSinkAnimation(steps: Int) {
  // 1
  guard 1..<100 ~= steps else {
    fatalError("Steps must be between 0 and 100.")
  }
  // 2
  tag = steps
  // 3
  _ = Timer.scheduledTimer(
    timeInterval: 0.05, 
    target: self, 
    selector: #selector(removeWithSinkAnimationRotateTimer), 
    userInfo: nil, 
    repeats: true)
}

This animation is based on progressive transformations applied a fixed number of times — each 1/20th of a second. Let's break down how the first part works:

  1. This animation works best if the number of steps falls between zero and 100, so here you throw a fatal error if values outside of that range are provided.
  2. Next, you set the initial value of the tag to the initial number of steps. As the animation progresses through each step, you'll decrement this value.
  3. To trigger the animation sequence, you schedule a timer that calls removeWithSinkAnimationRotateTimer(timer:) every 0.05 seconds until you invalidate the timer.

Now, implement the second half of this animation by adding the following method:

@objc func removeWithSinkAnimationRotateTimer(timer: Timer) {
  // 1
  let newTransform = transform.scaledBy(x: 0.9, y: 0.9)
  // 2
  transform = newTransform.rotated(by: 0.314)
  // 3
  alpha *= 0.98
  // 4
  tag -= 1;
  // 5
  if tag <= 0 {
    timer.invalidate()
    removeFromSuperview()
  }
}

This code is the meat of the custom animation; here's how it works:

  1. First, you create a scale transform that will shrink the view to 90% of its current size. You use scaledBy(x:y:) on the current transform to make it additive on the existing transform.
  2. Next, apply a rotation of 18°.
  3. To slowly fade the view out, you also reduce the alpha to 98% of its current value.
  4. Now, you decrement the current step by subtracting 1 from the tag value.
  5. Finally, you check to see if the tag value is 0, indicating the animation sequence is complete. If it is, you invalidate the timer and remove the view to finish things up.

To see these new UIView animations in action, you're going to add them to the HUD presentation and dismissal triggered by the Show HUD button in the main interface.

Open ViewController.swift and add the following code to showHUD():

// 1
let nib = UINib.init(nibName: "FakeHUD", bundle: nil)
nib.instantiate(withOwner: self, options: nil)

if let hud = hud {
  // 2
  hud.center = view.center
  // 3
  view.addSubviewWithFadeAnimation(hud, duration: 1.0, 
                                   options: selectedCurve.animationOption)
}

The above code is fairly straight-forward; but here's a breakdown for clarity:

  1. First, you instantiate the hud property by loading the FakeHUD NIB.
  2. Next, you center the HUD in the current view.
  3. Finally, you add the HUD and fade it in by calling your newly created addSubviewWithFadeAnimation(_:duration:options:).

To wire up the HUD dismissal, add the following code to dismissHUD():

hud?.removeWithSinkAnimation(steps: 75)

In the above code (triggered by the Dismiss button on the HUD), you remove the HUD view using removeWithSinkAnimation(steps:).

Build & run the app once more and tap Show HUD, which will cause the HUD to fade in. Tap Dismiss to be treated to the sink drain dismiss animation.