iOS 7: UINavigationBar out of sync with UINavigationController - ios

I have the following code:
NSArray* stack = self.navigationController.viewControllers;
NSArray* newStack = #[stack[0], stack[2]];
[self.navigationController setViewControllers:newStack animated:NO];
stack contains 3 view controllers. The problem is that the navigation bar is not removing UINavigationItems to match, so self.navigationController.navigationBar.items.count still returns 3 after running this code. Going back gets you into a weird state where you have a back button at the top that you can press but it just disappears, not taking you back any further.
Is this a bug in iOS 7 or am I just trying to do something really stupid? What's the best way to fix or work around this?

The navigationBar has its own ‘items’ stack which is not updated until viewDidAppear hits.
Which means, if we recreate the navigation controllers’ stack in viewDidLoad using i.e. setViewControllers: when we get to viewDidAppear we will have the current item added to the bars’ ‘items’ stack and therefore the UINavController viewController stack will not be in sync with the UINavBar items stack. This appears to be an iOS 7 bug.
In iOS 6.0 the 2 different stacks do not get out of sync no matter where we set the new viewControllers stack.
So try moving your code in viewDidAppear and see if that fixes the problem. I bet it will, because for me it did.

The behavior you are describing is a corrupt navigation controller stack. This is probably because you are trying to use a navigationController improperly. I don't have much context from the code here, but I am guessing you are trying to skip back to your root view controller? I think this is probably more what you would need:
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html#//apple_ref/occ/instm/UINavigationController/popToRootViewControllerAnimated:
you will then want to add a custom back button with something like so:
UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:#selector(yourMethodToPopToRoot)];
//yourMethodToPopToRoot is a method you create that runs popToRootViewController
self.navigationItem.leftBarButtonItem = backButton;
Any questions let me know.

Related

iOS 8: UINavigationController popping without animation and then pushing

In our app, there are many places that we need to quickly pop a view controller without animation and then push a new one with animation. We would do something like
[navController popViewControllerAnimated:NO];
[navController pushViewController:newVC animated:YES];
Pre-iOS8, this worked fine and the animation showed the new view controller sliding in over the current one, since the navigation controller was first popped without animation.
Now with iOS8, this seems to have changed and what happens now is the top view controller gets popped and the underlying view controller flashes for a split second and then the new view controller gets pushed on. I created a Xcode Project from scratch for iOS8 and tried to test this. Please see this GIF for a demonstration of what it looks like. Every time we tap one of the buttons in the master side of the split view, we perform the above two lines of code on the detail (right) side of the split. Note that the gray view (which is the root of the navigation controller) flashes for a brief second before the new one is pushed.
I have tried searching for any reason why this might have changed in iOS8 but I cant seem to find any documentation on it. Any one have any ideas on what might have caused this change? Any input would be greatly appreciated!
Also, I tried playing around with the code and discovered that doing the following code instead
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:navController.viewControllers];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[navController setViewControllers:viewControllers animated:YES];
seems to fix the issue. However, I would prefer not to use this if possible since there are many places in our app that do this 2-line pop-push combo and I would prefer not to have to change it all over the place.
Thank you!
I came up with similar solutions to you, the first is situational however.
First, you could override the back buttons in the stack you're manipulating to pop to root (or whatever controller you want to be root).
Second, you could add a category to UINavigationController basically implementing the code you listed above. That would save you from having to change it everywhere in your app.
-(UIViewController *)popPushViewController:(UIViewController *)controller animated:(BOOL)animated {
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.viewControllers];
[viewControllers removeLastObject];
[viewControllers addObject:controller];
[self setViewControllers:viewControllers animated:animated];
}
I'm planning on implementing the latter if Apple doesn't fix it/respond.

UIBarButtonItem in custom UINavigationController not showing up

On my side menu I call a NavigationController for each item on didSelectRowAtIndexPath.
Because of that I have a few NavigationControllers. So I created a custom UINavigationController to reuse the code.
The thing is that my UINavigationController subclass is being called but nothing appears on the simulator.
[self.sideMenuViewController setContentViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"EventsXib"]];
EventsXib is my CustomNavigationController
Any idea?
For the record, the whole point for me to do this is that I want the same leftBarButtonItem and 2 rightBarButtonItems on all my ViewControllers.
UPDATED:
I noticed that this actually worked:
self.navigationItem.leftBarButtonItem = self.navigationController.navigationItem.leftBarButtonItem;
self.navigationItem.rightBarButtonItems = self.navigationController.navigationItem.rightBarButtonItems;
But I still have to do this in every viewController, and thats not what I want.
Here is a general idea of what it looks like:
As #GoGreen suggested, I created a base view controller with the corresponding buttons on the navigationItem.
Its not the simple solution I had in my mind but it works pretty good.

ios navigation Stack Manipulation

i am having issues try to manipulate the navigationstack from an ios app. Or at least with the behaviour as a result of that manipulation.
My Situation:
i have 3 ViewControllers.
Controller a shows multiple levels,
Controller b is the gameview
Controller c is some sort of Score
Obviously i will select a level in controller a, which triggers a segue to controller b, once the level is finished ill segue to controller c. every segue as a push.
Now once im in controller c i dont want to be able to go back to b using the back button. I want to move back to controller a. In order for this to work, i removed the controller from the stack, so back wont move to controller b. This works fine.
The issue im facing is that the backbutton does show on controller a, which seems off since there shouldn't be any back. If i click the backbutton, the app doesnt crash, the button just disappears leaving the title.
i tried adding :
NSArray* controllers = [self.navigationController viewControllers];
if ([controllers count]<=1) {
[self.navigationItem setHidesBackButton:YES animated:YES];
} else {
[self.navigationItem setHidesBackButton:NO animated:YES];
}
[super viewDidAppear:animated];
as suggested in some relative stackoverflow article, without success. Besides this not working it seems off that ios creates those buttons from Storyboard without me actually adding them, but doesnt remove them when they arent necessary anymore. This leaves me with some options.
either i think that ios is smarter than it actually is
i am missing something essential to update the navigationbar
i went at this all wrong
besdies im using this code snipped to segue from Controller b to c.
[self performSegueWithIdentifier:#"feedbackSegue" sender:self];
[self removeFromParentViewController];
Any hints concerning missing operations or general bad practice is greatly appreciated.
Update
After further investigation, its not just the back button, its the whole navigationbar that is off. it behaves as if the removed controller was still there. The BackButton is there and another uiActionButton on the right end.
Does the navigationbar store its states onto a different stack, than the viewcontroller one? if this was the case, i could remove that state from this stack as well, to keep it consistent.
You could try this in your view controller c. This will remove the previous view controller, in your case the b. You'd also have to keep b in your stack (remove the line [self removeFromParentViewController]'; )
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if(self.navigationController.viewControllers.count>2){
NSArray *controllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray arrayWithArray:controllers];
[newViewControllers removeObject:[controllers objectAtIndex:self.navigationController.viewControllers.count - 2]];
self.navigationController.viewControllers = newViewControllers;
}
}
I believe the "correct" way to do this is to remove the back button on controller c. Depending on how and when you are removing controller b, you may be corrupting the navigation controller stack. It's generally not a good practice to manipulate the view controller stack.
To remove the back button, you have the correct code:
self.navigationItem.hidesBackButton = YES;
However, note that you must call this before the view controller is presented -- i.e., in something like viewDidLoad.
When you want to pop back to A, use:
[self.navigationController popToRootViewControllerAnimated:YES];

UISplitViewController: Programmatically Making Popover Controller Access Button Appear on Detail View

I don't know how to ask this more precisely. I have a master/detail and am creating the whole thing programmatically. I subclasses UISplitViewController and populated it with the two controllers, and everything looks as it should until I set splitViewController:shouldHideViewController:inOrientation such that it returns YES in portrait modes.
When I have the master hiding in portrait and portrait upside-down, as expected, it hides. However, I can't add a "Master" button to the nav bar at the top of the detail view in splitViewController:willHideViewController:withBarButtonItem:forPopoverController. This is probably because I have a fundamental misunderstanding of how I'm supposed to accomplish that task.
I followed the Apple examples and did:
barButtonItem.title = NSLocalizedString(#"Master", #"Master");
[detailController.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
I'm not getting any errors, but no button either. I speculate that perhaps it's because what I'm saving as detailController in my subclass is a UINavigationController and not a UIViewController.
Any guidance on this is much appreciated!
Having written this, I realized that there were several errors in wiring this whole thing up:
splitViewController:willHideViewController:withBarButtonItem:forPopoverController really wants you to not only to set the barButtonItem title, but also to add it to the nav bar of the detail controller.
If you ever want to programmatically dismiss the popover, you have to store the popover supplied in splitViewController:willHideViewController:withBarButtonItem:forPopoverController someplace in the master view.
So, the answer to the first part of the question was:
barButtonItem.title = NSLocalizedString(#"Master", #"Master");
[[detailController.topViewController navigationItem] setLeftBarButtonItem:barButtonItem animated:YES];
That got me to the UIViewController that can set a UIBarButtonItem on the navigation bar. I'm sure I could have done this directly on the UINavigationController but didn't immediately see how.
The second, unasked part of this question, deals with what to do with the popover once it's visible. Again, I needed the detail controller to know what the actual popover was so it can be dismissed, so in splitViewController:willHideViewController:withBarButtonItem:forPopoverController, I added code like:
[masterController.navigationItem topViewController].popoverController = pc;
where pc is the value of the argument passed into the delegate method. Then, in my master controller, I have a UITableView and on the didSelectRowAtIndexPath, I simply did this:
if(popoverController)
[popoverController dismissPopoverAnimated:YES];
And that's what I learned in iOS school today :)

How to remove itself and superview from super superview?

I have a UIViewController classes A and B. A loads B using: [A.view addSubView B.view].
B has a navigation bar with a Back button. I want to go back to A when I click it, so in the selector I tried [self.view removeFromSuperview], but it only removed the navigation bar. Then I tried [self.view.superview removeFromSuperview], it still just removed the navigation bar. What should I do?
Also, another minor issue with the Back button: setting it's title. I tried these two ways, but it still displays "Back".
navItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Chapter" style:UIBarButtonItemStylePlain target:self action:#selector(handleBackBarButtonItem:)];
navItem.backBarButtonItem.title = #"Chapter";
Thank you in advance!
I don't think you quite understand how navigation (with UINavigationController) works in iOS. Assuming you want animation, this is what you want:
Set up a UINavigationController. This can be done in the app's delegate (to avoid memory leakage, set an instance variable on UINavigationController *navController:
navController = [[UINavigationController alloc] initWithRootViewController:A];
Note that we are adding A as our root view controller.
Push the second view controller when needed. I assume that you are adding B.view after a button is clicked or something. In the implementation of the method that adds the second view controller, run the following code, instead of [A.view addSubview:B.view]. This method should be in the first controller's .m file:
[self.navigationController pushViewController:B animated:YES];
This will also give a nice transition effect.
Pop the second view controller off the stack. With UINavigationController, a pretty arrow-shaped back button is automatically included in a pushed view controller, to navigate back to the last view controller. This means that you don't even need any code to allow backward navigation.
That's it! Now if you need to change the title of B's back button, do this in A's viewDidLoad method:
self.navigationItem.backBarButtonItem = customBackButtonItem;
You can get an array of subviews and then remove the ones you wanted to be removed. This SO post will show you how to remove all subviews or multiple subviews using subviews array.

Resources