We refactor some view controller logic while working on our upcoming tvOS app. Instead of letting multiple view controllers manage the navigation bar's state individually, we pull this code out and unify the logic in one place.
0:06 Today we're going to work on our tvOS app, which we intend to release
very early instead of spending a lot of time polishing it.
Toggling the Navigation Bar
0:31 Our current version has a bug that we need to fix before refactoring
the code. When we select an episode from the episode list and open it, we see a
double title; the navigation bar shows the title of the view controller, and
this blocks a title label that we added to the view. We should hide the
navigation bar in the episode view controller. Or it's the other way around
actually: we only want to see the navigation bar when we're in the episode list.
1:06 Let's take a look at the EpisodeDetailViewController. We can hide
the navigation bar by overriding viewWillAppear:
2:01 That fixes part of the problem, but when we go back to the episode
list, the navigation bar stays hidden. We need to also modify the
EpisodeListViewController to unhide the navigation bar, so we copy the code
and replace true with false:
2:45 That single piece of code, which toggles the navigation bar, is the
only reason we had to write a specific view controller for the episode list. The
rest of the functionality is covered by a generic table view controller from
which is wrapped inside this custom view controller.
3:19 We could've considered other solutions, like adding a property to
the generic table view controller that manages the navigation bar, using a
callback somehow, or subclassing the table view controller. But the solution we
chose gives us another problem. Because we have to hide and show the navigation
bar in different places, the code is smeared out over different classes. This
makes it quite fragile and less easy to use one of these view controllers in a
different place. It would be better if we have one place — ideally outside the
view controller — where we can manage the visibility of the navigation bar. From
this central place, we can decide to only show the navigation bar in the root
view controller of the hierarchy.
4:13 The way we architected this app is very similar to what we did in
an early episode called Connecting View
Instead of using storyboards and segues, we have a class, App, that controls
the flow of view controllers. The individual view controllers should have no
knowledge about where they are in the application — for example, whether they're
in a navigation controller or not. We violated this rule by making our view
controllers deal with the navigation bar. In our current architecture, the App
class would be the place from which to control the navigation bar visibility,
not the view controllers.
5:17 Let's fix that! We'll use a delegate that gets notified when we
navigate between view controllers and controls the navigation bar visibility. We
make this delegate conform to UINavigationControllerDelegate, and we implement
the protocol method navigationController(willShow:) to perform a specific
task. Now, the navigation bar is only visible when the root view controller is
about to be
8:49 This delegate replaces the two pieces of code with viewWillAppear
from earlier, so they can be removed from EpisodesListViewController and
EpisodeDetailViewController. This makes these view controllers less smart
about where they are used inside our architecture, and that's a good thing.
Generics and Reusability
9:36 Let's move on to the next problem. We're using some specific code
to constrain the table view to the readable content guide instead of it
stretching out over the entire width of the screen. Again, we did this by
wrapping the table view controller and replicating all of its API in the wrapper
view controller. The only important part of the wrapper is the line where we
constrain the view's edges. We should do better than this. Instead of creating
an extra class, we can use the generic table view controller, along with a
generic helper class that constrains the view to the readable content guide.
12:09 We rename the EpisodesListViewController to a more generic
ReadableContentViewController that's initialized with a view controller. It
adds this view controller to its children and copies the child's title. Then we
remove all the table view code:
13:41 Now we have to use this generic class instead of the specific
EpisodesListViewController. Where our Screens class creates the episodes
list table view controller, we wrap a generic table view controller in a
14:44 To handle the child view controller's title more correctly, we
should observe it; that way, we can update the parent's title whenever the
child's title changes. But our current method works for now.
15:00 We're making nice improvements toward a more flexible
architecture. We removed all the boilerplate code by using our generic view
controllers again and by abstracting away all the specifics.
15:58 A next step could be to improve EpisodeNavigationDelegate. We
could continue the pattern and make this delegate more generic too, perhaps by
passing in a closure to replace the single line that actually controls the
navigation bar. All we'd have left in the generic delegate class is boilerplate
code, which is reusable for creating other types of navigation controllers.
16:40 So far, we're already much happier with our code.