Navigation bar shows wrong navigation item in iOS 9.2 - ios

A UINavigationController's navigationBar will initially show the correct UINavigationItem, but then will revert to the previous UINavigationItem every time a UIViewController is pushed onto the stack.
Steps to Reproduce:
Push a UIViewController onto a UINavigationController stack
Set navigationBarHidden = YES on the navigation controller
Push another view controller onto the navigation stack.
Begin an interactive pop transition and then cancel it.
Pop back to the previous view controller
Set navigationBarHidden = NO on the navigation controller
Attempt to push a view controller onto the stack
Looks like -[UINavigationBar _cancelInteractiveTransition] is getting called, even on push transitions after getting into this state? I can set a breakpoint on that symbol, and the navigation bar shows the correct navigation item before it and the wrong navigation item afterwards.
But what I want is that the navigation controller's navigation bar should display the current topViewController's navigation item.

#interface UINavigationController (Private)
- (void)_cancelInteractiveTransition:(float)arg1 transitionContext:(id)arg2;
#end
- (void)_cancelInteractiveTransition:(float)arg1 transitionContext:(id)arg2
{
BOOL hidden = self.isNavigationBarHidden;
if (hidden) {
[self setNavigationBarHidden:NO animated:YES];
}
[super _cancelInteractiveTransition:arg1 transitionContext:arg2];
if (hidden) {
[self setNavigationBarHidden:hidden animated:YES];
}
}

I recently ran into this issue on iOS10 and I'm sure it was there on iOS9, assuming we still supported it. It turned out that the issue was that at the start of the interactive transition we were setting navigationController.navigationBarHidden=NO and then when it was cancelled forgetting to set it back to navigationController.navigationBarHidden=YES. It seems like the navigation bar doesn't like to be unhidden twice in a row. I would imagine that its the same for setting it to hidden twice in a row as well. The good news is that this was not an issue with iOS11.

Related

Pushing a UITableViewController from a tabBarController embedded view doesn't remove tab bar?

I have a tab bar controller with 4 tabs and each tab is their own UINavigationController, which is how you're supposed to nest tab bar and navigation controller's together. The initial tab is a TableViewController and works/appears the way that it should. From the tableVC I can push standard view controller's onto the navigation controller with:
[self.navigationController pushViewController:VC animated:YES];
and it works properly.
If I attempt to push another TableViewController onto the navigation with the same method it works the same way, but the initial tab bar does not get pushed off screen like it should, it just stays in place.
Why would the tab bar stay on screen even though I am pushing a new VC onto the navigation?
I have tested with multiple instances of different TableVC's and it only happens with a table view controller.
Here is the code I'm using:
- (void)pushTableVC
{
TestTableVC *tableVC = [[TestTableVC alloc] init];
[self.navigationController pushViewController:tableVC animated:YES];
}
This will push the new table view onto the stack, but the tab bar from the parent VC stays in place and does not get pushed off screen like it should.
You should call the method setHidesBottomBarWhenPushed: on the view controller you are pushing to correctly hide the tab bar.
UIViewController *viewController = [[UIViewController alloc] init];
[viewController setHidesBottomBarWhenPushed:YES];
[[self navigationController] pushViewController:viewController animated:YES];
When you use a UITabBarController, the tab bar always stays on screen, even as you push additional view controllers onto an embedded UINavigationController. You will see this in any app that has a UITabBarController implemented, unless they implement custom behavior to change this.
The UINavigationController contains everything above the UITabBar, but does not contain the UITabBar itself, so it would not be able to push it offscreen.

UINavigationItem craziness from pushing view controllers with no animation?

I am pushing a viewController onto the UINavigationController with animation, and the controller being pushed on is basically doing something like:
--- app delegate:
[((UINavigationController *)window.rootViewController) pushViewController:initialController animated:YES];
--- initial controller:
- (void)viewDidLoad {
[super viewDidLoad];
if (self.shouldSkipThisController) {
SomeOtherViewController *someOther = [[SomeOtherViewController alloc] init];
[self.navigationController pushViewController:someOther animated:NO];
}
}
This is causing some CRAZY behavior which I don't understand at all. Basically, it seems like the navigation items set on SomeOtherViewController are being covered up by some strange other button that has the name of the title in a back button. It looks like although SomeOtherViewController is setting it's own left and right navigation items, they are covered up by the "default" back button--- and then if I tap on that back button, then just the navigation bar at the top animates-- and THEN SomeOtherViewController's navigation items are then there.
The only thing I could find that sort of worked was to either 1) not animate the push of the initial view controller in the app delegate, or 2) move the shouldSkipThisController condition into viewDidAppear: method.
However, neither of those options are ideal... Any help could be greatly appreciated.

View Controller Containment shared parent objects

I am using view controller containment, and as part of my implementation a child view controller needs to disable a bar button item of the navigation controller of the parent view controller. The theory is that being a child view controller, I had access to the same navigation controller (in my context anyway) as the parent view controller's.
From debugging I can see that self.navigationController and self.parentViewController.navigationController were set to the same address.
For example:
NSLog(#"%# - %#", self.navigationController,
self.parentViewController.navigationController);
NSLog(#"%# - %#", self.navigationItem,
self.parentViewController.navigationItem);
NSLog(#"%# - %#", self.navigationController.navigationItem,
self.parentViewController.navigationController.navigationItem);
Resulted in the following console log:
<UINavigationController: 0xc482290> - <UINavigationController: 0xc482290>
<UINavigationItem: 0xa5f3620> - <UINavigationItem: 0xc482490>
<UINavigationItem: 0xa5f36e0> - <UINavigationItem: 0xa5f36e0>
Results
Navigation controllers are the same, as expected (self->navController == self->parent->navController).
Navigation item's are different, this is expected. Each view controller has it's own nav item (self->navItem != self->parent->navItem).
Accessing the navigation controllers nav item from the parent or the child view controller is identical, as expected (self->navController->navItem == self->parent->navController->navItem)
So I now ask: why didn't disabling a bar button item in the child view controller with the following code work:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
This should be synonym to the following (which does work as expected) given their shared address:
self.parentViewController.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
Update
I was reading my logs too fast. Indeed the parentViewController and the navigation controller have 2 different navigation items. I missed this in my example here because the memory addresses were extremely close: 0xa5f3620 != 0xa5f36e0
A UINavigationController has it's very own navigationItem (like any other UIViewController) that will only come into play if you push it onto the stack of another UINavigationController.
Generally you aren't going to push one UINavigationController onto another so manipulating self.navigationController.navigationItem is pointless.
EDIT:
To check ... I've just aded:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UINavigationItem *myNavControllersItem = self.navigationController.navigationItem;
UINavigationItem *myItem = self.navigationItem;
NSLog(#"%# - %#", myNavControllersItem, myItem);
}
to a VC that I'm pushing onto a NavController and get:
<UINavigationItem: 0x1ed0c170> - <UINavigationItem: 0x1ed46330>
Additionally, if I add:
self.navigationItem.rightBarButtonItem.enabled = NO; to viewDidAppear the right button gets disabled.
if I add:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; it does not.
Well basically the problem is the way the navigation controller updates the bar button items, basically the buttons are update whenever the top-level child is changed.
For the right bar button item, if the new top-level child has it's own bar buttons items then the navigation controller will select the existing one, if not nothing will be displayed.
So in your case the parrentViewController already has a navigationItem.rightBarButonItem which is not disabled, by calling self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; you are disabling the child's nabigationItem.rightBarButtonItem and you can't see this because the updates are made only when the top-level child is changed which is not your case.
So in order to disable the parentViewController right button you should do:
self.parentViewController.navigationItem.rightBarButtonItem.enabled = NO
For more details regarding the nav bar button updates please check apple docs regarding the Updating Navigation Bar Items

One instance of navigation bar in every tab

I've been struggling this problem for several days. In my application I have tab bar controller with UINavigationViewControllers inside. I want every navigation bar in every navigation controller to look exactly the same depending on user actions and app state.
For example: if user logged in to my app in first view controller, app sets his name in navigation bar and sets navigation bar into logged in state. Then when user selects other tab item, I want to set this logged in state of navigation bar from first view controller to other view controllers. I've tried to use singleton but with no effect.
Seems like you have to do 2 things:
Set the current settings while init the UIViewController
Update all initiated Controllers after state changes
to 1:
create a class with
#interface UIViewController (UINavigationController)
- (UINavigationController*)wrapWithNavigationController;
#end
#implementation UIViewController (UINavigationController)
- (UINavigationController*)wrapWithNavigationController
{
UINavigationController* navigationController = [[UINavigationController alloc] initWithRootViewController:self];
// your customizations
navigationController.navigationBar.barStyle = UIBarStyleBlack;
[...]
return [navigationController autorelease];
}
#end
you can call it UIViewControllerAdditions for example.
to 2:
use the NSNotificationCenter for updates :)
Do you really have multiple UINavigationControllers, or do you just want to use the navigationBar for the same actions and functionality in every tab?
If you just want to have the bar, you could also use an UINavigationController as your apps rootViewController and then the UITabBarController as a childViewController of your UINavigationController.

Set navigation bar hidden, depending on how the view controller appeared

I have a tab bar with a navigation controller in one of the tabs. Currently the root view of the navigation controller doesnt have the nav bar showing and animates nicely into the subviews by
- (void)viewDidLoad {
...
[self.navigationController setNavigationBarHidden:YES animated:NO];
...
}
and
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
But of course changing tabs initiates the viewWillAppear function and so as I go back to the root view the navigation bar slides away, rather than just not being there.
Is there a way that I can hide the nav bar on the root view without animating it except for when appearing from a subview on the navigation stack?
The (BOOL)animated parameter on viewWillAppear:animated. When changing Tabs, it will come as NO, since the animation is immediate. On the other hand, if it's being pushed or popped from the navigation stack with animated:YES, then it will come as YES.
Although this looks like a hack, it's the correct way: you don't need to figure out who was the caller, instead, focus on the fact that if your view controller will appear animated, you have time to do your own animations, if not, screw it, show (or in this case, hide) everything immediately.
Try showing/hiding the bar in the UINavigationController's delegate's navigationController:willShowViewController:animated: method, depending on whether the view controller being shown is your root view controller.
What if you set a boolean variable in your application delegate and in set that boolean accordingly in subviews as 0 and in other views as 1. And in your viewwillappear, according to your variable's value, you can set the animation.

Resources