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

37. The Detail Pop-up
Written by Eli Ganim

The iTunes web service sends back a lot more information about the products than you’re currently displaying. Let’s add a “details” screen to the app that pops up when the user taps a row in the table:

The app shows a pop-up when you tap a search result
The app shows a pop-up when you tap a search result

The table and search bar are still visible in the background, but they have been darkened.

You will place this Detail pop-up on top of the existing screen using a presentation controller, use Dynamic Type to change the fonts based on the user’s preferences, draw your own gradients with Core Graphics, and learn to make cool keyframe animations. Fun times ahead!

This chapter will cover the following:

  • The new view controller: Create the bare minimum necessary for the new Detail pop-up and add the code to show/hide the pop-up.
  • Add the rest of the controls: Complete the design for the Detail pop-up.
  • Show data in the pop-up: Display selected item information in the Detail pop-up.

The new view controller

A new screen means a new view controller, so let’s start with that.

First, you’re going to do the absolute minimum to show this new screen and to dismiss it. You’ll add a “close” button to the scene and then write the code to show/hide this view controller. Once that works, you will put in the rest of the controls.

The basic view controller

➤ Add a new Cocoa Touch Class file to the project. Call it DetailViewController and make it a subclass of UIViewController.

➤ Open the storyboard and drag a new View Controller on to the canvas. Change its Class to DetailViewController — via the Identiy inspector tab.

➤ For ease of reference, change the new scene’s name from Detail View Controller to Detail by clicking on the yellow circle for the view controller on the Document Outline and clicking again to be able to edit the name.

Editing the scene name to give it a simpler name
Editing the scene name to give it a simpler name

➤ Similarly, rename the previously added Search View Controller scene to Search.

➤ Set the Background color of the Detail scene’s view to black, 50% opaque. That makes it easier to see what is going on in the next steps.

➤ Drag a new View into the scene. Using the Size inspector, make it 240 points wide and 240 high. Add Auto Layout constraints for width and height to ensure that the view statys at this size.

➤ Center the view in the scene by setting up horizontal and vertical centering Auto Layout constraints.

➤ In the Attributes inspector, change the Background color of this new view to Secondary System Background Color, 95% opaque. This makes it appear slightly translucent, just like navigation bars.

➤ With this new view still selected, go to the Identity inspector. For Document - Label — the field with the hint text of “Xcode Specific Label” — type Pop-up View. You can use this field to give your views names, so they are easier to distinguish in the Document Outline in Interface Builder. Now, instead of having multiple items called “View”, this particular view will display as “Pop-up View”.

Giving the view a description for use in Xcode
Giving the view a description for use in Xcode

➤ Drag a Button into the scene and place it somewhere on the Pop-up View. In the Attributes inspector, change Image to CloseButton — you already added this image to the asset catalog earlier.

➤ Remove the button’s text. Choose Editor ▸ Size to Fit Content to resize the button and place it in the top-left corner of the Pop-up View, at X = 4 and Y = 2.

➤ If the button’s Type now says Custom, change it back to System. That will make the image turn blue, because the default tint color is blue.

➤ Set the Xcode Specific Label for the Button to Close Button. Remember that this only changes the title displayed in the Interface Builder; the user will never see that text.

The design should look something like this:

The Detail scene has a white square and a close button on a dark background
The Detail scene has a white square and a close button on a dark background

Note: Xcode currently gives a warning that this new scene is unreachable. This warning will disappear after you make a segue to it, which you’ll do in a second.

Showing and hiding the scene

Let’s write the code to show and hide this new screen.

➤ In DetailViewController.swift, add the following action method:

// MARK:- Actions
@IBAction func close() {
  dismiss(animated: true, completion: nil)
}

➤ Connect this action method to the X button’s Touch Up Inside event in Interface Builder — as before, Control-drag from the button to the view controller and pick from Sent Events.

➤ Control-drag from the yellow circle at the top of the Search scene to the Detail scene to make a Present Modally segue. Give it the identifier ShowDetail.

Because the table view doesn’t use prototype cells, you have to put the segue on the view controller itself. That means you need to trigger the segue manually when the user taps a row.

➤ Open SearchViewController.swift and change didSelectRowAt to the following:

func tableView(_ tableView: UITableView, 
  didSelectRowAt indexPath: IndexPath) {
  tableView.deselectRow(at: indexPath, animated: true)
  // Add the following line
  performSegue(withIdentifier: "ShowDetail", sender: indexPath)  
}

You’re sending along the index-path of the selected row as the sender parameter. This will come in useful later when you’re putting the SearchResult object into the Detail pop-up.

Let’s see how well this works.

➤ Run the app, do a search, and tap on a search result. Hmm, that doesn’t look too good.

Even though you set the main view to be half transparent, the Detail screen still has a solid black background. Only during the animation is it see-through.

What happens when you present the Detail screen modally
What happens when you present the Detail screen modally

Hmm, presenting this new screen with a regular modal segue isn’t going to achieve the effect we’re after.

There are three possible solutions:

  1. Don’t have a DetailViewController. You can load the view for the detail pop-up from a nib and add it as a subview of SearchViewController, and put all the logic for this screen in SearchViewController. This is not a very good solution because it makes SearchViewController more complex — the logic for a new screen should really go into its own view controller.

  2. Use the view controller containment APIs to embed the DetailViewController “inside” the SearchViewController. This is a better solution but it’s still more work than necessary — you’ll see an example of view controller containment in an upcoming chapter where you’ll be adding a special landscape mode to the app.

  3. Use a presentation controller. This lets you customize how modal segues present their view controllers on the screen. You can even have custom animations to show and hide the view controllers.

Let’s go for #3. Transitioning from one screen to another in an iOS app involves a complex web of objects that take care of all the details concerning presentations, transitions, and animations. Normally, that all happens behind the scenes and you can safely ignore it.

But if you want to customize how some of this works, you’ll have to dive into the excitingly strange world of presentation controllers and transitioning delegates.

Custom presentation controller

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

➤ Replace the contents of this new file with the following:

import UIKit

class DimmingPresentationController: UIPresentationController {
  override var shouldRemovePresentersView: Bool {
    return false
  }
}

The standard UIPresentationController class contains all the logic for presenting new view controllers. You’re providing your own version that overrides some of this behavior — in particular, telling UIKit to leave the SearchViewController visible. That’s necessary to get the see-through effect.

Later you’ll also add a light-to-dark gradient background view to this presentation controller; that’s where the “dimming” in its name comes from.

Note: It’s called a presentation controller, but it is not a view controller. The use of the word controller may be a bit confusing here but not all controllers are for managing screens in your app — generally, only those with “view” in their name do that.

A presentation controller is an object that “controls” the presentation of something, just like a view controller is an object that controls a view and everything in it. Soon you’ll also see an animation controller, which controls — you guessed it — an animation.

There are quite a few different kinds of controller objects in the various iOS frameworks. Just remember that there’s a difference between a view controller and other types of controllers.

Now you need to tell the app that you want to use your own presentation controller to show the Detail pop-up.

➤ In DetailViewController.swift, add the following extension to the end of the file:

extension DetailViewController: 
          UIViewControllerTransitioningDelegate {

  func presentationController(
     forPresented presented: UIViewController, 
     presenting: UIViewController?, source: UIViewController) ->
     UIPresentationController? {
    return DimmingPresentationController(
             presentedViewController: presented, 
                          presenting: presenting)
  }
}

The methods from this delegate protocol tell UIKit what objects it should use to perform the transition to the Detail View Controller. It will now use your new DimmingPresentationController class instead of the standard presentation controller.

➤ Also add the following init method to DetailViewController:

required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  modalPresentationStyle = .custom
  transitioningDelegate = self
}

Recall that init?(coder) is invoked to load the view controller from the storyboard. Here you tell UIKit that this view controller uses a custom presentation and you set the delegate that will call the method you just implemented.

➤ Run the app again and tap a row to bring up the detail pop-up. That looks much better! Now the list of search results remains visible.

The Detail pop-up background is now see-through
The Detail pop-up background is now see-through

The standard presentation controller removed the underlying view from the screen, making it appear as if the Detail pop-up had a solid black background. Removing the view makes sense most of the time when you present a modal screen, as the user won’t be able to see the previous screen anyway. Plus, not having to redraw this view saves battery life too.

However, in your case, the modal segue leads to a view controller that only partially covers the previous screen. You want to keep the underlying view to get the see-through effect. That’s why you needed to supply your own presentation controller object.

➤ Also verify that the close button works to dismiss the pop-up.

Adding the rest of the controls

Let’s finish the design of the Detail screen. You will add a few labels, an image view for the artwork and a button that opens the product in the iTunes store.

The finished design will look like this:

The Detail screen with the rest of the controls
The Detail screen with the rest of the controls

Adding the controls

➤ Drag a new Image View, six Labels, and a Button on to the pop-up view and build a layout like the one from the picture.

Some suggestions for the dimensions and positions:

➤ The Name label’s font is System Bold 20. Set Autoshrink to Minimum Font Scale so the font can become smaller if necessary to fit as much text as possible.

➤ The font for the $9.99 button is also System Bold 20. You will add a background image for this button in a bit.

➤ You shouldn’t have to change the font for the other labels; they use the default value of System 17.

➤ Set the Color for the Type: and Genre: labels to 50% opaque black.

These new controls are pretty useless without outlet properties, so add the following lines to DetailViewController.swift:

@IBOutlet weak var popupView: UIView!
@IBOutlet weak var artworkImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var artistNameLabel: UILabel!
@IBOutlet weak var kindLabel: UILabel!
@IBOutlet weak var genreLabel: UILabel!
@IBOutlet weak var priceButton: UIButton!

➤ Connect the outlets to the views in the storyboard. Control-drag from Detail View Controller to each of the views and pick the corresponding outlet. The Type: and Genre: labels and the X button do not get an outlet.

➤ Run the app to see if everything still works.

The new controls in the Detail pop-up
The new controls in the Detail pop-up

Did you notice something interesting here? You didn’t add any Auto Layout constraints to the contents of the Pop-up View and yet, the controls you added stay in place fine no matter which size screen you run the app on. Try it by running the app on several different simulators.

Can you guess why?

The reason is that the Pop-up View itself is constrained to 240 x 240 points in size. So the contents of the view do not shift around — or change size — when the screen size changes. Because of this, you can rely on your original positioning of your controls to serve you here without any issues.

However, if the parent view — Pop-up View, in this case — were to change size, then you would need to set up Auto Layout constraints for the child controls if you wanted them to size correctly as the parent’s size changed.

In the meantime, you will get Xcode warnings about the controls inside the pop-up view not having Auto Layout constraints. Generally, whether you add Auto Layout constraints in such situations, or disregard these warnings, is totally up to you.

Note: For this particular app, let the Xcode warnings stay around for the moment. We will be adding Auto Layout constraints in the next chapter — there’s a reason for the delay.

Stretchable images

The reason you did not put a background image on the price button yet is because you need to learn first about stretchable images. When you put a background image on a button in Interface Builder, it always has to fit the button exactly. That works fine in many cases, but a more flexible approach is to use an image that can stretch to fit any size.

When an image view is wider than the image, it will automatically stretch the image to fit. In the case of a button, however, you don’t want to stretch the ends (or “caps”) of the button, only the middle part. That’s what a stretchable image lets you do.

The caps are not stretched but the inner part of the image is
The caps are not stretched but the inner part of the image is

For Bull’s Eye you used resizableImage(withCapInsets:) to cut the images for the slider track into stretchable parts. You can also do this in the asset catalog without having to write any code.

➤ Open Assets.xcassets and select the PriceButton image set.

The PriceButton image
The PriceButton image

If you take a look at the image info, you will see that it is only 11 points wide. That means it has a 5-point cap on the left, a 5-point cap on the right, and a 1- point body that will be stretched out.

Click the Show Slicing button at the bottom of the central panel.

The Start Slicing button
The Start Slicing button

Now all you have to do is click Start Slicing on each of the two images, followed by the Slice Horizontally button:

The Slice Horizontally button
The Slice Horizontally button

You should end up with something like this for each of the button sizes:

After slicing
After slicing

Each image is cut into three parts: the caps on the end and a one-pixel area in the middle that is the stretchable part. Now when you use this image with a button or a UIImageView, it will automatically stretch itself to whatever size it needs to be.

Important: Do the above for both the 2x image and the 3x image.

➤ Go back to the storyboard. For the $9.99 button, change Background to PriceButton.

If you see the image repeating, make sure that the button is only 24 points high, the same as the image height.

➤ Run the app and check out that button. Here’s a close-up of what it looks like:

The price button with the stretchable background image
The price button with the stretchable background image

The main reason you’re using a stretchable image here is that the text on the button may vary in size depending on the price of the item. So, you don’t know in advance how big the button needs to be. If your app has a lot of custom buttons, it’s worth making their images stretchable. That way you won’t have to re-do the images whenever you’re tweaking the sizes of the buttons.

The button could still look a little better, though — a black frame around dark green text doesn’t particularly please the eye. You could go into Photoshop and change the color of the image to match the text color, but there’s an easier method.

The tint color

The color of the button text comes from the global tint color. UIImage makes it very easy to make images appear in the same tint color.

➤ In the asset catalog, select the PriceButton set again and go to the Attribute inspector. Change Render As to Template Image.

When you set the “template” rendering mode on an image, UIKit removes the original colors from the image and paints the whole thing in the tint color.

The dark green tint color looks nice in the rest of the app, but for this pop-up it’s a bit too dark. You can change the tint color on a per-view basis; if that view has subviews the new tint color also applies to these subviews.

➤ In DetailViewController.swift, add the following line to viewDidLoad():

view.tintColor = UIColor(red: 20/255, green: 160/255, 
                        blue: 160/255, alpha: 1)

Note that you’re setting the new tintColor on view, not just on priceButton. That will apply the lighter tint color to the close button as well:

The buttons appear in the new tint color
The buttons appear in the new tint color

Much better, but there is still more to tweak. In the screenshot at the start of this section the pop-up view had rounded corners. You could use an image to make it look like that, but instead you’ll learn a neat little trick.

Rounded corner views

UIViews do their drawing using what’s known as a CALayer object. The CA prefix stands for Core Animation, which is the awesome framework that makes animations so easy on the iPhone. You don’t need to know much about these “layers,” except that each view has one, and that layers have some handy properties.

➤ Add the following line to viewDidLoad():

popupView.layer.cornerRadius = 10

You ask the Pop-up View for its layer and then set the corner radius of that layer to 10 points. And that’s all you need to do!

➤ Run the app. You have your rounded corners:

The pop-up now has rounded corners
The pop-up now has rounded corners

Tap gesture recognizer

The close button is pretty small, about 15 by 15 points. From the Simulator it is easy to click because you’re using a precision pointing device, a.k.a. the mouse. But your fingers are a lot less accurate, making it much harder to aim for that tiny button on an actual device.

That’s one reason why you should always test your apps on real devices and not just on the Simulator. Apple recommends that buttons always have a tap area of at least 44×44 points.

To make the app more user-friendly, you’ll also allow users to dismiss the pop-up by tapping anywhere outside it. The ideal tool for this job is a gesture recognizer.

➤ Add a new extension to DetailViewController.swift:

extension DetailViewController: UIGestureRecognizerDelegate {
  func gestureRecognizer(
       _ gestureRecognizer: UIGestureRecognizer, 
       shouldReceive touch: UITouch) -> Bool {
    return (touch.view === self.view)
  }
}

You only want to close the Detail screen when the user taps outside the pop-up, i.e. on the background. Any other taps should be ignored. That’s what this delegate method is for. It only returns true when the touch was on the background view — it will return false if the touch was inside the Pop-up View.

Note that you’re using the identity operator === to compare touch.view with self.view. You want to know whether both variables refer to the same object. This is different from using the == equality operator. That would check whether both variables refer to objects that are considered equal, even if they aren’t the same object.

Using == here would have worked too, but only because UIView treats == and === the same. But not all objects do, so be careful!

➤ Add the following lines to viewDidLoad():

let gestureRecognizer = UITapGestureRecognizer(target: self, 
                                   action: #selector(close))
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)

This creates a new gesture recognizer that listens to taps anywhere in this view controller and calls the close() method in response.

➤ Try it out. You can now dismiss the pop-up by tapping anywhere outside the white pop-up area. That’s a common thing that users expect to be able to do, and it was easy enough to add to the app!

Showing data in the pop-up

Now that the app can show this pop-up after a tap on a search result, you should put the name, genre and price from the selected product in the pop-up.

Exercise: Try to do this by yourself. It’s not very different from what you’ve done in the previous apps!

There is more than one way to pull this off. One common way is to pass the SearchResult object to the DetailViewController.

Displaying selected item information in pop-up

➤ Add a property to DetailViewController.swift to store the passed in object reference:

var searchResult: SearchResult!

As usual, this is an implicitly-unwrapped optional because you won’t know what its value will be until the segue is performed. It is nil in the mean time.

➤ Also add a new method, updateUI():

// MARK:- Helper Methods
func updateUI() {
  nameLabel.text = searchResult.name
  
  if searchResult.artist.isEmpty {
    artistNameLabel.text = "Unknown"
  } else {
    artistNameLabel.text = searchResult.artist
  }
  kindLabel.text = searchResult.type
  genreLabel.text = searchResult.genre
}

That looks very similar to what you did in SearchResultCell. The logic for setting the text on the labels has its own method, updateUI(), because that is cleaner than stuffing everything into viewDidLoad().

➤ Add a call to the new method to the end of viewDidLoad():

override func viewDidLoad() {
  . . .
  if searchResult != nil {
    updateUI()
  }
}

The if != nil check is a defensive measure, just in case the developer forgets to fill in searchResult on the segue.

Note: You can also write the above check as if let _ = searchResult to unwrap the optional. Because you’re not actually using the unwrapped value for anything, you use the _ wildcard symbol.

The Detail pop-up is launched with a segue triggered from SearchViewController’s tableView(_:didSelectRowAt:). You’ll have to add a prepare(for:sender:) method to configure the DetailViewController when the segue happens.

➤ Add this method to SearchViewController.swift:

// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
 if segue.identifier == "ShowDetail" {
   let detailViewController = segue.destination 
                              as! DetailViewController
   let indexPath = sender as! IndexPath
   let searchResult = searchResults[indexPath.row]
   detailViewController.searchResult = searchResult
 }
}

This should hold no big surprises for you. When didSelectRowAt starts the segue, it sends along the index-path of the selected row.

That lets you find the SearchResult object and pass it on to DetailViewController.

➤ Try it out. All right, now you’re getting somewhere!

The pop-up with filled-in data
The pop-up with filled-in data

Showing the price

You still need to show the price for the item and the correct currency.

➤ Add the following code to the end of updateUI() in DetailViewController.swift:

// Show price
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = searchResult.currency

let priceText: String
if searchResult.price == 0 {
  priceText = "Free"
} else if let text = formatter.string(
          from: searchResult.price as NSNumber) {
  priceText = text
} else {
  priceText = ""
}

priceButton.setTitle(priceText, for: .normal)

You’ve used DateFormatter previously to turn a Date object into human-readable text. Here you use NumberFormatter to do the same thing for numbers.

Previously, you’ve turned numbers into text using string interpolation \(…) and String(format:) with the %f or %d format specifier. However, in this case you’re not dealing with regular numbers but with money in a certain currency.

There are different rules for displaying various currencies, especially if you take the user’s language and country settings into consideration. You could program all of these rules yourself — which is a lot of effort — or, choose to ignore them. Fortunately, you don’t have to make that tradeoff because you have NumberFormatter to do all the heavy lifting for you.

You simply tell the NumberFormatter that you want to display a currency value and what the currency code is. That currency code comes from the web service and is something like “USD” or “EUR.” NumberFormatter will insert the proper symbol, such as $ or € or ¥, and format the monetary amount according to the user’s regional settings.

There’s one caveat: if you’re not feeding NumberFormatter an actual number, it cannot do the conversion. That’s why string(from:) returns an optional that you need to unwrap.

➤ Run the app and see if you can find some good deals.

Sometimes, you might see this, or something similar:

The price doesn’t fit into the button
The price doesn’t fit into the button

When you designed the storyboard, you made this button 68 points wide. You didn’t put any constraints on it, so Xcode gave it an automatic constraint that always forces the button to be 68 points wide, no more, no less.

But buttons, like labels, are perfectly able to determine what their ideal size is based on the amount of text they contain. That’s called their intrinsic content size.

➤ Open the storyboard and with the price button selected, click the Add New Constraints button. Add spacing constraints for the right and the bottom, both 8 points in size. Also add a 24 point Height constraint.

To recap, you have set the following constraints on the button:

  • Fixed height of 24 points. That is necessary because the background image is 24 points tall.
  • Pinned to the right edge of the pop-up with a distance of 8 points. When the button needs to grow to accommodate larger prices, it will extend towards the left.
  • Pinned to the bottom of the pop-up, also with a distance of 8 points.
  • There is no constraint for the width. That means the button will use its intrinsic width — the larger the text, the wider the button. And that’s exactly what you want to happen here. ➤ Run the app again and pick an expensive product — something with a price over $9.99; e-books are a good category for this.

The button is a little cramped
The button is a little cramped

That’s better, but the text is now right up against the button border. You can fix this by setting “content edge insets” for the button.

➤ Go to the Size inspector and find where it says Content Insets. Change Left and Right to 6.

Changing the content edge insets of the button
Changing the content edge insets of the button

This adds 6 points of padding on the left and right sides of the button.

➤ Run the app; now the price button should finally look good:

That price button looks so good you almost want to tap it!
That price button looks so good you almost want to tap it!

Note: After you added spacing constraints for the price button, you might have noticed that you started getting an additional Xcode warning saying “Leading constraint is missing, which may cause overlapping with other views.”

If you think about it, this makes sense since there is no leading constraint for the price button and if you were to add a new button to the left of the price button, you do run the risk of the price button accidentally expanding enough to overlap that hypothetical button. In this particular instance, it is not strictly necessary to do anything since there won’t be any other buttons for the price button to overlap. But if you wanted to remove the compiler warning, all you need to do is to add a leading constraint for the price button.

Navigating to the product page on iTunes

Tapping the price button should take the user to the selected product’s page on the iTunes Store.

➤ Add the following method to DetailViewController.swift:

@IBAction func openInStore() {
  if let url = URL(string: searchResult.storeURL) {
    UIApplication.shared.open(url, options: [:], 
                          completionHandler: nil)
  }
}

➤ Connect the openInStore action to the button’s Touch Up Inside event in the storyboard.

That’s all you have to do. The web service returns a URL for the product page. You simply tell the UIApplication object to open this URL. iOS will now figure out what sort of URL it is and launch the proper app in response — iTunes Store, App Store, or Mobile Safari. On the Simulator you’ll probably receive an error message that the URL could not be opened — try it on a device instead.

Note: You haven’t used UIApplication before, but every app has a UIApplication object and it handles application-wide functionality. You won’t directly use UIApplication a lot, except for special features such as opening URLs. Instead, most of the time you deal with UIApplication through your AppDelegate class, which — as you can guess from its name — is the delegate for UIApplication.

Loading artwork

For the Detail pop-up, you need to display a slightly larger, more detailed image than the one from the table view cell. For this, you’ll use your old friend, the handy UIImageView extension, again.

➤ First add a new instance variable to DetailViewController.swift. This is necessary to cancel the download task:

var downloadTask: URLSessionDownloadTask?

➤ Then add the following line to updateUI():

// Get image
if let largeURL = URL(string: searchResult.imageLarge) {
  downloadTask = artworkImageView.loadImage(url: largeURL)
}

This is the same thing you did in SearchResultCell, except that you use the other artwork URL — 100×100 pixels — and no placeholder image.

It’s a good idea to cancel the image download if the user closes the pop-up before the image has been downloaded completely.

➤ To do that, add a deinit method:

deinit {
  print("deinit \(self)")
  downloadTask?.cancel()
}

Remember that deinit is called whenever the object instance is deallocated and its memory is reclaimed. That happens after the user closes the DetailViewController and the animation to remove it from the screen has completed. If the download task is not done by then, you cancel it.

➤ Try it out!

The pop-up now shows the artwork image
The pop-up now shows the artwork image

Did you see the print() from deinit after closing the pop-up? It’s always a good idea to log a message when you’re first trying out a new deinit method, to see if it really works. If you don’t see that print(), it means deinit is never called, and you may have an ownership cycle somewhere keeping your object alive longer than intended. This is why you used [weak self] in the closure from the UIImageView extension, to break any such ownership cycles.

➤ This is a good time to commit the changes.

You can find the project files for this chaper under 37 – The Detail 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.