ios navigation Stack Manipulation - ios

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];

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.

Cant make navigation back to original tab without creating a new instance

Here is my set up in storyboard.
I'm trying to use this code: (In an IBAction connected to the UIBarButtonItem in the last VC):
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
}
to navigate from the last ViewController in this picture to that very first UINavigationController in the stack. I was informed that UINavigationControllers are not meant to be nested like this, and that only one UINavigationController should be necessary, but when I successfully remove(which I have done at least 5 times) the other three UINavigationControllers from the rest of the stack, I completely lose my UINavigationBars from the regular ViewControllers.
The problem with navigating the way that I currently am with my 'confirmClicked:' method, is that I create another instance of that first UIViewController in the hierarchy.
Should I:
A) Remove all the unnecessary UINavigationControllera in the view hierarchy, then use 'popToRootViewController:' to correctly navigate from the last VC to the first?
B) Try to navigate another way from the last VC to the first VC, keeping the extra UINavigationControllers in the view hierarchy?
Also, if I remove the Navigation Controllers, how will I keep my navigation bars in the UIViewControllers, since they have been disappearing in the past when removing the Navigation Controllers?
Sorry in advance for the many questions but I have been stuck for a while.
OK
First thing
Remove all those navigation controllers. You only need one.
Second
The code in that IBAction is creating a brand new instance of the TabBarController and placing it over the current stack (this is how to get memory problems).
Third
What you probably want is either an unwind segue (possibly) or code something like this...
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.tabBarController setSelectedIndex:1];
}

Pushing and popping multiple view controllers causes random crash

I'm currently working on an app that builds a stack of controllers depending on the user.
Basically, I have a UIViewController that has a UIButton leading to another UIView Controller; that has a button leading to another view controller and so on. The view controllers are pushed so that when the user always press the button, I get a stack of multiple view controllers. The views are popped whenever the user wants to go back to the previous view controller.
Everything is working well (push and pop). However, at random instances, the app would crash. I noticed that it happens when there are already a large amount of views pushed, and I suspect that it can be a memory issue.
My question is, other than pushing the view controllers, is there an alternative so that I can avoid stacked views? Could it also be that the crash is not because of the stacked views but because I'm just missing something out? There is no error presented in the logs so I can't find out what's happening and I'm also new to iOS development.
Thank you very much!
Edit 1: There is no error in the logs but when the app crashes, there is this message:
Thread 1: EXC_BAD_ACCESS(code = 1, address = 0xd000000c)
Edit 2: This is how I am pushing the controller:
CustomController *custom = [self.storyboard instantiateViewControllerWithIdentifier:#"Custom"];
[self.navigationController pushViewController:custom animated:YES];
And this is how I popped it when the back button is pressed:
[self.navigationController popViewControllerAnimated:YES];
Edit 3: After enabling zombie objects in the scheme, I started to get this messages after multiple push and pop:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Unbalanced calls to begin/end appearance transitions for
Do those messages say that the problem is actually on pushing the controller with animations? Thanks everyone!
Edit 4: I'll try to revise the question to make it more descriptive
This is my setup:
Controller A displays icons that corresponds to different places. You can click on the icon to push Controller B and display details for Location A.
Controller B displays information about Location A, with a button to show Controller A that now displays icons close to location of Location A. Now, you can again click an icon, say for Location B, and display details and so on.
When the user presses the back button, it should display the previous view controller. This is why I used push and pop. Is there a better way to handle this process. Thanks again!
My suggestion is: check if Zombie Objects is enabled and use the instrument "Allocations" to see if your application have, in fact, memory issues. With the informations provided by these tools, you can figure out what is happening with your application and work on it.
Tell me if you need help.
Good luck!
When push or pop, you should turn off animation. I think this causes crash when animation does not finish.
Push: self.navigationController pushViewController:custom animated:NO];
Pop: [self.navigationController popViewControllerAnimated:NO];
My guess is that you push multiple view controllers with animations - this may be a root cause of error. If you push more than one view controller you should animate only LAST push, say:
VC1 *vc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC1"];
[self.navigationController pushViewController:vc1 animated:NO];
VC2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2"];
[self.navigationController pushViewController:vc1 animated:NO];
VC3 *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3"];
[self.navigationController pushViewController:vc1 animated:YES];
However, i hardly imagine the situation where the multiple push would be necessary - i think it always leads to bad UX.

How do I emulate 'Notes' from Apple and start the app with a navigation controller with a 'back' view?

I have an application that has a login page. It then moves to a navigation controller that has a collection view as its root view controller. When the app starts and there is only one item in the collection, I want to have the navigation controller automatically push to that item and allow the user to use 'back' to view the collection. This is the same behavior that is in 'Notes' from Apple.
The idea is to allow the user to immediately start to use the app and only 'discover' the need for the collection view after using the app for some time.
I am using IB, storyboards, and segues for my view transitions.
If I programmatically have the root view controller do a performSegue in its viewWillLoad: I get an error about causing a transition while a transition is still in process.
If I move the code calling performSegue into the didLoad, then the user sees a double transition.
A navigation controller usually manages the underlying stack itself. That is, you pass it an initial view controller, and then through segues or pushViewController:animated: the stack is altered. However, there is nothing stopping you from manually altering the stack. In prepareForSegue:sender:, you can check the number of items, and if it's 1, do something like this:
UIViewController *singleItemViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SingleItem"];
NSMutableArray *viewControllers = [sender.destinationViewController.viewControllers mutableCopy];
[viewControllers addObject:singleItemViewController];
sender.destinationViewController.viewControllers = viewControllers;
Now instead of starting at the first view controller of the stack, you start at the second one, with a back button to navigate back.
Actually, I found that the following worked the best for me.
if (/* reason to change back list*/) {
UIViewController * rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"backViewController"];
NSArray * viewControllers = #[rootViewController, self];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
setViewControllers seems to be the best way to manipulate the stack.

UINavigationController back button doesn't pop to previous VC

I have a problem similar to this question, but with a different cause it appears.
I have A nav controller with Views A>B>C>D>E.
In C I push to D, in which users "create a new session". Sometimes users want to add detail to this new session, so they click add detail which pushes to E.
When they don't choose to add detail they press "done" on D, I call [navControler popViewControllerAnimated:YES]; and they end up in C without the problem mentioned in the question above, meaning if on C they press back, it successfully goes to B. However, when they do add detail, they go to E and when finished they press the nav ctrl's back button. At that point I added this code to take them back to C:
NSArray *allViewControllers = [[self navigationController] viewControllers];
for (UIViewController *aViewController in allViewControllers) {
if ([aViewController isKindOfClass:[TSessionMgmtViewController class]]) {
[self.navigationController popToViewController:aViewController animated:NO];
}
}
They end up back at C, but when they hit the back button on C, it does the strange back animation but ends up on C again. Then pressing back a second time takes them back to B.
I have already checked the following:
I am not calling extra notifications that cause an extra C to be pushed.
I am not pushing C in any other way
I checked the nav controller stack with NSLog(#"nav controller stack: %#", [[self navigationController] viewControllers]); and it does not show an extra C on the stack.
So is this a problem with the back button itself on C? Or is there some invisible C view controller that's there but does not show when printing the stack?
Thanks so much in advance!
You ask the nav controller for the list of vcs, but the first time you call pop, they change! You should just choose one to pop to. Or use a nav cont subclass and add logic to it so when you just pop the top controller, the one under it ( in its viewWillAppear) can query to see what it's suppose to do.

Resources