The Navigator class’s single purpose is to facilitate navigating (push, present modal, popover, and child/container) from one view controller to another from outside of those view controllers.
A project created with Xcode automatically marks the AppDelegate
as the app’s entry point with @UIApplicationMain
. The problem is that it also binds the storyboard defined by the Main Interface setting to the AppDelegate
’s UIWindow
member variable. The consequence of this is that the root navigation controller used for all navigation now resides at AppDelegate
’s window?.rootViewController?.navigationController
property. Rather than accessing this member variable navigation is often performed from within a view controller e.g. viewController.push(destinationViewController, animated: true)
. Apple’s View Controller Programming Guide for iOS states:
• The shouldPerformSegueWithIdentifier:sender: method gives you an opportunity to prevent a segue from happening. Returning NO from this method causes the segue to fail quietly but does not prevent other actions from happening. For example, a tap in a table row still causes the table to call any relevant delegate methods.
• The prepareForSegue:sender: method of the source view controller lets you pass data from the source view controller to the destination view controller. The UIStoryboardSegue object passed to the method contains a reference to the destination view controller along with other segue-related information.
Inherent in this is:
- A view controller have logic to decide the next view controller.
- A view controller have data to pass to the next view controller.
- A view controller have knowledge of the next view controller e.g. it’s type and delegate requirements.
- Data fetching becomes tied to a view controller's life cycle events e.g.
viewWillAppear
.
Clear the Main Interface setting and remove the AppDelegate
’s UIWindow
member variable. Create an independent object to create a UIWindow
, UINavigationController
, and UIViewController
and store the reference to the root navigation controller. This object can now perform all navigation from outside a UIViewController
and further, removes the need for segues. Navigator
is such an object that can be managed by popular architecture patterns e.g. AppController
, FlowController
, and Presenter
. This app uses an AppController
to control the navigation flow (using Navigator
) and interact with the data layer.
Navigator
provides a configuration callback that allows concerns to be set externally e.g.:
navigator.push() { viewController in
viewController.delegate = self
viewController.data = viewModel
}
This prevents UIViewController
from having knowledge of external concerns e.g. data layer, delegates, view model transformations, etc.
Navigator
also encapsulates re-occurring implementation details as well as pitfalls including:
- Optionally loads storyboards.
- Optionally wrap view controllers with
UINavigationController
so storyboards don't need them. - Consistent API for push, presentModal, popover, and add/remove child.
preconditionFailure
for Is Initial View Controller setting.preconditionFailure
for Custom Class setting.- Exposes completion blocks called when did load.
- Ensuring popovers implement
UIPopoverPresentationControllerDelegate
andmodalPresentationStyle
. - Handles popover anchors as
UIBarButtonItem
orUIView
. - Ensuring view controllers are fully formed with
loadViewIfNeeded()
. - Preventing sluggish behavior caused by empty view controllers.
- Container view controller details like matching
didMove(toParent)
andwillMove(toParent)
.