Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

38. Polish the Pop-up
Written by Eli Ganim

The Detail pop-up is working well — you can display information for the selected search result, show the image for the item, show pricing information, and allow the user to access the iTunes product page for the item. You are done with the Detail pop-up and can move on to the next item, right?

Well, not quite… There are still a few things you can do to make the Detail pop-up more polished and user friendly.

This chapter will cover the following:

  • Dynamic type: Add support for dynamic type so that your text can dispaly at a size specified by the user.
  • Gradients in the background: Add a gradient background to make the Detail pop-up background look more polished.
  • Animation!: Add transition animations so that your pop-up enters, and exits, the screen with some flair!

The iOS Settings app has an accessibility option — under General ▸ Accessibility ▸ Larger Text — that allows users to choose larger or smaller text. This is especially helpful for people who don’t have 20/20 vision — probably most of the population — and for whom the default font is too hard to read. Nobody likes squinting at their device!

You can find this setting both in your device and in the Simulator:

The Larger Text accessibility settings
The Larger Text accessibility settings

Apps have to opt-in to use this Dynamic Type feature. Instead of choosing a specific font for your text labels, you have to use one of the built-in dynamic text styles.

Configuring for Dynamic Type

To provide a better user experience for all users, whether their eyesight is good or bad, you’ll change the Detail pop-up to use Dynamic Type for its labels.

➤ Open the storyboard and go to the Detail scene. Change the Font setting for the Name label to the Headline text style:

Changing the font to the dynamic Headline style
Changing the font to the dynamic Headline style

You can’t pick a font size when selecting text styles — the font size depends on the user and the Larger Text setting they use on their device.

➤ Set the Lines attribute to 0. This allows the Name label to fit more than one line of text.

Auto Layout for Dynamic Type

Of course, if you don’t know beforehand how large the label’s font will be, you also won’t know how large the label itself will end up being, especially if it sometimes may have more than one line of text. You won’t be surprised to hear that Auto Layout and Dynamic Type go hand-in-hand.

You want to make the name label resizable so that it can hold any amount of text at any possible font size, but it cannot go outside the bounds of the pop-up, nor overlap the labels below.

The trick is to capture these requirements in Auto Layout constraints.

Previously you’ve used the Add New Constraints button to make constraints, but that may not always give you the constraints you want. With this menu, pins are expressed as the amount of “spacing to nearest neighbor.” But what exactly is the nearest neighbor?

If you use the Add New Constraints button on the Name label, Interface Builder may decide to pin it to the bottom of the close button, which is weird. It makes more sense to pin the Name label to the image view instead. That’s why you’re going to use a different way to make constraints.

➤ Select the Name label. Now Control-drag to the Image View and let go of the mouse button.

Control-drag to make a new constraint between two views
Control-drag to make a new constraint between two views

From the pop-up that appears, choose Vertical Spacing:

The possible constraint types
The possible constraint types

This puts a vertical spacing constraint between the label and the image view:

The new vertical space constraint
The new vertical space constraint

Of course, you’ll also get some red lines because the label still needs additional constraints.

The vertical space you just added needs to be 8 points.

➤ Select the constraint — by carefully clicking it with the mouse or by selecting it from the Document Outline — then go to the Size inspector; or the Attributes inspector, they both show the same settings for layout constraints, and make sure that Constant is set to 8.

Attributes for the vertical space constraint
Attributes for the vertical space constraint

Note that the inspector clearly describes what sort of constraint this is: Name Label.Top is connected to Artwork Image View.Bottom with a distance (Constant) of 8 points.

➤ Select the Name label again and Control-drag to the left and connect it to Pop-up View. Select Leading Space to Container:

The pop-up shows different constraint types
The pop-up shows different constraint types

This adds a blue bar on the left. Notice how the pop-up offered different options this time? The constraints that you can set depend on the direction that you’re dragging in.

➤ Repeat the step but this time Control-drag to the right. Now choose Trailing Space to Container.

The constraints for the Name label
The constraints for the Name label

The Name label is now connected to the left edge of the Pop-up View and to its right edge — enough to determine its X-position and width — and to the bottom of the image view, for its Y-position. There is no constraint for the label’s height, allowing it to grow as tall as it needs to using its intrinsic content size.

Shouldn’t these constraints be enough to uniquely determine the label’s position and size? If so, why is there still a red box?

Simple: the image view now has a constraint attached to it, and therefore no longer gets automatic constraints. You also have to add constraints that give the image view its position and size.

➤ Select the Image View, Control-drag up to the Pop-up View, and choose Top Space to Container. That takes care of the Y-position.

➤ Repeat but now Control-drag to the left (or right) and choose Center Horizontally in Container. That center-aligns the image view to take care of the X-position. If you don’t see this option, then make sure you’re not dragging outside the Pop-up View.

➤ Control-drag diagonally this time, but let go of the mouse button while you’re still inside the image view. Hold down Shift and put checkmarks in front of both Width and Height, then press return. If you don’t see both options, make sure you Control-drag diagonally instead of straight up or sideways.

Adding multiple constraints at once
Adding multiple constraints at once

Now the image view and the Name label will have all blue bars.

There’s one more thing you need to fix. Look again at that blue bar to the right of the Name label. This forces the label to be always about 45 points wide. That’s not what you want; instead, the label should be able to grow until it reaches the edge of the Pop-up View.

➤ Click that blue bar to select it and go to the Size inspector. Change Relation to Greater Than or Equal, and Constant to 8.

Converting the constraint to Greater Than or Equal
Converting the constraint to Greater Than or Equal

Now this constraint can resize to allow the label to grow, but it can never become smaller than 8 points. This ensures there is at least an 8 point margin between the label and the edge of the Detail pop-up.

By the way, notice how this constraint is between Pop-up View.Trailing and Name Label.Trailing? In Auto Layout terminology, trailing means “on the right,” while leading means “on the left.”

Why didn’t they just call this left and right? Well, not everyone writes in the same direction. With right-to-left languages such as Hebrew or Arabic, the meaning of trailing and leading is reversed. This allows your layouts to work without changes for those languages too.

➤ Run the app and try it out:

The text overlaps the other labels
The text overlaps the other labels

Well, the word-wrapping seems to work, but the text overlaps the labels below it. Let’s add some more constraints so that the other labels get pushed down instead.

Tip: In the next steps you’ll change the properties of the constraints using the Attributes inspector, but it can be quite tricky to select those constraints. The blue bars are often tiny, making them difficult to click. It’s often easier to find the constraint in the Document Outline, but it’s not always immediately obvious which one you need.

A smarter way to find a constraint is to first select the view it belongs to, then go to the Size inspector and look in the Constraints section. Here is what it looks like for the Name label:

The Name label’s constraints in the Size inspector
The Name label’s constraints in the Size inspector

To edit the constraint, double-click it or use the Edit button to the right of each constraint.

OK, let’s make those changes…

➤ Select the Artist Name label and set its Font to the Subhead text style.

➤ Set the Font of the other four labels to the Caption 1 text style. You can do this in a single go if you multiple-select these labels by holding down the key.

Auto Layout for Artist Name

Let’s pin the Artist Name label. Again you do this by Control-dragging.

  • Pin it to the left with a Leading Space to Container.
  • Pin it to the right with a Trailing Space to Container. Just like before, change this constraint’s Relation to Greater Than or Equal and Constant to 8.
  • Pin it to the Name label with a Vertical Spacing. Change this to size 4.

Auto Layout for Type

For the Type: label:

  • Pin it to the left with a Leading Space to Container.
  • Pin it to the Artist Name label with a Vertical Spacing, size 8.

The Kind Value label is slightly different:

  • Pin it to the right with a Trailing Space to Container. Change this constraint’s Relation to Greater Than or Equal and Constant to 8.
  • Control-drag from Kind Value to Type and choose First Baseline. This aligns the bottom of the text of both labels. This alignment constraint determines the Kind Value’s Y-position so you don’t have to make a separate constraint for that.

Auto Layout for Genre

Two more labels to go. For the Genre: label:

  • Pin it to the left with a Leading Space to Container.

  • Pin it to the Type: label with a Vertical Spacing, size 4.

  • On the right, pin it to the Genre Value label with a Horizontal Spacing. This should be a 8 point distance.

And finally, the Genre Value label:

  • Pin it to the right with a Trailing Space to Container, Greater Than or Equal 8.
  • Make a First Baseline alignment between Genre Value and Genre:.
  • Make a Leading alignment between Genre Value and Kind Value. This makes these two labels neatly align on the left.
  • Resolve any Auto Layout issues by selecting Editor ▸ Resolve Auto Layout Issues ▸ Update Frames from the Xcode menu. You may need to set the Constant of the alignment constraints to 0 if things don’t line up properly.

That’s quite a few constraints, but using Control-drag to make them is quite fast. With some experience you’ll be able to whip together complex Auto Layout constraints in no time.

Auto Layout for Price button

There is one more thing to do. The last row of labels needs to be pinned to the price button. That way there are constraints going all the way from the top of the Pop-up View to the bottom. The heights of the labels plus the sizes of the Vertical Spacing constraints between them will now determine the height of the Detail pop-up.

The height of the pop-up view is determined by the constraints
The height of the pop-up view is determined by the constraints

➤ Control-drag from the $9.99 button up to Genre Value. Choose Vertical Spacing. In the Size inspector, set Constant to 10.

While you might not notice this immediately, this introduces some Auto Layout constraint issues at this point — try clicking on the Genre: or Name labels and you’ll see some constraints turn red.

This is because the Pop-up View still has a Height constraint that forces it to be 240 points high. But the labels, image, and the vertical space constraints on these views don’t add up to 240.

➤ You no longer need this Height constraint, so select it — the one called height = 240 in the Document Outline — and press delete to get rid of it.

➤ If necessary — if you have any views with orange rectangles around them — from the Editor ▸ Resolve Auto Layout Issues menu, choose Update Frames from the “All Views” section.

Now all your constraints turn blue and everything fits snugly together.

➤ Run the app to try it out.

The text properly wraps without overlapping
The text properly wraps without overlapping

You now have an automatically resizing Detail pop-up that uses Dynamic Type for its labels!

Testing Dynamic Type

➤ Close the app and open the Settings app. Go to General ▸ Accessibility ▸ Larger Text. Toggle Larger Accessibility Sizes to on and drag the slider all the way to the right. That gives you the maximum font size — it’s huge!

Now go back to StoreSearch and open a new pop-up. The text is a lot bigger:

Changing the text size results in a bigger font
Changing the text size results in a bigger font

For fun, change the font of the Name label to Body. Bazinga, that’s some big text!

When you’re done playing, put the Name label font back to Headline, and turn off the Larger Text setting — the slider goes in the middle.

Dynamic Type is an important feature to add to your apps. This was only a short introduction, but hopefully the principle is clear: instead of a font with a fixed size, you use one of the available Text Styles: Body, Headline, Caption, and so on.

Then you set up Auto Layout constraints to make your views resizable and looking good no matter how large or small the font.

➤ This is a good time to commit the changes.

Exercise: Set up the cells from the table view for Dynamic Type. There’s a catch: when the user returns from changing the text size settings, the app should refresh the screen without needing an app restart. You can do this by reloading the table view when the app receives a UIContentSizeCategoryDidChange notification — see the previous app for a refresher on how to handle notifications.

Also check out the property adjustsFontForContentSizeCategory on UILabel. If you set this to true, then the app will automatically update the label whenever the font size changes. Good luck! Check the forums at forums.raywenderlich.com for solutions from other readers.

Stack Views

Setting up all those constraints was quite a bit of work, but it was good Auto Layout practice! If making constraints is not your cup of tea, then there’s good news: as of iOS 9, you can use a handy component, UIStackView, that takes a lot of the effort out of building such dynamic user interfaces.

Using stack views is fairly straightforward: you drop a Horizontal or Vertical Stack View in your scene, and then you put your labels, image views, and buttons inside that stack view. Of course, a stack view can contain other stack views as well, allowing you to create very complex layouts quite easily.

Give it a try! See if you can build the Detail pop-up with stack views. If you get stuck, we have a video tutorial series on the website that goes into great detail on UIStackView: raywenderlich.com/tag/stack-view

Gradients in the background

As you can see in the previous screenshots, the table view in the background is dimmed by the view of the DetailViewController, which is 50% transparent black. That allows the pop-up to stand out more.

It works well, but a plain black overlay is a bit dull. Let’s turn it into a circular gradient instead.

You could use Photoshop to draw such a gradient and place an image view behind the pop-up, but why use an image when you can also draw using Core Graphics? Additionally, an image would increase the size of your app and might also create some issues when you need to support larger screen sizes.

To pull this off, you will create your own UIView subclass.

The GradientView class

➤ Add a new Swift File to the project. Name it GradientView.

This will be a very simple view. It simply draws a black circular gradient that goes from mostly opaque in the corners to mostly transparent in the center. Placed on a white background, it looks something like this:

What the GradientView looks like by itself
What the GradientView looks like by itself

➤ Replace the contents of GradientView.swift with:

import UIKit

class GradientView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = UIColor.clear
  }
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    backgroundColor = UIColor.clear
  }
  
  override func draw(_ rect: CGRect) {
    // 1
    let components: [CGFloat] = [ 0, 0, 0, 0.3, 0, 0, 0, 0.7 ]
    let locations: [CGFloat] = [ 0, 1 ]
    // 2
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let gradient = CGGradient(colorSpace: colorSpace, 
                   colorComponents: components, 
                   locations: locations, count: 2)
    // 3
    let x = bounds.midX
    let y = bounds.midY
    let centerPoint = CGPoint(x: x, y : y)
    let radius = max(x, y)
    // 4
    let context = UIGraphicsGetCurrentContext()
    context?.drawRadialGradient(gradient!, 
      startCenter: centerPoint, startRadius: 0, 
      endCenter: centerPoint, endRadius: radius, 
      options: .drawsAfterEndLocation)
  }
}

In the init(frame:) and init?(coder:) methods you simply set the background color to fully transparent — the “clear” color. Then in draw() you draw the gradient on top of that transparent background, so that it blends with whatever is below.

The drawing code uses the Core Graphics framework. It may look a little scary but this is what it does:

  1. First, you create two arrays that contain the “color stops” for the gradient. The first color (0, 0, 0, 0.3) is a black color that is mostly transparent. It sits at location 0 in the gradient, which represents the center of the screen because you’ll be drawing a circular gradient.

    The second color (0, 0, 0, 0.7) is also black but much less transparent and sits at location 1, which represents the circumference of the gradient’s circle. Remember that in UIKit, and also in Core Graphics, colors and opacity values don’t go from 0 to 255 but are fractional values between 0.0 and 1.0.

    The 0 and 1 from the locations array represent percentages: 0% and 100%, respectively. If you have more than two colors, you can specify the percentages of where in the gradient you want to place these colors.

  2. With those color stops you can create the gradient. This gives you a new CGGradient object.

  3. Now that you have the gradient object, you have to figure out how big you need to draw it. The midX and midY properties return the center point of a rectangle. That rectangle is given by bounds, a CGRect object that describes the dimensions of the view. If possible, it’s better to not hard-code any dimensions such as “375 by 667 points.” By using bounds, you can use this view anywhere you want to, no matter how big a space it should fill. You can use it without problems on any screen size from the smallest iPhone to the biggest iPad. The centerPoint constant contains the coordinates for the center point of the view and radius contains the larger of the x and y values; max() is a handy function that you can use to determine which of two values is the biggest.

  4. With all those preliminaries done, you can finally draw the thing. Core Graphics drawing always takes places in what’s known as a graphics context. We’re not going to worry about exactly what that is, just know that you need to obtain a reference to the current context and then you can do your drawing.

    And finally, the drawRadialGradient() function draws the gradient according to your specifications.

Generally speaking, it isn’t optimal to create new objects inside your draw() method, such as gradients, especially if draw() is called often. In such cases it is better to create the objects the first time you need them and to reuse the same instance over and over — lazy loading for the win!

However, you don’t really have to do that here because this draw() method will be called just once — when the DetailViewController gets loaded — so you can get away with being less than optimal.

Note: By the way, you’ll only be using init(frame:) to create the GradientView instance. The other init method, init?(coder:), is never used in this app. However, UIView demands that all subclasses implement init?(coder:) — that is why it is marked as required — and if you remove this method, Xcode will complain with an error.

Using GradientView

Putting this new GradientView class to work is pretty easy. You’ll add it to your own presentation controller object. That way, the DetailViewController doesn’t need to know anything about it. Dimming the background is really a side effect of doing a presentation, so it belongs in the presentation controller.

➤ Open DimmingPresentationController.swift and add the following code to the class:

lazy var dimmingView = GradientView(frame: CGRect.zero)

override func presentationTransitionWillBegin() {
  dimmingView.frame = containerView!.bounds
  containerView!.insertSubview(dimmingView, at: 0)
}

The presentationTransitionWillBegin() method is invoked when the new view controller is about to be shown on the screen. Here you create the GradientView object, make it as big as the containerView, and insert it behind everything else in this “container view.”

The container view is a new view that is placed on top of the SearchViewController, and it contains the views from the DetailViewController. So this piece of logic places the GradientView in between those two screens.

There’s one more thing to do: because the DetailViewController’s background color is still 50% black, this color gets multiplied with the colors inside the gradient view, making the gradient look extra dark. It’s better to set the background color to 100% transparent, but if we do that in the storyboard, it makes it harder to see and edit the pop-up view. So let’s do this in code instead.

➤ Add the following line to viewDidLoad() in DetailViewController.swift:

view.backgroundColor = UIColor.clear

➤ Run the app and see what happens.

The background behind the pop-up now has a gradient
The background behind the pop-up now has a gradient

Nice! That looks a lot smarter.

Animation!

The pop-up itself looks good already, but the way it enters the screen — Poof! It’s suddenly there — is a bit unsettling. iOS is supposed to be the king of animation, so let’s make good on that.

You’ve used Core Animation and UIView animations before. This time you’ll use a keyframe animation to make the pop-up bounce into view.

To animate the transition between two screens, you use an animation controller object. The purpose of this object is to animate a screen while it’s being presented or dismissed, nothing more.

Now let’s add some liveliness to this pop-up!

The animation controller class

➤ Add a new Swift File to the project, named BounceAnimationController.

➤ Replace the contents of the new file with:

import UIKit

class BounceAnimationController: NSObject, 
                         UIViewControllerAnimatedTransitioning {
  
  func transitionDuration(using transitionContext: 
       UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.4
  }
  
  func animateTransition(using transitionContext: 
                         UIViewControllerContextTransitioning) {
      
    if let toViewController = transitionContext.viewController(
               forKey: UITransitionContextViewControllerKey.to),
       let toView = transitionContext.view(
                       forKey: UITransitionContextViewKey.to) {

      let containerView = transitionContext.containerView     
      toView.frame = transitionContext.finalFrame(for: 
                                               toViewController)
      containerView.addSubview(toView)
      toView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
      
      UIView.animateKeyframes(withDuration: transitionDuration(
        using: transitionContext), delay: 0, options: 
        .calculationModeCubic, animations: {
        UIView.addKeyframe(withRelativeStartTime: 0.0, 
             relativeDuration: 0.334, animations: {
          toView.transform = CGAffineTransform(scaleX: 1.2, 
                                                    y: 1.2)
        })
        UIView.addKeyframe(withRelativeStartTime: 0.334, 
             relativeDuration: 0.333, animations: {
          toView.transform = CGAffineTransform(scaleX: 0.9, 
                                                    y: 0.9)
        })
        UIView.addKeyframe(withRelativeStartTime: 0.666, 
             relativeDuration: 0.333, animations: {
          toView.transform = CGAffineTransform(scaleX: 1.0, 
                                                    y: 1.0)
        })
      }, completion: { finished in
        transitionContext.completeTransition(finished)
      })
    }
  }
}

To become an animation controller, the object needs to extend NSObject and also implement the UIViewControllerAnimatedTransitioning protocol — quite a mouthful! The important methods from this protocol are:

  • transitionDuration(using:) – This determines how long the animation is. You’re making the pop-in animation last for only 0.4 seconds, but that’s long enough. Animations are fun, but they shouldn’t keep the user waiting.

  • animateTransition(using:) – This performs the actual animation.

To find out what to animate, you look at the transitionContext parameter. This gives you a reference to a new view controller and lets you know how big it should be.

The actual animation starts at the line UIView.animateKeyframes(…). This works like all UIView-based animations: you set the initial state before the animation block, and UIKit will automatically animate any properties that get changed inside the closure. The difference from before is that a keyframe animation lets you animate the view in several distinct stages.

The property you’re animating is the transform. If you’ve ever taken any matrix math you’ll be pleased — or terrified! — to hear that this is an affine transformation matrix. It allows you to do all sorts of funky stuff with the view, such as rotating it or shearing it. But the most common use of the transform is for scaling.

The animation consists of several keyframes. It will smoothly proceed from one keyframe to the next over a certain amount of time. Because you’re animating the view’s scale, the different toView.transform values represent how much bigger or smaller the view will be over time.

The animation starts with the view scaled down to 70% (scale 0.7). The next keyframe inflates it to 120% of its normal size. After that, it will scale the view down a bit again but not as much as before — only 90% of its original size. The final keyframe ends up with a scale of 1.0, which restores the view to an undistorted shape.

By quickly changing the view size from small to big to small to normal, you create a bounce effect.

You also specify the duration between the successive keyframes. In this case, each transition from one keyframe to the next takes 1/3rd of the total animation time. These times are not in seconds but in fractions of the animation’s total duration, which is 0.4 seconds.

Feel free to mess around with the animation code. No doubt you can make it much more spectacular!

Using the new animation controller

To use this animation in your app, you have to tell the app to use the new animation controller when presenting the Detail pop-up. That happens in the transitioning delegate inside DetailViewController.swift.

➤ Add the following method to the UIViewControllerTransitioningDelegate extension:

func animationController(forPresented presented: 
     UIViewController, presenting: UIViewController, 
     source: UIViewController) -> 
     UIViewControllerAnimatedTransitioning? {
  return BounceAnimationController()
}

And that’s all you need to do.

➤ Run the app and get ready for some bouncing action!

The pop-up animates
The pop-up animates

The pop-up looks a lot spiffier with the bounce animation, but there are two things that could be better: the GradientView still appears abruptly in the background, and the animation upon dismissal of the pop-up is very plain.

Animating the background

There’s no reason why you cannot have two things animating at the same time. So, let’s make the GradientView fade in while the pop-up bounces into view. That is a job for the presentation controller, because that’s what provides the gradient view.

➤ Go to DimmingPresentationController.swift and add the following to the end of presentationTransitionWillBegin():

// Animate background gradient view
dimmingView.alpha = 0
if let coordinator = 
   presentedViewController.transitionCoordinator {
  coordinator.animate(alongsideTransition: { _ in
	self.dimmingView.alpha = 1
  }, completion: nil)
}

You set the alpha value of the gradient view to 0 to make it completely transparent, and then animate it back to 1 — or 100% — and fully visible, resulting in a simple fade-in. That’s a bit more subtle than making the gradient appear so abruptly. The special thing here is the transitionCoordinator stuff. This is the UIKit traffic cop in charge of coordinating the presentation controller and animation controllers and everything else that happens when a new view controller is presented.

The important thing to know about the transitionCoordinator is that all of your animations should be done in a closure passed to animateAlongsideTransition to keep the transition smooth. If your users wanted choppy animations, they wouldn’t be using iPhones, would they?

➤ Also add the method dismissalTransitionWillBegin(), which is used to animate the gradient view out of sight when the Detail pop-up is dismissed:

override func dismissalTransitionWillBegin()  {
  if let coordinator = 
     presentedViewController.transitionCoordinator {
    coordinator.animate(alongsideTransition: { _ in
      self.dimmingView.alpha = 0
    }, completion: nil)
  }
}

This does the reverse: it animates the alpha value back to 0% to make the gradient view fade out.

➤ Run the app. The dimming gradient now appears almost without you even noticing it. Slick!

Animating the pop-up exit

After tapping the Close button, the pop-up slides off the screen, like modal screens always do. Let’s make this a bit more exciting and make it slide up instead of down. For that you need another animation controller.

➤ Add a new Swift File to the project, named SlideOutAnimationController.

➤ Replace the new file’s contents with:

import UIKit

class SlideOutAnimationController: NSObject, 
                         UIViewControllerAnimatedTransitioning {
  func transitionDuration(using transitionContext: 
       UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.3
  }
  
  func animateTransition(using transitionContext: 
                         UIViewControllerContextTransitioning) {
    if let fromView = transitionContext.view(forKey: 
                      UITransitionContextViewKey.from) {
      let containerView = transitionContext.containerView
      let time = transitionDuration(using: transitionContext)
      UIView.animate(withDuration: time, animations: {
        fromView.center.y -= containerView.bounds.size.height
        fromView.transform = CGAffineTransform(scaleX: 0.5, 
                                                    y: 0.5)
      }, completion: { finished in
        transitionContext.completeTransition(finished)
      })
    }
  }
}

This is pretty much the same as the other animation controller, except that the animation itself is different. Inside the animation block you subtract the height of the screen from the view’s center position while simultaneously zooming it out to 50% of its original size, making the Detail screen fly up-up-and-away.

➤ In DetailViewController.swift, add the following method to the UIViewControllerTransitioningDelegate extension:

func animationController(forDismissed dismissed: 
  UIViewController) -> UIViewControllerAnimatedTransitioning? {
  return SlideOutAnimationController()
}

This simply overrides the animation controller to be used when a view controller is dismissed.

➤ Run the app and try it out. That looks pretty sweet if you ask me!

➤ If you’re happy with the way the animations look, then commit your changes.

Exercise: Create some exciting new animations. You can definitly improve on the existing ones. Hint: use the transform matrix to add some rotation to the mix.

You can find the project files for this chaper under 38 – Polish the Pop-up in the Source Code folder.

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.
© 2026 Kodeco Inc.