Chapters

Hide chapters

iOS Animations by Tutorials

Seventh Edition · iOS 15 · Swift 5.5 · Xcode 13

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

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:

  1. Create a new empty method resetForm() and call it from your code where the placeholder comment //reset form lives.

  2. In resetForm() use transition(with:duration:options: animations:completion:) to set the visibility of status to hidden and center to self.statusPosition. This should reset the banner to its initial state. Use a 0.2 seconds duration for that transition.

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

  4. Next, add a call to animate(withDuration:delay:options: animations:completion:) in resetForm(). 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 to 0.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 by 80.0 points.

  • Finally, move the button back up to its original spot under the password field and decrease center.y by 60.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:

  1. Create a new method with signature animateCloud(cloud: UIImageView) and add your code inside that.

  2. First, calculate the average cloud speed. Assume the cloud should cross the entire length of the screen in about 60.0 seconds. Call that constant cloudSpeed and set it to 60.0 / view.frame.size.width.

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

  4. Then call animate(withDuration:delay:options:animation:completion:) with the duration you just calculated above. You’ll need to create an instance of TimeInterval 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.

  1. Inside the animations closure expression set the frame.origin.x property of the cloud to self.view.frame.size.width. This moves the cloud to just outside the screen area.

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

  3. Still working in the completion closure, add a call to animateCloud() so that the cloud re-animates across the screen.

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

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.