Modal Segues

Of course, once we have something that works, our first thought as developers often is how to make it better. One obvious limitation of our app thus far is that it only loads a single default podcast feed. Since we already built the table with the expectation that there can be many feeds (one per table section), let’s add the ability to do that. Along the way, we’re going to see a very different kind of navigation.

In the Project Navigator on the left, add a new file. Use the Cocoa Touch Class template, make it another subclass of UIViewController, and call it AddPodcastViewController. Go ahead and delete the stubbed-out methods like viewDidLoad(), since we won’t need them.

Adding to the Navigation Bar

In Main.storyboard, grab the view controller icon (the plain white box in a yellow ball) from the Object Library, and drag it into the storyboard. It will be convenient to drop it above the episode list scene, since that’s the scene that will be showing it.

images/navigation/ib-bar-button-item-icon.png

So how are we going to get to this scene? Right now the episode list scene is mostly full from the table view. But the navigation bar actually provides a standard place to add additional functionality. Scroll down to the episode list scene, and find the Bar Button Item in the Object Library (it looks like a box that just says “Item”).

Drag the bar button item to the right side of the navigation bar in the episode list scene. A “well” to accept the drop will appear as you mouse over that part of the bar. Once we drop it, it just says “Item,” but we can change that. Select it and bring up the Attributes Inspector. By default, the button has a custom title that can be edited here, but there are a number of “System Items” that use common wording or icons across apps and localize in other languages as needed. Use the System Item pop-up to change its type to Add, which will change the bar button to a plus sign (+).

images/navigation/ib-navigation-bar-with-add-item.png

Going Modal

Like the table cells, we can Control-drag from this bar button item to another scene to create a segue to that scene. So do that; drag from the plus button in the navigation bar to the empty scene we just created. However, at the end of the drag, when the HUD pops up asking what kind of segue to create, choose Present Modally.

Go ahead and run the app again, and instead of choosing an episode, tap the add button. This segues to our new scene, but things are different: the new scene comes in from the bottom rather than the left. Also, it does not have a back button. In fact, we are stuck here and need to click Stop in Xcode to get out.

So what’s the point? It’s that a modal segue is different; a view controller presented modally must somehow dismiss itself, usually by providing a button like Done or Cancel. The reason we have modals is that sometimes we need to stop the current navigation and compel the user to take an action before continuing.

Building the Modal Scene

OK, let’s get some contents in the new scene, particularly including a button that will let us get out. Select the new scene’s view controller, and in the Identity Inspector (3), set its class to the AddPodcastViewController we recently created.

This can be a very simple scene, so we can use a nice trick to make our layout simpler. Add the following elements to the scene, top to bottom, with no regard for autolayout constraints.

images/navigation/ib-text-field-icon.png
  1. A label with text “Add Podcast” in a large font

  2. Another label with text “Enter the URL of the new feed” in a large font

  3. A text field icon (the rounded rectangle icon that says “Text”) and Placeholder text https://example.com/feed.rss

  4. A button, titled “Done”

images/navigation/ib-stack-icon-button.png

Here’s where we save ourselves ten minutes of fiddling with auto layout. If we are OK with these items just appearing horizontally centered, with an equal vertical distance between each, then we can use a stack view, a superview that performs this sort of very simple layout. Select all four subviews with Shift-click, Command-click, or by drawing a box around them. Use the menu item EditorEmbed In…Stack View, or the stack view layout button to the immediate left of the autolayout Pin pop-up button.

The previously cut-off labels immediately expand to their proper size, but the layout isn’t quite right, because the stack view itself has no layout constraints. Select the stack view from the Document Outline on the left, bring up the Pin menu, and give it top, left, and right constraints of 0, then use the Update Frames button to fix up the layout.

images/navigation/ib-stack-view-attributes.png

Almost done. It just looks a little off because everything is left-aligned and spaced tightly. With the stack view still selected, visit the Attributes Inspector and set the Alignment to Center and Spacing to 10. This should clean up the layout nicely, and look like the following figure.

images/navigation/ib-add-podcast-scene-layout.png

OK, now that we’re laid out, let’s deal with functionality. Clearly, we need an outlet to the text field, so that our code can retrieve whatever the user types into it. Bring up the Assistant Editor and Control-drag an outlet over to AddPodcastViewController.swift. Call the outlet urlField.

 @IBOutlet​ ​var​ urlField: ​UITextField​!

Exit Segues

It’s tempting at this point to think we will connect an action to the Done button, and it will be responsible for both reading the value from the text field and dismissing the modal somehow. Actually, there’s a better and somewhat more interesting way to get out of modal presentations.

Hover over the third icon on the title bar atop this scene. A pop-over appears that says “Exit”; this represents a drop target for exit segues, which, instead of going to a specified scene, go back to a scene we’ve previously been in.

In fact, we’re not limited to going back only to the previous scene. We can go back anywhere in our previous navigation. To make this work, we have to indicate where we want to go. We do that by creating an unwind action. This is a method in a previous scene that serves as our destination.

Since we want to go back to the EpisodeListViewController, switch over to that class and write the following method:

 @IBAction​ ​func​ unwindToEpisodeList(_ segue: ​UIStoryboardSegue​) {
 }

That’s right; this method is completely empty, for now at least. All that matters is that it exists and adheres to a specific signature: it must have the @IBAction annotation, and it must take one argument of type UIStoryboardSegue.

Back in the storyboard, Control-drag from the Done button up to the Exit icon. When you release, a HUD menu appears listing all known unwind actions; unwindToEpisodeList will be one of them, so choose that.

images/navigation/ib-connect-exit-segue.png

This new exit segue gets us unstuck. Run the app again, tap the add button, and once the modal slides in, tap Done. The modal scene slides back out the bottom where it came from, and we’re back at the episode list.

Collecting Data on an Exit

This is great, but we do want our “add podcast” scene to, you know, actually add podcasts. So this is our final task: figuring out where and how to do it.

We have a couple of options. Since the exit segue is a genuine segue, prepare(for:sender:) is called on the AddPodcastViewController prior to performing the exit, so we have an opportunity to do some work there.

But then again, we just put a method in EpisodeListViewController as the unwind action, and this does more than give the exit segue somewhere to go; it actually gets executed as part of the unwind segue. Better yet, it passes in the UIStoryboardSegue object, so we can get a reference to the scene we’re exiting, which will be the sourceViewController of the exit segue.

So rewrite unwindToEpisodeList() like this:

1: @IBAction​ ​func​ unwindToEpisodeList(_ segue: ​UIStoryboardSegue​) {
if​ ​let​ addPodcastVC = segue.source ​as?​ ​AddPodcastViewController​,
let​ urlText = addPodcastVC.urlField.text,
let​ url = ​URL​(string: urlText) {
5: let​ parser = ​PodcastFeedParser​(contentsOf: url)
parser.onParserFinished = { [​weak​ ​self​] ​in
if​ ​let​ feed = parser.currentFeed {
self​?.feeds.append(feed)
}
10:  }
}
}

This starts with a big if let. We ask for the source view controller as an AddPodcastViewController (line 2), and the contents of its urlField (line 3), and a URL of that text (line 4). If all that works, then we create a new PodcastFeedParser and add any results to our feeds data model, exactly as we’ve done before (lines 5 through 10).

images/navigation/simulator-core-intuition-podcast-episodes.png

Run again, and click the add button. Type in the RSS URL of a favorite podcast. To speed this up, in the simulator, you can use EditPaste to paste the contents of your Mac’s clipboard into the simulated iPhone’s clipboard, and then long-press on the text field to bring up the iOS Paste menu. For this example, we can use the feed of our friends at the Core Intuition podcast (https://coreint.org), whose feed is http://www.coreint.org/podcast.xml. Remember to type the entire URL, including the http:// part, since we’ve done nothing to sanitize or clean up the input. Click Done and scroll down. You should find the episodes of the newly added podcast in their own section of the table, as shown in the figure.

Like our earlier feed from CocoaConf, the Core Intuition podcast doesn’t have distinct images for each episode, so our code reuses the feed’s main show logo for every episode. Even then, it’s a good visual cue to help tell one podcast from another in our list, something users will appreciate.

So now, let’s bask in the glow of our fully functional podcast client application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset