Improving the portability of UIViewControllers that require a UINavigationBar - ios

We often try to reuse our view controllers, whether they get pushed to a navigation controller or are presented. However, things can get a little hairy when an explicit UINavigationBar is part of the view controller's function design.
If we just set the view controller's navigation item, pushing to a navigation controller works as expected, but presenting results in no navigation bar at all. On the other hand, if we explicitly add and configure a UINavigationBar to the view, presenting works fine, but pushing results in double navigation bars. While we could specify the parent navigation controller's navigation bar to be hidden for that view, it creates a clumsy animated transition when pushing or popping that view controller.
(In a perfect world, I would imagine that the navigationBar property would be managed by UIViewController instead of UINavigationController. Alas, that's not the case, so here I am.)
What are some of the best practices people here have found to maintain the portability of view controllers requiring a UINavigationBar?

I've handled this in one of two ways:
I fall back to asserting viewDidAppear: that self.navigationController is non-nil. Push the responsibility of providing a UINavigationController to wrap your view controller instance to clients of the class.
Alternatively, you can embed a UINavigationBar instance and manage it yourself, just remember to hide the possible navigation bar from your containing navigation controller in viewWillAppear:.

Related

Does a navigation bar ever require constraints?

So far, I have two distinct uses of a navigation bar in my app: one produced by a Navigation Controller with a View Controller embedded in it and one added manually from the Object Library into a different View Controller presented modally (since a modally presented view apparently doesn't inherit the navigation controller of the view under it).
My question: do either of these navigation bars require constraints?
Yes, the one added by you, as it is managed by you. The other one is managed by UINavigationController. Also it wouldn't make sense for a modal controller to have the navigation bar of its presenter - you are showing an "extra" screen, not navigating the hierarchy. It is also worth mentioning that nothing stops you from presenting another UINavigationController modally with a separate navigation flow.

How to Share a View Across a Navigation Hierarchy with No Animation?

I have a simple Navigation View Hierarchy that has 2 views it goes between. I wanted a customized navigation bar, so I have the default one hidden, and I've implemented a Container View which is shared between the 2 views in the nav hierarchy.
Everything works as I want it to, except when I segue to the lower or higher view the top bar appears slides away and reappears on the new view. I would like it to appear stationary when I push or pop to other views in the hierarchy.
Is there an easy way to do this? Or should I delete my custom shared Container View and try to make this work with the Navigation Bar (which I have currently "hidden")?
I had to do this for a client once. The way we did it was, like you said, make an encompassing view controller that housed a container view. Within this container view, we embedded a UINavigationController and would manually pop and push UIViewControllers to its navigation stack. Of course you want to hide the UINavigationController's nav bar.
It sounds like you sort of implemented this, but instead you just embedded a plain old view controller inside your custom navigation controller, and then segue to another view controller that is also embedded in the custom view controller? Ideally you want one instance of this custom nav controller with an embedded UINavigationController. I believe you will have to do all the view controller transitions programmatically.
Opinion: Personally, I would recommend against doing this. I believe that an app should feel like an extension of the OS it's on. A user should feel it's a part of their phone. Using the native navigation bar also decreases the level of effort a user is required to put forth to understand your app.
I know you're thinking "but it's just a nav bar" but we're talking about the same people that will potentially uninstall an app if it takes longer than 2.5s to load.
I wanted a customized navigation bar, so I have the default one hidden
That's your mistake. The way to get a customized navigation bar in a UINavigationController interface is to initialize it with init(navigationBarClass:toolbarClass:). Now the built-in navigation controller is using your navigation bar! And from there on, all will be well.
https://developer.apple.com/reference/uikit/uinavigationcontroller/1621866-init

UITAbBarController as Master in UISplitController and showDetail:

I have a storyboard with this situation;
The root view controller is a UISplitViewController with:
MASTER: a UITabBarController
0 ->UINavigationController -> ...other ViewControllers
1 ->UINavigationController -> ...other ViewControllers
DETAIL: a UINavigationController -> a DetailViewControler
With this hierarchy the segue showDetail from last viewcontroller in master to the Navigation Controller in Detail doesn't work because the Detail is presented Modally in a Collapsed environment instead of presenting it with a push.
I think this behavior comes from the Tab Bar Controller because it isn't a Container like a UINavigationController. In fact if i remove the tab bar and set a navigation as Master of Split View Controller it works like usual.
What can i do for using a tab bar like Master of Split View Controller and get the right behavior of showDetail segue in a collapsed environment?
P.S.: for right behavior i mean pushing the Detail in the Master Navigation Controller in a horizontal compact environment (iPhone 6 plus Portrait).
I solved this issue overriding these methods of the UISplitViewControllerDelegate and implementing inside them all the behavior i want from the SplitViewController:
primaryViewControllerForCollapsingSplitViewController
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController
primaryViewControllerForExpandingSplitViewController
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
I suggest you to take a look at UISplitViewController Documentation because it explains very well the behavior of the Split Controller:
UISplitViewController Documentation
You can find what you need here:
The split view controller performs collapse and expand transitions when its size class toggles between horizontally regular and horizontally compact. During these transitions, the split view controller changes how it displays its child view controllers. When changing from horizontally regular to horizontally compact, the split view controller collapses one view controller onto the other. When changing from horizontally compact back to horizontally regular, it expands the interface again and displays one or both of its child view controllers depending on the display mode.
When transitioning to a collapsed interface, the split view controller works with its delegate to manage the transition. At the end of a collapse transition, the split view controller normally shows only the content from its primary view controller. You can change this behavior by implementing the primaryViewControllerForCollapsingSplitViewController: method in your split view controller delegate. You might use that method to specify the secondary view controller or an entirely different view controller—perhaps one better suited for display in a horizontally compact environment. If you want to perform any additional adjustments of the view controllers and view hierarchy, you can also implement the splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: method in your delegate.
The expansion process reverses the collapsing process by asking the delegate to designate which view controller becomes the primary view controller and to give the delegate a chance to perform the transition itself. If you implement the delegate methods for collapsing your split view interface, you should also implement the primaryViewControllerForExpandingSplitViewController: and splitViewController:separateSecondaryViewControllerFromPrimaryViewController: methods for expanding that interface. If you do not implement any of the methods, the split view controller provides default behavior to handle the collapsing and expanding transitions.
For more information about the methods you use to manage the collapse and expand transitions, see UISplitViewControllerDelegate Protocol Reference.
Hope this can help you.
PS: sorry for bad english.
During a showDetail segue the Split View Controller checks if collapsed and calls showViewController on the primary, which in this case is the tab controller which doesn't implement this method so relies on the default view controller method which is to search up the hierachy to find on that does; in this case showViewController called on the Split View Controller which implements it by firs checking if collapsed then it calls presentViewController which is why you see it as presented modally.
To solve this you could subclass the Tab Controller, implement showViewController and call it on the selected view controller, which would be the current navigation controller.
You also need to handle the collapse and separate, one way would be to implement separateSecondaryViewControllerForSplitViewController in your tab subclass and forward that on to the nav controller child that contains the detail nav controller because that is what un-hides its nav bar and pops it off the stack. You wouldn't get that behaviour if you implemented the split controllers' separateSecondary delegate method

Adding Popover to current Navigation Controller hierarchy

I've seen a lot of other questions on here about adding a UINavigationBar to a UIPopoverController. All of the examples I've seen follow one of two patterns:
In the init or viewDidLoad method of the Popover subclass, you alloc-init a UINavigationBar directly, as suggested here. This method is a little hacky, and while it shows up nicely, if the popover is a UITableViewController, you have to mess with a bunch of things to make sure the navigation bar you just added doesn't overlap one of your cells.
Alternatively, a lot of post suggest creating a UINavigationController just before presenting the popover, as shown here.
With the second method, however, won't the popover be the only controller in the newly created navigation controller? And if my view that I'm presenting the popover from is itself already in a navigation controller, the popover will NOT be in that same navigation controller, correct? It seems to be that the more appropriate thing to do would be to add the popover being created as another controller in the navigation controller that already exists (and which the controller that presents the popover is already a part of). Is that possible? Or is there a reason why the navigation controller for the popover needs to be independent from the navigation controller for the presenting controller? Or am I totally missing something here?
You have many questions, young Skywalker. :)
Creating a UINavigationController and then embedding the controller you would like to present is the way to go.
Don't get confused by all the controllers involved here:
UIPopoverController is a construct that shows an existing UIViewController in an overlay like style. UIPopoverController itself even isn't a subclass of UIViewController. The name is misleading.
So UIPopoverController hosts another controller. In your case, we let it host a UINavigationController.
UINavigationController is a subclass of UIViewController. It is a container controller and can handle a stack of UIViewControllers.
On that stack we push one UIViewController: the one you want to display and garnish with a UINavigationBar. Since Mr. UINavigationController comes with a build in UINavigationBar, he's our friend.
There is no need to subclass UIPopoverController. You just keep one static reference to it around so you can dismiss the current open popover in case you want to present another.
It does not matter where you present the UIPopoverController from. It will always be a popover. Even if presented from an existing UINavigationController. Only if you use presentViewController: you will get different results depending on the controller you're presenting from (modal or pushed on top of the stack).
won't the popover be the only controller in the newly created navigation controller?
No, the popover will contain the navigation controller and the navigation controller will only contain its root view controller (which would otherwise have been added directly to the popover as its root).
You seem to be a little confused about the relationship between the popover and the popover root view controller...
the popover will NOT be in that same navigation controller, correct
Yes, correct. The popover is effectively a window floating above all other views
Or am I totally missing something here?
Maybe... The popover would usually be used for displaying something modal, transient and smaller than full screen size. Putting a navigation controller in the popover and adding views to it is the normal approach.
Adding a navigation bar to a popover isn't hacky. A navigation bar is just another regular view. That also means that using a UITableViewController with it, the navigation bar will overlap the table view, as the UITableViewController's view property just returns the controller's tableView property. If you want to add a navigation bar above a table view, without it overlapping the table view, use a regular UIViewController and add your navigation bar and table view the normal way. UITableViewController should only be used if your only view within that view controller is a table view.
Having said that, I do agree with others that just using a navigation controller without using its navigation features is the most common approach.

Can a UISplitViewController be the root controller in a UINavigationController?

Interface builder does not allow you to add a UISplitViewController as the root controller of a UINavigationController.
I've also tried programmatically creating the UINavigationController and setting its root view controller to be the UISplitViewController.
The result is an empty window with just the nav bar.
I've also tried a split view controller replacement, MGSplitViewController. It mostly works, except that within the split view controller, the master view is another UINavigationController. Its nav bar shows up too thick. Changing orientation and back clears it up.
I've been trying all sorts of different approaches to having a view that looks like a split view and other views that I switch between. I've tried within a tab view controller, writing my own controller to manage subviews of the window and having the split view as a managed view, and now the navigation controller. All attempts have had some issues. The most consistent issue is regarding the orientation of the view. My app is running in landscape mode and typically the child views think its still portrait.
Any ideas appreciated.
No.
The bottom line: a UISplitViewController must be the root view of an app (or perhaps more specifically, a window). It can not live inside a UINavigationController or anything else.
This is the case with the current SDK, and there's no guarantee that will change in future SDKs.
It seems strange to add a split view to a navigation stack. The master pane of a split view controller is generally a navigation controller, so (without knowing more about your design), I'd probably use that to control your navigation hierarchy.

Resources