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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIView Animations Tutorial: Practical Recipes
25 mins
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:
- First, you set the view’s transform to a scale of
0.01
forx
andy
, causing the view to start in at a size 1 percent of its regular size. - Next, you add the view as a subview of the view this method is called on.
- Finally, to trigger the animation, you set the view’s transform to
CGAffineTransform.identity
within theanimations
block ofUIView.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:
- Since you want to remove
self
from its superview, you immediately perform the animation, setting the transform to0.01
forx
andy
, animating the scaling down of the view. - 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:
- First, you initialize the picker view controller and assign it to
pickerVC
. - Set the delegate for the picker to
self
so you can be notified when the user selects a new curve. - 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.
- Position the picker view to be centered within the root view.
- Start the presentation of the picker by adding it as a child view controller of the current view controller.
- Add the picker’s view by calling the
addSubviewWithZoomInAnimation(:duration:options:)
to animate the presentation. - 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:
- First, you set the
selectedCurve
property to the curve the user has just selected so it will be used in all future UIView animations. - Now, kick off the view controller removal by calling
willMove(toParent:)
passingnil
to indicate thatcontroller
(the picker view controller) is about to be removed from its parent view controller. - Next, to trigger the animation, you invoke
removeWithZoomOutAnimation(duration:options:)
on the picker controller’s view. - 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:
- To achieve a “fade-in” effect, you first set the input view’s
alpha
to0.0
so the view starts off invisible. - You then add the view as a subview.
- Finally, since the
alpha
property ofUIView
is another animatable property, simply setting it1.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:
- 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.
- 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. - To trigger the animation sequence, you schedule a timer that calls
removeWithSinkAnimationRotateTimer(timer:)
every0.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:
- 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. - Next, apply a rotation of 18°.
- To slowly fade the view out, you also reduce the alpha to 98% of its current value.
- Now, you decrement the current step by subtracting 1 from the
tag
value. - Finally, you check to see if the
tag
value is0
, 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:
- First, you instantiate the
hud
property by loading theFakeHUD
NIB. - Next, you center the HUD in the current view.
- 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.