Navigation Controllers

Let’s take stock of where we are with our app. We have two scenes—the episodes list and the media player—that currently have no relationship to one another. The only reason either works is that we reset the “Is initial view controller” setting in the AppDelegate. But what we want, of course, is for selecting an episode in the table to bring up the player for that episode. We need to navigate from one scene to the other.

The most common tool for achieving this on iOS is the UINavigationController, a view controller that manages a stack of other view controllers. Typically, it starts with a “root” view controller, and then when we want to show another view controller, we put the new one on the top stack. When we want to go back, we remove (or “pop”) the top view controller.

All the while, the navigation controller just knows to show the top view controller. Optionally, it has its own minimal UI: a navigation bar at the top of the screen with a back button, a title, and a right-hand button that may or may not be present. As an iOS user, you’ve surely seen this navigation bar many, many times before.

Adding a Navigation Controller

So our task now is to bring a navigation controller into our app. It will start with the episode list as its root view controller, and when we tap a row, we’ll want to navigate to the player scene.

images/navigation/ib-navigation-controller-icon.png

Go to Main.storyboard and zoom out enough to clearly see both scenes. We are going to add a navigation controller to the storyboard and connect it to the episode list scene as its root view controller. In the Object Library (3), find the navigation controller icon, which looks like the blue left-pointing “back” arrow in a yellow ball. Start dragging this into the storyboard, and don’t freak out that during the drag it shows two scenes. Drop it to the left of the episode list scene.

Interface Builder gives us two scenes: a navigation controller on the left, connected to a root scene with a table view. This is meant as a convenience when we’re starting from scratch, but we already have the scene we want to use as our root. Select this root scene’s view controller icon, and press backspace (or do EditDelete) to delete the scene.

Now we have a loose navigation controller scene without a root view controller. Select its yellow-ball icon and look at its connections (either Control-click to show the HUD, or visit the Connections Inspector, 6). Find the connection called “root view controller,” and drag from its circle to the episode list scene to use our scene as the new root view controller for the navigation controller.

images/navigation/ib-connect-root-vc.png

Making this connection actually makes several changes in our episode list scene. The table shifts down about 80 points, and a new Navigation Item icon appears in the Document Outline. Part of this is good change, and part is Xcode making a mistake.

Select the Navigation Item icon, and bring up Attributes Inspector (4). It only has three attributes: strings for a title, prompt, and back button. Enter Podcasts for the title. This causes the word “Podcasts” to appear at the top-center of the scene—the idea of the navigation item is that the navigation controller will use the title from this navigation item (if present) in the navigation bar. Each scene can have its own navigation item, so it’s easy to change titles for different scenes in a navigation.

Now to fix Xcode’s mistake. Select the episode list view controller (the yellow ball in its scene) and visit its Attributes Inspector. There’s a check box here for Adjust Scroll View Insets. That’s a setting that would be good if we wanted the table to go under the navigation bar, but if we wanted that we would have set the table’s top constraint to its superview, and not to the Top Layout Guide, which accounts for space taken up by the navigation bar if it’s there. So uncheck this check box. Now we should have a scene with a navigation bar showing the Podcasts title, and no extra space between the bar and the prototype table cell, as shown here.

images/navigation/ib-navigation-bar.png

There’s also an inconsistency in our storyboard. The episode scene is both the root view controller of the navigation controller scene, and it’s the initial view controller, as indicated by the arrow pointing into it. We need to make the navigation controller be the initial VC. Select the navigation controller’s icon, bring up its Attributes Inspector, and select “Is initial view controller.” The relationship between the three scenes should now look like the following figure:

images/navigation/ib-all-scenes-no-segue.png

We can run the app now, but we get an empty table with the Podcasts title on the navigation bar. Where did our table contents go? They didn’t get populated because our code in AppDelegate expects the first view controller to be the EpisodeListViewController, and it isn’t anymore. Go to AppDelegate.swift and rewrite applicationDidBecomeActive as follows:

1: func​ applicationDidBecomeActive(_ application: ​UIApplication​) {
if​ ​let​ url = ​URL​(string: ​"http://cocoaconf.libsyn.com/rss"​),
let​ topNav = application.keyWindow?.rootViewController
as?​ ​UINavigationController​,
5: let​ episodeListVC = topNav.viewControllers.first
as?​ ​EpisodeListViewController​ {
let​ parser = ​PodcastFeedParser​(contentsOf: url)
parser.onParserFinished = { [​weak​ episodeListVC] ​in
if​ ​let​ feed = parser.currentFeed {
10:  episodeListVC?.feeds = [feed]
}
}
}
}

The changes here are just lines 3-6. On lines 3-4, we now expect the initial view controller to be a UINavigationController, which we’ll store as the local variable topNav. A navigation controller’s stack of view controllers is available as the array viewControllers, so lines 5-6 try to get the first member of this array as a EpisodeListViewController. With this change, our app starts working again, with the addition of the navigation bar at the top.

images/navigation/simulator-nav-bar-on-episode-scene.png

Awesome, our app is back! Now let’s start navigating.

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

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