55

UIView Animations Tutorial: Practical Recipes

 5 years ago
source link: https://www.tuicool.com/articles/hit/eAraau2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Update note : Nick Bonatsakis updated this tutorial for iOS 12, Xcode 10 and Swift 4.2. Fabio Budai wrote the original.

If you’ve ever paid close attention to how a great iOS app feels as you use it, you’ll quickly notice that subtle and fluid UIView animations are key to making the experience feel interactive.

While animation is usually not critical to the core functionality of your app, it can help guide and inform your users, gracefully reveal and dismiss parts of your UI and generally add a level of polish that can make your app stand out from the competition.

UIKit has some really powerful built-in UIView animations, and the latest Swift-based interfaces make using them really easy.

The tutorial team has already covered how to use UIView Animations in this UIView Animation Tutorial , so you might want to go through that tutorial first if you’re unfamiliar with the concepts.

This tutorial takes things a step further, walking you through a number of cool animation recipes that you can easily apply to your own apps.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project. Open it in the latest version of Xcode. Build and run, and you should see a basic app that looks like this:

image01-281x500.png

You can tap around on the various buttons, but you’ll find that at this point, it doesn’t do much of anything.

Before you start beautifying this app with sweet, sweet UIView animations, here’s a brief tour of what the starter project already includes:

  • ViewController.swift : Contains the main view controller that renders the UI you’ll animate in later steps. It includes empty @IBAction methods for each animation that you will add, as well as @IBOutlet properties for various UI elements that you’ll animate.
  • AnimationCurvePickerViewController.swift : Contains a view controller that shows a list of possible animation curves for the user to select from in a UITableView , as well as a delegate protocol to notify callers of the selection when the user taps a row.
  • FakeHUD.swift : Contains the UIView implementation for the XIB of the same name. It renders a HUD-style view with a progress indicator and label.

Now that you have the basic lay of the land, it’s time to dive into implementing your first animation. You’re going to look first at a couple of different animatable properties . These are pretty self explanatory — they are properties of a UIView which are able to be animated.

Frames

Create a new file by selecting File ▸ New ▸ File… and choose the iOS ▸ Swift File template. Name the file UIView+Animations.swift and click Create . You’re going to add an extension to UIView , which allows you to add some methods to an already existing class ( UIView in this case) without subclassing it: a very neat and powerful way to expand an object’s functionality. You will put all the animation code in this extension.

Replace the newly created file’s content with the following code:

import UIKit

extension UIView {

  func move(to destination: CGPoint, duration: TimeInterval, 
            options: UIView.AnimationOptions) {
    UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
      self.center = destination
    }, completion: nil)
  }

}

This adds a new method to ALL instances of UIView, letting you animate moving the view itself from its current position to a new position. The time the animation takes is given by the duration parameter. Its units is seconds and is of type TimeInterval . Since TimeInterval is a typealias for Double , you have very fine control over how long the animation lasts. The options parameter relates to the animation curve used, which you’ll get more into that later on in the tutorial.

How does this work? The code uses the UIView.animate(withDuration:delay:options:animations:completion:) API on one of the view’s animatable properties: its frame. By simply changing the frame in the animations block, you tell Core Animation to move it to the new coordinates over the course of the duration. Core Animation will also automatically calculate and draw all the intermediate “steps” to get a smooth animation.

It’s as easy as that! You can see this in action by adding code to the view controller that powers the main interface.

Open ViewController.swift and wire up the first animation. To make the arrow move to a specified location, add the following code to moveTo(button:) method:

// 1
let destination = view.convert(button.center, from: button.superview)

// 2
movingButton.move(to: destination.applying(
                    CGAffineTransform(translationX: 0.0, y: -70.0)),
                  duration: 1.0,
                  options: selectedCurve.animationOption)

Not a ton of code, but here’s a breakdown of what it does:

  1. In this view controller, the movingButton (the view you want to move) is a subview of the root view, while each Move Here button is contained within a nested UIStackView . So to correctly animate the movingButton to the Move Here button that the user has tapped, you must first convert the coordinates of the button the user tapped to the coordinate space of the root view.
  2. Next, you call move(to:duration:options) , which is in the UIView extension you added earlier. You call it on the movingButton (remember that UIButton is just a subclass of UIView , so inherits any UIView extension methods) to animate the button move. You pass a destination slightly above the tapped upon button, a duration of 1.0 seconds, and the currently selected animation curve. You will learn more about the curves later on.

Now build and run. Tap any of the “Move Here” buttons and watch in awe as the arrow smoothly moves to the button. Pretty cool for just a few lines of code, eh?

move-1.gif

This recipe is useful to highlight a user’s selection or to slide in a bigger view — you put just a small part of a view on screen and allow the user to slide the rest of the view into the screen by tapping a button.

Transforms

One the most powerful animatable properties on a view is its transform . The transform is of type CGAffineTransform and represents a matrix that expresses how each pixel of the UIView should be modified by applying some matrix math.

Don’t worry, you don’t actually have to remember linear algebra, because there are some built-in transforms to make life nice and simple. Here, you’ll focus on two of them:

  • Scale : Allows you to scale a view up or down, optionally using different factors for the x and y axes. A value of 10.0 means “make the view 10 times bigger than the actual frame.” Please note that changing a transform’s scale will invalidate the frame property.
  • Rotate : Allows you to rotate a view by a given angle. It uses radians, the standard unit of angular measurement. One radian is equal to 180/π degrees.

It’s important to understand how UIView animations handle transforms: You can combine multiple transforms of different kinds, but iOS will “see” only the final result. So if you make a 360° (2π radians) rotation, the net effect is no rotation at all, and you won’t see any animation on your view!

Keep in mind that:

  • Rotations up to 180° are animated clockwise.
  • Rotations from 180° to 360° are animated counter-clockwise (it takes the shortest path, so 270° is rendered as a -90° rotation).
  • All rotations over a complete circle (360°) are ignored, and a 2π modulo is applied.

This will be easy to see when you try it out below.

Open UIView+Animations.swift and add the following method to the extension:

func rotate180(duration: TimeInterval, options: UIView.AnimationOptions) {
  UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
    self.transform = self.transform.rotated(by: CGFloat.pi)
  }, completion: nil)
}

This simply animates the view to rotate 180° over the specified number of seconds.

Time to see it in action! Jump back into ViewController.swift and fill out the rotate() method with the following code:

button.rotate180(duration: 1.0, options: selectedCurve.animationOption)

The above method is bound to the “touch up inside” action of the Arrow button, so it’s invoked any time you tap the arrow.

Build & run the app. Every time you tap the arrow button, you should see it flipping up/down (rotating 180°) with a smooth rotation. And you can still move it around!

rotate-1.gif

This recipe may be useful for something like an on/off switch. You can even play with different angles to generate arrows for different directions using just one image.

A scale transformation is perfect for creating a zoom effect that can be great for picker views, where you want the picker to zoom in, choose something and then zoom out. And a picker is exactly what we need in the next section!

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
.curveEaseIn
.curveEaseOut
.curveLinear

.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:

pickerVC
self
addSubviewWithZoomInAnimation(:duration:options:)
didMove(toParent:)

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.

curves.gif

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.

hud.gif

Adding Color Change Animations

Your animation recipes app is now pretty functional, but the interface still looks a bit dry. For your final trick, you'll wire up the Change Color button to a method that animates the background color of the main interface view to a random color.

Start by opening UIView+Animations.swift one last time, and add the following method:

func changeColor(to color: UIColor, duration: TimeInterval, 
                 options: UIView.AnimationOptions) {
  UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
    self.backgroundColor = color
  }, completion: nil)
}

Through the magic of Core Animation, animating from one color to another is very easy, since the framework does all the heavy lifting of interpolating from the existing color to the new color. Because backgroundColor is an animatable property of UIView , this method simply wraps the setting of its value in an animation block and Core Animation does the rest.

Next, open ViewController.swift and add the following property at the top of the class:

let colors: [UIColor] = [.white, .red, .green, .orange, .yellow, .lightGray, .darkGray]

Now, add the following code to changeBackgroundColor() :

view.changeColor(to: colors.randomElement()!, duration: 1.0, 
                 options: selectedCurve.animationOption)

Here, you simply invoke changeColor(to:duration:options:) on the root view to animate to a new, randomly selected color.

Build and run one last time, then tap Change Color a few times to see the view animate through various background colors.

colors.gif

Where to Go From Here?

You can download a completed version of the sample project using the Download Materials button at the top or bottom of the tutorial.

UIKit provides quite a powerful way to animate views and playing with the properties can give you some really cool effects with only a few lines of code. These effects are perfect for tool and utility-type apps and adding them as extensions of UIView makes your code more readable and lets you focus on your app logic.

I hope you enjoy your experimentations with UIView animations and look forward to seeing you use them in your apps!

If you have any questions or comments about UIView animation, please join the forum discussion below!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK