UINavigationBar subclass detect when navigation item title changes - ios

I am using a custom subclass of UINavigationBar with my UINavigationController by using: initWithNavigationBarClass:toolbarClass:.
Needless to say, in this subclass I need to monitor and update an additional interface element contained within my navigation bar subclass when the title of the presented navigation item changes. I have considered using KVO for this and observing the navigation items title property and updating as a when that changes but I was wondering if perhaps there was a better way. Would subclassing UINavigationController perhaps give me some more leeway to take control or monitor this function? I am interested to hear your thoughts on this one and whether or not there is a better way to go about this?
To elaborate: I am overiding some methods for example layoutSubviews. In this method when the navigation item has just been pushed onto the stack it doesn't yet have a title. This is because the title is set in the view controllers viewDidLoad which won't be executed until the view is loaded which has not happened yet. As such even though the navigation item has been pushed the title is still nil until it is changed in the presented view controllers viewDidLoad. This is what I want to be notified of when the navigation item is changed after it has been pushed onto the navigation stack.

Related

Are UINavigationControllers equivalent to having manually wiring each VC with a navbar?

If I have the storyboard in the form of (where the arrows are segues)
UINavigationController -> ViewControllerA -> ViewControllerB
Would that be basically more or less equivalent to
ViewControllerA -> ViewControllerB
(NavigationBar) (NavigationBar)
if I manually wire each NavigationBar up to Button Bar Items with event listeners attached to unwind segues?
Or does UINavigationController offer something more than that?
UINavigationController is what is known as a container view-controller: it takes a bunch of other view-controllers and manages how their views are presented on the screen. UISplitViewController is another example of a container view-controller.
In the case of UINC, it:
Allows pushing a new "top" controller, animating it with a left-to-right/right-to-left animation depending on locale
Remembers the stack of previous top controllers, allowing you to pop back to them
Adds a UINavigationBar view above the top controller's view so the user can pop back by themselves (you can disable this)
Sets a layoutMargin on the top controller's view, so it can adjust content to not underlap the bar
Provides an edge-swipe gesture so the user can interactively pop to previous controllers (slowly peel the top sheet back)
For more information, including about how to create your own container view-controllers, see Apple's documentation on the subject: https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
There are some differences.
One that I have noticed, a UINavigationController will handle correctly putting the navigation bar in the right place for iPhone X vs other models (It will make the height larger so it goes into the wedge of the of screen, while just a nav bar will remain its standard height).
If you just put a nav bar on each UIViewController, you are going to have to check on each view controller if you need to update the bar size.
Basically the navigationController provides you many thing that you might use: A stack of UIViewControllers, a navigation bar, a toolbar, delegate methods, show/pop animations, etc. Doing all that by hand will be not too appropriate and a little bit messy. I suggest you to take a look to the Appleā€™s documentation for UINavigationController. There you will get a much better understanding of all the capabilities and methods that this class provides you.

Improving the portability of UIViewControllers that require a UINavigationBar

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:.

UINavigationBar inside UITableViewController

I have some static cells that I want to display, so I have a UITableViewController. There is also a NavigationBar in this scene that contains some buttons at the top. The setup looks like this:
If I had a UIViewController that contained a UITableView in it, the setup would look like:
So, the question is:
Why does the Navigation Bar have to be embedded inside the UITableView when using a UITableViewController? (I have tried putting it elsewhere but IB won't let me)
I know that UITableView is a subclass of UIView, but is it OK that the top level element in the hierarchy is not a View (but a TableView)?
Thanks.
You shouldn't be placing your UINavigationBar in your UITableView. You should be putting your UITableViewController in a UINavigationController, because that will provide a UINavigationBar for you.
So if you select your UITableViewController in the storyboard, you can choose Embed In -> Navigation Controller from the Editor menu. This would be the proper way to do it.
There are two ways to use a UINavigationBar in iOS:
Embedded inside a UINavigationController (recommended)
As a standalone object
For your particular situation, I'd recommend that you put your UITableViewController as the rootViewController of a UINavigationController. That way you automatically get a navigation bar which you can customize according to your needs. In a typical user experience, when you tap some of your table view rows a new view controller will be pushed onto the navigation stack, so you'll probably end up needing a navigation controller anyway.
What if you decide to use a navigation bar as a standalone object? This is perfectly fine, you can use it inside a view hierarchy as an ordinary UIView, but you'll need to create another object that implements the UINavigationBarDelegate protocol and set it as the delegate property of your navigation bar. If you use a UINavigationController the delegate is already set and configured for you. You also need to add/remove navigation items (instances of UINavigationItem) to your navigation bar by using the pushNavigationItem:animated: and popNavigationItemAnimated: methods.
And about your question on the view hierarchy, you can use a UITableView anywhere a UIView is required. The only caveat is that a UITableView is a view hierarchy on its own and that may restrict your layout a little bit.
The way a UITableViewController works, is its root view is a UITableView. So there is no way to put the UINavigationBar anywhere other than in the UITableView.
I tend never to use a UITableViewController as it doesn't really give you much.
If you particularly want to use the UITableViewController, I don't believe that there is any real problem in having the navigation bar within the table view. You just need to make sure that you set the contentInset on the table view such that the navigation bar doesn't block the content. Though it seems a bit backward to do it this way.
My recommendation would be to just use a normal UIViewController with a navigation bar and a table view.
If you actually need functional navigation, you need to put your UITableViewController within a UINavigationController.
Hope this helps :)
Let me know if anything is still unclear.

Sharing a UINavigationItem among different ViewControllers

I have a requirement to implement an app that has a navigation bar like bar at the top of numerous screens.
It has an icon on the left, some text, and some buttons on the right thus these would map well to a navigation item's left bar button items, title view, and right bar button icons.
However on most of the screens the content of the bar remains the same - i.e. a back button and title change would only appear occasionally for some screens, and on others the navigation bar would be present but is not actually used for navigational purposes.
Is the best of implementing this to configure a UINavigationItem, if so as there are multiple screens and multiple view controllers is there anyway the same UINavigationItem can be shared? That way I can configure the UINavigationItem in the RVC and keep it there as different view controllers get pushed, replacing it where need be when a back button does actually need to appear?
If this isn't the best approach then what alternatives are there? I experimented with making my RVC a container view controller and adding the bar as a view of that, that works for the immediate child view controllers but not for grandchild view controllers (which would take up the entire screen and not the portion alloted to them by the container view).
The navigationItem in UIViewController is readonly, so you can't have a single shared UINavigationItem shared between view controllers. You could have a base view controller class that manages setting the navigation item that you derive all of your view controllers from. To keep your classes from getting too coupled you could have it update the contents of the navigationItem from a NSNotification. Then you can just post a notification when you need all of the navigation items to be updated.

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.

Resources