37.
The Detail Pop-up
Written by Fahim Farook
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 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 popup: 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 Identity 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.
➤ Similarly, rename the previously added Search View Controller scene to Search.
➤ Set the Background color of the Detail scene’s view to the ArtistName color asset. That makes it easier to see what is going on in the next steps. If you recall, this color asset is 50% black in Light mode and 50% white in dark mode.
➤ Drag a new View into the scene. Using the Size inspector, make it 280 points wide and 280 high. Add Auto Layout constraints for width=280 and height=280 to ensure that the view stays at this size.
➤ Center the view in the scene by setting up horizontal and vertical centering Auto Layout constraints.
➤ Open the Asset Catalog and add a new Color Set via the + button at the bottom and name it DetailBG.
➤ Set the Any Appearance color to white, 95% opaque via the Color Panel and the Dark Appearance to black, 95% opaque.
Note: It’s easiest to use the Color Palettes tab of the color panel to select the white and black colors but if you want to enter RGB values, remember that white is red=255, green=255, blue=255 and black is red=0, green=0, blue=0.
➤ Back in the storyboard, for the new view, in the Attributes inspector, change the Background color to the newly created DetailBG color asset. 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” in the Document Outline.
➤ Drag a Button into the scene and place it somewhere on the Pop-up View. In the Attributes inspector, change Image to xmark.circle.fill — this is another Symbol Image from Apple’s SF Symbols set.
➤ Remove the button’s text and place it in the top-right corner of the Pop-up View by adding new Auto Layout constraints for top = 8 and right = 8
➤ If the button’s Type now says Custom, change it back to System.
➤ The image should be the dark green you set as the AccentColor. If it isn’t, switch to the File inspector and change the Interface Builder Document - Global Tint to AccentColor. Now the button image should show the dark green you selected earlier.
➤ 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 (for Dark Appearance):
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.
Show and hide 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 handy 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.
The results aren’t too bad. But you’ll notice that the main results view is now inset a little and that the modal view looks a little strange under the Light appearance because you can’t clearly see the edges of the modal view and so it looks as if the background does not reach all the way to the top. It looks more like an accident rather than an intentional presentation style.
For the Dark appearance, the intention is more clear because the white background stands out clearly – especially the rounded corners of the modal view – against the darker background.
There are two possible solutions that immediately spring to mind:
-
Change the background color for the Light appearance so that the modal view is more evident.
This is the easier option since you’ll simply be changing a color value to make the contrast against the background better. But do remember that we are using this particular color asset, ArtistName, elsewhere as well. Of course, we could simply create a new color asset to get around that – so still not a big issue.
-
Change the presentation style of the modal view to make it full screen.
Modal views can have several different presentation styles. The one you see, where the parent view is slightly inset and the modal view has a card-style appearance, is the default style that you get, at least on an iPhone, when you don’t set a specific modal presentation style.
Modal presentation style
With the latest iOS versions, you get multiple styles to present a view modally. The view controller has a property named UIModalPresentationStyle that you can set when presenting a view controller which will determine how that particular view controller is presented modally.
While the modal presentation style property has been there since iOS 3.2, the number of available modal presentation styles keeps going up and is now at a whopping 11. We are not going to cover all of the available styles but we’ll go over some of the more important/useful ones.
-
pageSheet- This is the style that you saw in action when you presented the Detail view in your app. This shows a card-style appearance on iPhone and iPad. -
formSheet- This displays just likepageSheeton iPhone but on iPad it displays the detail view centered on the screen instead of showing it card-style. -
fullScreen- This style displays the detail view full screen, covering up the contents of the presenting parent view. Note that this style does not show the underlying parent view’s contents if you made your detail/child view have a transparent/semi-transparent background. -
overFullScreen- As the name implies, this again displays the detail view as a full screen view but this time, it shows the underlying view content if the detail view has a transparent/semi-transparent background. -
custom- This style allows you to create your own custom presentation controller and decide how you present the detail view via your custom code. -
blurOverFullScreen- You might see this style listed in the documentation and since it seems to imply that the underlying content is blurred – which it does – that this is a cool effect to have in your app. Unfortunately, this is a style is only available for tvOS – bummer!
There are a few more modal presentation styles but the above cover the most important For our purposes, if we wanted to go full screen, then the one we’d want to use would be overFullScreen.
Exercise: How would you set the modal presentation style so that the Detail view is presented with the
overFullScreenmodal presentation style.
Answer: The best place to do this would be via prepare(for:sender:) in SearchViewController since SearchViewController is the presenting view. You simply have to add the following code to SearchViewController.swift:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowDetail" {
segue.destination.modalPresentationStyle = .overFullScreen
}
}
Try it and see. In fact, try out some of the other modal presentation styles as well to see how each one looks and performs. Once you are done, don’t forget to remove this code. But setting the presentation style via code is not the only way to go. You can also set the presentation style via storyboard.
➤ Open the storyboard and select the Detail view controller. Switch to the Attributes inspector and check out the Presentation dropdown:
You can simply set the presentation style to be used by a segue right there. As usual, there’s more than one way to do things in iOS :]
One thing to be aware of, while the overFullScreen style looks nice, it has one drawback over the pageSheet style we got by default – you can’t dismiss the modal view except by tapping the close button you added. But with the card-style modal, you can dismiss the modal also by swiping down from the top.
Personally, I prefer the card-style modal. So we’ll stick with that.
“But what about the modal view not looking quite right in Light mode?” I hear you asking. Well, we could play around with the colors a bit and get that right.
➤ The semi-transparent white looks good against the darker edges of the background. So, instead of using the color asset which switches colors for each type of appearance, you can simply set the background for the Detail view to white 50% by selecting a custom color instead of a color asset.
But that makes the white 95% container view look off because now in Light mode, it’s white-on-white.
➤ Since we’re using a color asset for the Pop-up View and the only issue is the color for Light appearance, simply change the DetailBG color for Any Appearance (in the asset catalog) to red=197, green=197, blue=197, opacity=95%.
Run the app and test under both Light and Dark appearances:
That looks much better, right?
➤ Also verify that both swiping down from the pop-up and the close button work to dismiss the pop-up.
Add 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 Detail view will look like this:
In the early days of iOS, to create a view like the above, you would have put in the controls positioned each control manually and then corrected everything as needed.
Then you got Auto Layout and you could specify how far away from the edge of the screen (and from each other) each control had to be. It made things easier, but you still had to set up a lot of constraints.
But then Apple introduced Stack Views.
Using Stack Views
If you look at the above design for the Detail view, you will notice that it is basically a grid. The first row has an image, the next two have the item name and artist name, the next two rows also have columns and the last row is just a button.
The UIStackView control is meant for just such layouts. It is a control which, unlike views, is has no drawing component – it’s purely a layout component. So, it is lighter on system resources since iOS does not have to draw all the stack views on screen.
This means that using multiple nested stack views to create a complicated grid layout does not impact the system as much as if you’d used nested views to do the same job.
➤ Drag a Vertical Stack View on to the pop-up view and pin it to the pop-up view with Auto Layout constraints where left=16, top=16, right=16, and bottom=16.
Note: Depending on how you placed the stack view, it is possible that your right constraint might end up pinning the stack view to the close button instead of the pop-up view. Check the Size inspector to make sure that this is not the case and if it is, change the right constraint so that the stack view has 8 points of trailing space to its superview.
➤ In the Attributes inspector, change the stack view’s Alignment to Fill and Distribution to Equal Spacing – this makes all contents of the stack view fill all the available space horizontally — because this is a vertical stack view — and equally distribute the spacing between each row.
➤ Drag a new Horizontal Stack View on to the existing stack view – check the Document Outline to make sure that the horizontal stack view is a child of the previous vertical stack view. This will be the main container for the Type and Genre values since you need a 2 x 2 grid. Name this view Grid in the Document Outline.
➤ Change the Spacing for the Horizontal Stack View to 8.
➤ Drag a new Vertical Stack View on to the new Grid stack view you just added. Change this one’s Spacing to 8 as well. This will be the first column in the grid.
We won’t add the second column in the grid just yet – I want to show you a different way to do that in a bit :]
➤ Next drag an Image View and put it into the main stack view so that it sits above the horizontal stack view. It’s probably easiest to drop the Image View on to the Document Outline to make sure that you get the positioning correct.
➤ Pin the Image View using Auto Layout constrains so that width=100, height=100. (The image might get red markers indicating that there are Auto Layout issues. Don’t worry about it for now - we’ll be fixing this soon.)
➤ Drag two Label controls below the Image View you just added and above the Horizontal Stack View. Tip: Remember that you can duplicate items by Alt/Option+dragging an existing item. This works in both the scene view and in the Document Outline.
➤ Change the Title of the first label to Name and set its font to System Bold 20. Set Autoshrink to Minimum Font Scale so the font can become smaller if necessary to fit as much text as possible.
➤ Set the Title of the second label to Artist Name.
➤ Option+drag the Artist Name label from the Document Outline into the Vertical Stack View inside the Grid stack view. This adds a label inside the Vertical Stack View.
➤ Repeat the above step to add another Label to the Vertical Stack View inside the Grid stack view.
➤ Now Option+drag the whole Vertical Stack View inside the Grid stack view below itself to create a second Vertical Stack View which already has two labels inside.
Yes, you can duplicate a container view including all its children this way! This way, you can build a complex layout much faster by creating one container view and then duplicating it.
➤ Change the Title of the two labels in the first Vertical Stack View to Type: and Genre:.
➤ Change the Title of the two labels in the second Vertical Stack View to Kind Value and Genre Value.
➤ Set the Color for the Type: and Genre: labels to the ArtistName asset color.
➤ Finally, drag a Button on to the Vertical Stack View where the Button is the last item in the stack – below the two Horizontal Stack Views.
➤ Change the Button’s Style to Default, Title to $9.99 and set the title font to System Bold 20.
Your Pop-up View should now look like this:
There are some issues:
- The image is not 100 x 100 but instead stretches from side-to-side.
- The Kind Value and Genre Value are too far away from their respective labels.
- The $9.99 button isn’t right-aligned.
All the issues are alignment issues of some kind. Each stack view has an alignment setting but we set the main vertical stack view’s alignment to Fill when we first created it. Any of the other alignment settings would put some controls in the right position but others would be out of place. Fill is the optimal one to use because it fills the whole area and allows us to position individual items as we want.
Exercise: Can you think of how to fix the alignment issues?
Answer: We simply embed the views that need a different alignment in another stack view and change the alignment on the new stack view.
Confused?
Let’s try it out.
➤ Select the Image View and click on the Embed In button at the bottom of the Interface Builder — it’s next to the Auto Layout buttons — and select Stack View.
You could also do the same thing by selecting Editor ▸ Embed In ▸ Stack View from the Xcode menu.
➤ Select the new stack view and set its Alignment to Center. Now the image view is centered and shows the correct size.
➤ Select the $9.99 button and embed it in another stack view and this time set the new stack view’s Alignment to Trailing.
That just leaves the spacing for the Kind Value and Genre Value labels.
It might be helpful to know what the underlying issue here is — when you have multiple items in a stack view and the stack view has its Alignment set to Fill, Auto Layout has to decide how to distribute the controls for each row (or column) so as to fill the entire width (or height). Auto Layout takes the Content Hugging Priority — which is a setting you can see for each control under the Size inspector — for the control into account to figure out the size for the control.
The Content Hugging Priority indicates how reluctant a control is to expand from its default size. The higher the priority, more unlikely a control is to expand from the default size and take up more space than it needs.
➤ Select the Type: label and set its Content Hugging Priority - Horizontal to 252 in the Size inspector.
➤ Do the same for the Genre: label — you don’t need this as the layout stands now, but it’s better to be safe.
Your layout now looks like this:
That looks much more like it!
Add the outlets
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.
Everything appears to work … until you try to close the pop-up using the Close button. That doesn’t work any longer.
Exercise: Do you have any idea why this might be?
Answer: This is because the Close button is now being covered by the main stack view – the vertical one. While stack views aren’t visual components which are drawn on screen, they do handle touch input. This is because each control on screen passes on touch events to its child controls and if the stack view did not handle touches, anything contained by it — such as our $9.99 button — would not react to touch events either.
The fix is simple, move the Close button further up the view hierarchy so that it sits above the stack view. You can do this easily via the Document Outline.
Once you do that, test again to make sure that you can now use the Close button again.
Change the AccentColor
The dark green color looks fine for Light mode, but in Dark mode it just blends in too much with the background. Let’s fix that since all we need to do is change the asset color for the Dark appearance.
➤ Select the AccentColor in the Asset Catalog.
Till now, you’ve had a single color for the AccentColor since we haven’t seen it in action much. But now that the AccentColor is visible on the Detail screen, it’s evident that there should be a lighter version in play for Dark mode.
➤ Select the color asset from the central screen – where it says Universal under the color – and in the Attributes inspector change Appearances to Any, Dark.
➤ Now select the Dark Appearance variant of the color and change it to red=60, green=130, blue=130.
➤ Save your changes and run the app again to make sure that everything works correctly.
Stretchable images
The $9.99 button would look good with a background image.
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.
And if you say here, “Hey, why don’t we use a Symbol Image?”, then that’s actually a good idea. If you want, you can try that and see how that works out. For some situations, that could be the right solution. But I want to tell you about another option – stretchable images.
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.
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 the Asset Catalog and add the PriceButton@2x.png and PriceButton@3x.png images from the Images folder from this app’s resources. There are other images in that folder but don’t add them yet – you’ll get to them later.
➤ Select the PriceButton image set.
If you take a look at the 2x image info, you will see that it is only 22 points wide. That means it has a 10-point cap on the left, a 10-point cap on the right, and a 2- point body that will be stretched out.
Select Editor ▸ Show Slicing from the Xcode menu.
Now all you have to do is click Start Slicing on each of the two images, followed by the Slice Horizontally button:
You should end up with something like this for each of the button sizes:
Each image is cut into three parts: the caps on the end and an 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 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 green text doesn’t particularly please the eye in Light mode. And in Dark mode, the border completely disappears!
What if we could set the AccentColor for the image, then it would look right for all appearances.
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 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.
That’s all you need to do, really, since the global tint color is already set to AccentColor. The background image will be automatically tinted to that color the next time you run the app.
But, there’s another tiny issue – the background image is way too close to the price text in the button. Is there perhaps a way to add some space before and after the text?
➤ Select the price button in the storyboard, switch to the Size inspector and set Button - Content Inset values to left=10 and right=10.
That looks much better, doesn’t it?
Rounded corner views
There is one more thing to tweak. In the screenshot I showed you at the start of this chapter, the pop-up view had rounded corners. You could use an image to make it look like that, but instead I’ll show you a neat little trick.
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() in DetailViewController.swift:
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:
Tap gesture recognizer
You have two ways to close the pop-up – the close button and swiping down from the top of the pop-up view. But the app would be even more user-friendly if you also allowed 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 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!
Show 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, but I like to do it by passing the SearchResult object to the DetailViewController.
Display 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 forgot to fill in searchResult on the segue.
Note: You can also write the above check as
if let _ = searchResultto 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!
Show 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 :].
Navigate 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. If it doesn’t work on the Simulator, try it on a device instead.
Note: You haven’t used
UIApplicationbefore, but every app has aUIApplicationobject and it handles application-wide functionality. You won’t directly useUIApplicationa lot, except for special features such as opening URLs. Instead, most of the time you deal withUIApplicationthrough yourAppDelegateclass, which — as you can guess from its name — is the delegate forUIApplication.
Load 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!
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 chapter under 37-Detail-popup in the Source Code folder.