5.
Transitions
Written by Marin Todorov
Transitions are predefined animations you can apply to views. These predefined animations don’t attempt to interpolate between the start and end states of your view (like the animations you created in the previous two chapters). Instead, you’ll design the animations with the transitions API so that the various changes in your UI appear natural.
Example transitions
To better understand when you’d use transitions, this section walks you through the various animation scenarios where you could make use of transition animations.
Adding a new view
To animate the addition of a new view on the screen, you call a method similar to the ones you used in the previous chapters. The difference this time is that you’ll choose one of the predefined transition effects and animate what’s known as an animation container view.
The transition animates the container view, and any new views you add to it as subviews appear while the animation runs.
To better explain how you’d animate container views and when you’d execute the transition between subviews, read the following code snippet (you don’t need to type this in anywhere):
var animationContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//set up the animation container
animationContainerView = UIView(frame: view.bounds)
animationContainerView.frame = view.bounds
view.addSubview(animationContainerView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//create new view
let newView = UIImageView(image: UIImage(named: "banner"))
newView.center = animationContainerView.center
//add the new view via transition
UIView.transition(with: animationContainerView,
duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.animationContainerView.addSubview(newView)
},
completion: nil
)
}
In this hypothetical situation, you create a new view named animationContainerView
in viewDidLoad()
of your view controller. You then position and add this container to the view.
Later, when you want to create an animated transition, you create a new view to animate; here it’s named newView
.
To create the transition, you call transition(with:duration: options:animations:completion:)
. This is almost identical to the standard UIView animation method, but in this case you supply an extra parameter view
, which serves as the container view for the transition animation.
There’s a new animation option here, .transitionFlipFromBottom
, which you haven’t seen yet. This is one of the predefined transitions discussed in the introduction of this chapter. .transitionFlipFromBottom
flips the view over with the bottom edge of the view serving as the “hinge” around which the view flips.
Finally, you add a subview to your animation container within your animations block, which causes the subview to appear during the transition.
The full list of predefined transition animation options are as follows:
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
Removing a view
Removing a subview from the screen with a transition animation works much like adding a subview. To do this with a transition animation, you simply call removeFromSuperview()
inside the animation closure expression like so (again, this is an example, you don’t need to enter this code):
//remove the view via transition
UIView.transition(with: animationContainerView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
someView.removeFromSuperview()
},
completion: nil
)
The wrapper transition will perform the flip animation and someView
will disappear at the end of it all.
Hiding/showing a view
So far in this chapter, you’ve learned only about transitions that alter the view hierarchy. That’s why you needed a container view for the transitions — this puts the hierarchy change in context.
In contrast, you don’t need to worry about setting up a container view to hide and show views. In this case, the transition uses the view itself as the animation container.
Consider the following code to hide a subview using a transition:
//hide the view via transition
UIView.transition(with: someView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
someView.alpha = 0
},
completion: { _ in
someView.isHidden = true
}
)
Here you pass in the view you intend to show or hide as the first argument to transition(with:duration:options:animations:completion:)
. All you do after that is set the alpha
property of your view in the animations block and voilà, the transition animation kicks off.
Replacing a view with another view
Replacing one view with another view is also an easy process. You simply pass in the existing view as the first argument and set the toView
: parameter as the view you wish to replace it with as shown below:
//replace via transition
UIView.transition(from: oldView, to: newView, duration: 0.33,
options: .transitionFlipFromTop, completion: nil)
It’s becoming quite apparent how much of the heavy lifting UIKit does for you!
In the remainder of this chapter, you’ll work on showing and hiding UI elements via transitions and learn some new animation skills you can bring into your own projects!
Mixing in transitions
You’ll continue to work on the Bahama Air login screen project in this chapter; you’ve already created a number of compelling animations to the views on this screen to add a bit of pizzazz to the login form and to make the button react to being tapped.
Next, you’re going to simulate a bit of user authentication and animate several different progress messages. Once the user taps the Log In button, you’ll show them messages including “Connecting…”, “Authorizing…” and “Failed.”
If you haven’t worked through the previous chapters, you can begin with the starter project in the Resources folder for this chapter. If you’ve followed the examples in the last few chapters in your own project, great job! You can simply continue working with your existing project.
Open ViewController.swift and look at viewDidLoad()
. Part of this method adds a hidden image view stored in the class variable status. The code then creates a text label and adds it as a sub-view to status
.
You’ll use status
to show the progress messages to the user. The messages are sourced from the messages array, which is another class variable included in the starter project.
Add the following method to ViewController
:
func showMessage(index: Int) {
label.text = messages[index]
UIView.transition(with: status, duration: 0.33,
options: [.curveEaseOut, .transitionCurlDown],
animations: {
self.status.isHidden = false
},
completion: {_ in
//transition completion
}
)
}
This method takes one parameter called index
, which you use to set the value of label
to the contents of messages based on index
.
Next you call transition(with:duration:options:animations: completion:)
to animate the view. To show the banner as it transitions you set isHidden
to false
within the animations block.
There’s another new animation option above: .transitionCurlDown
. This transition animates the view like a sheet of paper being flipped down on a legal pad and looks like the following:
It’s time to exercise your new showMessage(index:)
method. Find the following block of code in login()
:
animations: {
self.loginButton.bounds.size.width += 80.0
}, completion: nil)
This is your existing animations block that makes the login button bounce when the user taps it. You’ll add something new — a completion closure — to this animation that calls showMessage(index:)
.
Replace the completion’s nil
argument value with the following closure expression:
completion: { _ in
self.showMessage(index: 0)
}
The closure takes one Bool
parameter, which tells you whether the animation finished successfully or was cancelled before it ran to completion.
Note: The completion closure above has a single parameter for
completed
. Because you don’t care whether it finished or not, Swift allows you to skip binding the parameter by putting “_” in its place.
Inside the closure you simply call showMessage
with index 0
to show the very first message from the messages array.
Build and run your project; tap the Log In button and you’ll see the status banner appear with your first progress message.
Did you see how the banner curled down like a sheet of paper? That’s a really nice way to bring attention to messages that are usually just shown as a static text label.
Note: Some of your animations seem to run really quickly, don’t they? Sometimes it’s tricky to ensure the animation happens at just the right location, and in the correct sequence. Or maybe you just want things to happen slower so you can appreciate the effect!
To slow down all animations in your app without changing your code, select Debug/Toggle Slow Animations in Frontmost App from the iPhone Simulator menu. Now tap the Login button in your app and enjoy the animations and transitions in vivid slow motion!
For the next animation, you’ll first need to save the banner’s initial position so you can place the next banner in just the right spot.
Add the following code to the end of viewDidLoad()
to save the banner’s initial position to the property called statusPosition
:
statusPosition = status.center
Now you can begin to devise a mixture of view animations and transitions.
Add the following method to remove the status message from the screen via a standard animation:
func removeMessage(index: Int) {
UIView.animate(withDuration: 0.33, delay: 0.0, options: [],
animations: {
self.status.center.x += self.view.frame.size.width
},
completion: { _ in
self.status.isHidden = true
self.status.center = self.statusPosition
self.showMessage(index: index+1)
}
)
}
In the code above you use your old friend animate(withDuration:delay:options:animations:completion:)
to move status
just outside of the visible area of the screen.
When the animation completes in the completion
closure, you move status back to its original position and hide it. Finally you call showMessage
again, but this time you pass the index of the next message to show.
Combining standard animations with transitions is quite easy: you simply call the appropriate API and UIKit calls the corresponding Core Animation bits in the background.
Now you need to complete the chain of calls between showMessage
and removeMessage
to mimic a real authentication process.
Find showMessage(index:)
and replace the comment //transition completion
with the following code:
delay(2.0) {
if index < self.messages.count-1 {
self.removeMessage(index: index)
} else {
//reset form
}
}
Once the transition completes, you wait for 2.0
seconds and check if there are any remaining messages. If so, remove the current message via removeMessage(index:)
. You then call showMessage(index:)
in the completion block of removeMessage(index:)
to display the next message in sequence.
Note:
delay(_:completion:)
is a convenience function that runs a block of code after an elapsed delay; it’s defined at the top of ViewController.swift. Here you’re using it to simulate the usual network access delay.
Build and run your project once more; enjoy the resulting animation sequence, which updates the authentication progress messages like so:
Transitions are a small but important subset of animation knowledge to keep in your figurative toolbox, since they’re the only way to create 3D-styled animations in UIKit.
If you’re looking forward to learning more elaborate 3D effects, you’ll have the opportunity to do that in Section VI, “3D Animations”, which discusses Core Animation and 3D layer transformations in detail.
Before you move on to the next section, give the challenges in this chapter a try. Since you’ve learned so much about animation in the last three chapters, one challenge isn’t enough — I’ve given you three!
These challenges give you the chance to wrap up development on your Bahama Air login screen — and also to take on your first über haxx0r challenge. Woot!
Key Points
-
Apple provides a set of predefined animations called transitions you can use to handle special changes in your app’s UI state.
-
Transitions are targeted towards adding, removing, and replacing views in the view hierarchy.
-
When you are designing animations, you can enable Debug ▸ Toggle Slow Animations in the Simulator to be able to observe them in slow motion.
Challenges
Challenge 1: Pick your favorite transition
So far you’ve seen only one of the built-in transition animations. Aren’t you curious to see what the others look like?
In this challenge you can try out all the other available transition animations and use your favorite to animate the progress message banner.
Open ViewController
and find the line in showMessage(index:)
where you specify the transition animation .transitionCurlDown
:
UIView.transitionWithView(status, duration: 0.33, options:
[.curveEaseOut, .transitionCurlDown], animations: …
Replace .transitionCurlDown
with any of the other transition animations available, then build and run your project to see how they look. Here’s the list of available transitions:
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
Which one do you think works best with the other animations on this screen?
In case you don’t have a favorite, try my favorite transition: .transitionFlipFromBottom
. I think it fits really well with the banner graphic:
Challenge 2: Reset the form to its initial state
For this challenge, you’ll reset the form to its initial state by undoing all the animations that run once you tap the Log In button. That way, if the login fails, the user would see all of the animations happen again when they tap the Log In button a second time.
Here’s a list of the general steps you’ll need to complete this challenge:
-
Create a new empty method
resetForm()
and call it from your code where the placeholder comment//reset form
lives. -
In
resetForm()
usetransition(with:duration:options: animations:completion:)
to set the visibility of status to hidden and center toself.statusPosition
. This should reset the banner to its initial state. Use a0.2
seconds duration for that transition. -
It would be nice if the transition that hides your banner uses the exact opposite animation of the one that shows the banner. For example, if you show the banner via
.transitionCurlDown
, then use.transitionCurlUp
to hide it. The reverse of.transitionFlipFromBottom
would be.transitionFlipFromTop
…and so forth. -
Next, add a call to
animate(withDuration:delay:options: animations:completion:)
inresetForm()
. Make the following adjustments inside the animations closure block:
-
Move
self.spinner
— the activity indicator inside the Log In button — to its original position of(-20.0, 16.0)
. -
Set the alpha property of
self.spinner
to0.0
to hide it. -
Tint the background color of the Log In button back to its original value:
UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
. -
Continue to reset all changes you made to the Log In button and decrease the
bounds.size.width
property by80.0
points. -
Finally, move the button back up to its original spot under the password field and decrease
center.y
by60.0
points.
If you precisely reversed all of the animations in the authentication process, the screen should animate as below once all authentication messages have displayed:
Well done! And now for this chapter’s final challenge…
Challenge 3: Animate the clouds in the background
Wouldn’t it be cool if those clouds in the background moved slowly across the screen and reappeared from the opposite side?
Yeah, it would totally be cool — and that’s your challenge!
The four cloud image views are already wired to four outlets in ViewController
so you’re good to go. You can try to make the clouds move on your own, using your newfound knowledge of transition animations, or you can follow the recipe below:
-
Create a new method with signature
animateCloud(cloud: UIImageView)
and add your code inside that. -
First, calculate the average cloud speed. Assume the cloud should cross the entire length of the screen in about
60.0
seconds. Call that constantcloudSpeed
and set it to60.0 / view.frame.size.width
. -
Next, calculate the duration for the animation to move the cloud to the right side of the screen. Remember, the clouds don’t start from the left edge of the screen, but from random spots instead. You can calculate the correct duration by taking the length of the path the cloud needs to follow and multiplying the result by the average speed:
(view.frame.size.width - cloud.frame.origin.x) * cloudSpeed
. -
Then call
animate(withDuration:delay:options:animation:completion:)
with the duration you just calculated above. You’ll need to create an instance ofTimeInterval
from it as the compiler won’t have decided the correct type for you:TimeInterval(duration)
.
For the options parameter use .curveLinear
; this is one of the very few times you’ll use an animation with no easing. Clouds are naturally quite far in the background, so their movement should look absolutely even-keeled.
-
Inside the animations closure expression set the
frame.origin.x
property of the cloud toself.view.frame.size.width
. This moves the cloud to just outside the screen area. -
Inside the completion closure block move the cloud to just outside the opposite edge of the screen from its current position. Don’t forget to skip the closure parameter by using “_” as you did earlier in this chapter. To position the cloud properly set its
frame.origin.x
to-cloud.frame.size.width
. -
Still working in the completion closure, add a call to
animateCloud()
so that the cloud re-animates across the screen. -
Finally, add the following code to the end of
viewDidAppear()
to start the animations for all four clouds:
animateCloud(cloud1)
animateCloud(cloud2)
animateCloud(cloud3)
animateCloud(cloud4)
This should make all four clouds traverse the screen slowly to create a nice, unobtrusive effect.
If you completed the challenges for this chapter, congratulations! They were tough!
The past few chapters had a lot of information to digest, but you took a stiff, static login form and turned it into an eye-catching and fun experience for the user:
It’s time to take a short break from new material and put all your view animation knowledge to the test! In the next chapter, you’ll use a wide assortment of the practical things you’ve learned to add some serious polish to the Bahama Air app.