Two UINavigationItems? - ios

Could anyone explain why there are two navigationItems? When I log like below:
NSLog(#"%#", self.navigationItem);
NSLog(#"%#", self.navigationController.navigationItem);
I get two different instances of UINavigationItem:
<UINavigationItem: 0x7f85b06f5a20>
<UINavigationItem: 0x7f85b06ab640>
I have only created a UINavigationController programmatically once.

All UIViewControllers have a property navigationItem. Therefore, because UINavigationController is a subclass of UIViewController, it also has this property.
self.navigationItem will be the one presented when your controller is pushed. The documentation for navigationItem, it's clear about this property
This is a unique instance of UINavigationItem created to represent the view controller when it is pushed onto a navigation controller.
self.navigationController.navigationItem would be the item displayed if Apple allowed UINavigationControllers to be nested. However, since this isn't allowed, it's best to forget about it.

Okay, this question puzzled me for awhile but I think I figured it out. self.navigationItem and self.navigationController.navigationItem are two different objects. Here's why:
In iOS, each UIViewController object has a UINavigationItem. The navigationItem for your current view controller is self.navigationController, and self.navigationController.navigationItem is kind of a spillover object, it's the navigationItem for your parent navigationController.

Related

NavigationController StoryboardSegue not showing navigationBar and navigation delegate not called

I recently changed my app structure to include a UINavigationController as base for my hierarchy and I had its root viewController implement the UINavigationControllerDelegate protocol in order to implement custom segues.
My viewController implements navigationController:animationControllerForOperation:fromViewController:toViewController:.
My problem is two-fold:
The navigationController.delegate methods are not being called.
The navigationBar is not called in the views being pushed via storyboardSegues of type show.
The prepareForSegue:sender: function is being called.
This is my UI:
Turns out that UIStoryboardSegues I added before I added the UINavigationController to my hierarchy are still interpreted as modal segues. Probably this is set during creation.
The problem was solved by deleting and re-adding the segues in question, with the relevant information (identifier, class...) transferred to the new instance.
If you have the same problem, when you set Top Par to inferred in your segued viewController you will see no navigationBar showing.
After replacing the segues the Top Bar showed again as normal.
Edit:
I posted the question together with this answer, since there was no post on SO covering this issue. self-answer

how to pass a variable back to root navigation controller from the UIViewController using Protocol and Delegate in Swift

Xcode Image
As you can see in the image attached I have the root navigation controller called - Notifications and a UIViewController called NotificationsController.
So my Question is how can i pass a variable from NotificationsController back to Notifications using Protocol and Delegate, because in this case there is no segue but a default relationship between them.
Is my question correct or is there another way to do what i need.
Any help is really appreciated
To reference the navigation controller from the view controller (it is an optional so you need to handle that):
// self is a UIViewController
self.navigationController
To reference the view controller from the navigation controller:
// self is a UINavigationController
let index = // Index of the view controller. You may need to iterate over viewControllers to find this.
self.viewControllers[index]
You don't really need to set up a delegate. As keithbhunter pointed out in his answer, a view controller has a navigationController property that will point to the navigation controller that manages it.
I suggest you define a protocol for the messages you want to send to your navigation controller, and then have your custom subclass of UINavigationController conform to that protocol.
Within the view controllers that are on the navigation controller's stack you can fetch a pointer to your navigation controller and cast it to type UINavigationController<myNavControllerProtocol>.
(UINavigationController that conforms to myNavControllerProtocol. I'm working in Objective-C these days and don't remember the exact syntax for that.)

How to set UIBarButtonItem created on a child VC to the navigation controller of its parent VC?

I created a custom container view controller. It has two child view controllers. Each one set an instance of UIBarButtonItem to self.navigationItem.rightBarButtonItem to show the button on the navigation bar. But, nothing showed up.
What is the right way to make it work?
The following code is what I used in viewDidLoad of the container view controller.
UPDATE:
I marked the only answer here as the right one. #rdelmar is explaining correctly as far as my code concerns. But, here is one thing I'd like to point out. Basically, It's not a good approach to make any child VCs expect the existence of the navigation controller if the children are supposed to be used in the custom controller.
If I could get back in the past, I would have made some interfaces on the container to let their children know that they can set any navigation items if needed.
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];
If the parent controller is embedded in a navigation controller, then that parent is the one that will have a navigationItem, not the child. So, self.navigationItem in the child will be nil. You need to reference the parent,
self.parentViewController.navigationItem.rightBarButtonItem = ...

displaying a ViewController from a non UI class

I perform some data loading tasks from an Ojective§C class and once everything is loaded, I simply wants to display a Viewcontroller subclass prepared in a storyboard.
So when everything is ok, the following method is called:
- (void)loadingNextView
{
CABBndGSite *mySite = [CABBndGSite alloc];
CABBndGSelectLanguageViewController *vc = [[mySite myRootViewController].storyboard instantiateViewControllerWithIdentifier:#"SelectLanguageViewController"];
[[mySite myRootViewController] presentViewController:vc animated:YES completion:nil];
}
So I verified that myRootViewController is not nil. It's a UINavigationController class.
vc is not nil so it found my view in the storyboard.
Anyway, the presentViewcontroller message seems to doing what expected.
Certainly a stupid mistake but my poor iOS programming knowledge lets me in the fog!
I use this code from ViewController subclasses with success and as here I get a valid ViewController pointer, I don't understand why it doesn't work.
I also tried to implement the AppDelegate method explained here How to launch a ViewController from a Non ViewController class? but I get a nil navigation pointer. Maybe something not well connected in my application
May I have some explanation?
Kind regards,
UINavigationController maintains a stack of view controllers. You can access this stack through the viewControllers property. To present your view controller, you can:
(a) have the navigation controller push the new view controller on to
the stack (pushViewController:animated:);
(b) have the top view controller in the view controller stack present
the new view controller modally (presentViewController:animated:completion:), or;
(c) add the new view controller to the view controller stack array
manually by assigning a new viewControllers array to the navigation
controller's viewControllers property (setViewControllers:).

iOS How to override NavigatonController's pop and push methods?

I'm working on an app that uses NavigationController based Storyboard, but the navigation is controlled by a segmentedControl. This way I can iterate over the x menus several times, then I can go back for about a year :D
My question is:
How can I override the push methods of the NavigationController to check if there's already an instance in the stack and reuse that without adding the same instance to the stack again?
Thank you in advance!
Subclass the UINavigationController and implement the pushViewController:animated: method in the subclass. There you can iterate through the stack by calling self.viewControllers which returns an NSArray of the view controllers on the stack. There you can check, whether the controller you're trying to push has already been pushed before. If not, call [super pushView...]. If it has, create a mutable copy of the stack NSMutableArray * newStack = [self.viewControllers mutableCopy] and move the desired view controller to the end of the array. Once you have that, just call [super setViewControllers:newStack animated:...] which sets your new stack manually.
I don't think you need to override the popViewController method, though.
Oh, and once you have that, don't forget to change the class of the UINavigationController in the Storyboard to the name of your subclass of the navigation controller.
Another thought - if you're using the segmented control, have you thought about using UITabBarController instead of UINavigationController for navigation?

Resources