nested push animation can result in corrupted navigation bar - iphone

in one of my viewcontroller, lets say A, I have the following:
OnbViewController *on = [[OnbViewController alloc] initWithNibName:nibName bundle:nil];
onboardingTutorial.delegate_ = self;
self.test = on
UINavigationController *nController = [[UINavigationController alloc] initWithRootViewController:self.test]
[self presentModalViewController:nController]
[nController release];
then inside OnbViewController I have pushed to the navigationController:
[self.navigationController pushViewController:someViewController];
then in didSelectRowForIndexPath: I called a delegate, which is A, inside the delegate function I called I tried to push again by doing:
[self.test pushViewController:someOtherViewController];
and then this gives me that error:
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 <someOtherViewController: 0x6f942e0>.
any idea?

Rather convoluted calling sequence, but the first issue I see is that it looks like you're calling pushViewController on an OnbViewController, not on its navigationController. Maybe changing to [self.test.navigationController pushViewController:someOtherViewController]; will suffice?
Edit: in looking further at this, I note the "animation" in "nested push animation". Is onbViewController pushing to someViewController during its viewWillAppear or somewhere early like that? Maybe skipping the second animation will work?

It's a real tangle of pointers getting created there. self.test is the root vc of a navigation controller (nController). You present the nController modally, and then push other view controllers onto it's vc stack.
Then, while that stack is still there, you push onto self.test, which is like pushing onto the root again.
Let the VC that's getting the didSelectRow message do the push. You can still tell the delegate that it happened.

Related

self.navigationController pushViewController not adding controller to stack

I am on a viewController called vcA and I do this:
[self.navigationController pushViewController:vcB animated:YES];
and it works. vcB is pushed. Inside vcB's viewDidAppear I do this:
NSArray *controllers = self.navigationController.viewControllers;
and controllers contains just one object, vcA !!!! (what?)
Why is vcB being added to the controllers array? Is there anything that can prevent that from happening?
thanks.
I banged my head on the wall for a week to solve this problem involving pushing a view controller on the navigation controller stack.
The problem was this: I was on a navigation controlled based app and I had 3 view controllers, vA, vB and vC.
I was pushing vB from vA using this:
[self.navigationController pushViewController:vB animated:YES];
it worked, but, when I tried to push vC from vB, the self.navigationController property of vB was nil and vB was not on the navigation controller stack. What? Yes, I pushed vB on the navigation stack and vB was not being added to it, but even so, vB was showing correctly.
I discovered that inside vB's viewDidLoad, self.navigationController was not nil and that vB was on the navigation controller stack, but as soon as vB's viewDidLoad ended, vB was removed from the navigation controller stack and its navigationController property was set to nil. At that time, vB was a kind of ghost control, outside the navigation controller stack.
I don't need to mention that from vB I was unable to get back to vA or to push vC.
How that happen?
simple, I was pushing vB from inside a block... something like:
void (^ block1)() = ^(){
[self.navigationController pushViewController:vB animated:YES];
};
What was happening because a UIKit function (the push thing) was being executed in a block that was probably running on a thread that was not the main thread.
The resolution to that was to wrap the push in a dispatch to the main thread, using this:
void (^ block1)() = ^(){
dispatch_async(dispatch_get_main_queue(),
^{
[self.navigationController pushViewController:vB animated:YES];
});
};
The app had another minor problem with a wrong outlet connected to a viewController, but this was the main problem. This is the kind of problem hard to spot, for being subtle. I discovered the problem when I added another button to the navigation bar and that button did not appear. So, I suspect that something involving UIKit was happening, or not happening in that case. The big problem here is that this code was not crashing, no error message, nothing, just working bad, but working.
I believe this has to do with the timing of your checking. During the pushViewController method, the next view controller's viewWillAppear: method (and others, such as the one you're checking) are in the process of getting called. I believe after all those method are called, the view controller is considered on the navigation stack AFTER the animation is done completing.

Go to first view controller in app

I need to go to the first view in my app. I have a few views pushed onto the stack then a modal navigation controller and more views pushed onto that.
The problem I'm having is that using [[self navigationController] popToRootViewControllerAnimated:YES]; only goes back to the first view in the modal stack.
And I can't get [[self navigationController] popToViewController:.. to work because the true first view controller isn't accesible with [[self navigationController] viewControllers].
Any ideas on how to accomplish this? Thanks.
Do this:
[[self navigationController] dismissModalViewControllerAnimated:YES];
That will get you back to the VC that modally presented the navigation controller. Getting farther back after that depend on how you pushed those "few views" before the navigation controller.
Edit - explanation to get to the deepest root...
It sounds like those "few views" are on another, underlying navigation controller's stack. This can be a little tricky, because the clean way to get farther back in that stack is to have that underlying navigation controller pop to it's own root. But how can it know that the modal VC on top of it is done?
Let's call the view controller that did the modal presentation of second navigation controller VC_a. It's a modally presented navigation controller whose topmost VC is VC_b. How can VC_a know to pop to it's navigation root when VC_b modally dismisses itself?
The good answer (usually) is that VC_b decided to dismiss itself for a reason - some condition in your app/model changed to make it decide to be done.
We want VC_a to detect this condition, too. When VC_b gets dismissed, and VC_a gets a viewWillAppear message because it's about to be uncovered:
// VC_a.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (/* some app condition that's true when VC_b is done */) {
// I must be appearing because VC_b is done, and I'm being uncovered
// That means I'm done, too. So pop...
[self.navigationController popToRootViewControllerAnimated:NO];
} else {
// I must be appearing for the normal reason, because I was just pushed onto the stack
}
}
You need to do it by using the delegation pattern. Specifically, by creating a protocol that implements the delegate's respondsToSelector method.
See this post for complete details. It should be almost exactly what you are looking for. I had to do something similar, except I only needed to pop one view off the navigation stack instead of using popToRootViewControllerAnimated:.
For iOS6...
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
In AppDelegate.m class create method with bellow flow...
-(void)MethodName{//your method name
YourViewController *objViewController = [[[YourViewController alloc] initWithNibName:#"YourViewController" bundle:nil] autorelease]; ///define your viewcontroller name like "FirstViewController"
UINavigationController *yourNavigationController = [[[UINavigationController alloc] initWithRootViewController:objViewController] autorelease];
self.window.rootViewController = yourNavigationController;
}
When you want redirect on firstview just call this method from appdelegate object....

iPhone UINavigation Issue - nested push animation can result in corrupted navigation bar

I keep getting the following errors:
2011-04-02 14:55:23.350 AppName[42430:207] nested push animation can result in corrupted navigation bar
2011-04-02 14:55:23.352 AppName[42430:207] nested push animation can result in corrupted navigation bar
2011-04-02 14:55:23.729 AppName[42430:207] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
2011-04-02 14:55:23.729 AppName[42430:207] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Here is what I am doing. From a view controller, I call the following when a certain button is pushed:
EventsViewController *viewController = [[EventsViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBar.tintColor = [UIColor blackColor];
[self presentModalViewController:navController animated:YES];
[viewController release];
[navController release];
Then, if a certain button is pushed in EventsController, I call:
SingleEventViewController *viewController = [[SingleEventViewController alloc] initWithEvent:[currentEvents objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
Then, if a certain button is pushed in SingleEventViewController, I call:
EventMapView* viewController = [[EventMapView alloc] initWithCoordinates];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
So yea, it's obvious that there's nested push animations, but isn't this the right way to go about it? I checked out Apple's DrillDownSave code and this appears to be how they're doing it. Does it matter that I use init methods instead of viewDidLoad methods?
Calling pushViewController before viewDidAppear is unsafe.
ACCIDENTLY TRIGGERING THE SAME SEGUE TWICE
Once in code, and once from interface builder, but both at the same time...
I was getting the same error as the rest of you. Only my problem was I was accidentally firing the same segue, twice. Once from interface builder, and once from within my code.
I have a UITableView. When a cell is selected, a segue in interface builder fires. Heres my problem, I had the segue set up to be directly fired off clicking the CELL ITSELf, inside interface builder, then in my code, I had under didSelectRowAtIndexPath, code that would fire that same segue... like so...
[self performSegueWithIdentifier:#"MySegue" sender:tableView];
That means when didSelectRowAtIndexPath gets called because a row was selected, it fires the segue with the above line of code. Then interface builder, also triggers the segue, because its connected directly to the cell object in interface builder. To stop interface builder from directly firing the segue. You have to connect the segue from the top of the view controller, not nested down inside coming off of the cell itself.
So if you are having this problem for the same reason as me, that is, you are calling the same segue twice, you can fix this by unlinking the connection from the CELL DIRECTLY, to your segue, and having the segue connection originate at the top of the table hierarchy in IB, rather than nested inside the cell. Connect the segue from you View Controller itself, to the segue. If you have done this correct, when you select the segue, it should highlight the ENTIRE view it is coming from, not just the cell.
Now Apples documentation states thus under the performSegueWithIdentifier:sender: reference:
Apps normally do not need to trigger segues directly. Instead, you configure an object in Interface Builder associated with the view controller, such as a control embedded in its view hierarchy, to trigger the segue. However, you can call this method to trigger a segue programmatically, perhaps in response to some action that cannot be specified in the storyboard resource file. For example, you might call it from a custom action handler used to process shake or accelerometer events.
In my case, I have a search button for my UITableView, and whether the segue is called when the search results table is present, or the normal table view is present, had to be determined. So I needed to trigger the segue directly.
So remove the embedded control from interface builder, and just stick it on the view controller itself, then trigger the segue in your code!
Now, no more double segues! And no more errors.
I had the same problem / error message as you did just now, was looking for a solution and ended up at this thread, however, for me I found that the solution is actually having only one animated:YES when doing a nested push (I put animated:YES only for the final push), hope this helps
cheers.
I've figured it out. Apparently if you call -pushViewController from outside of the -didSelectRowAtIndexPath method of a UITableViewDelegate, it doesn't work. Moving the call into that function worked. Weird.
I happened upon this same problem that resulted from a button in a nib being connected to two different actions. It tried loading both view controllers, thereby corrupting the stack.
What do you mean when you say you use init methods instead of viewDidLoad methods?
If you're pushing a new view controller before the old push has bad a chance to be actioned, you will get this sort of error. So putting certain code into init and doing things prematurely could certainly get you the error being reported.
At the point where init is being run on a view controller, the view hasn't been loaded yet!
Um I had this issue, and Im new to the whole iOS dev scene. But after looking at my connections inspector (with file's owner) in the interface builder i saw that as I had copied a button it had the previous buttons method assigned to it as well as the new method I had created. I guess that was where the nested aspect of my problem came from, as it was executing 2 different methods both of which pushed a view onto the Nav Controller. I know this has already been answered but I figured I would put this up just in case anyone else had a silly mistake like mine.
This has already been answered, but I thought this might help others as I got the same error but without using table views. I finally figured out the problem.
I had an existing button whose IBAction invoked a pushViewController. I had created a new button by copying the existing button. The new button also had an action that invoked pushViewController. When the new button was tapped (touch up inside) and the view controller was pushed, I got this error. I deleted the new button, created it from scratch, bound it to the existing outlets and actions, and the error went away.
Ran into the same problem. In my case I was missing a break in the switch statement so two segues were fired at the same time. Easy fix for me.
My problem had to do with the keyboard being active.
This was caused for me by pushing a ViewController from a textField's delegate method:
-(void)textFieldDidBeginEditing:(UITextField *)textField{
FilterLocationViewController *destViewController = (FilterLocationViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"FilterLocationViewController"];
[self.navigationController pushViewController:destViewController animated:YES];
}
By changing the code to this:
-(void)textFieldDidBeginEditing:(UITextField *)textField{
[_textFieldLocation resignFirstResponder]; //adding this line
FilterLocationViewController *destViewController = (FilterLocationViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"FilterLocationViewController"];
[self.navigationController pushViewController:destViewController animated:YES];
}
(adding the line [textField resignFirstResponder];) the problem went away.
Basically the lesson is that you shouldn't modify the navigationController stack if the keyboard is out.
Recently, I've faced the same problem. The reason was: -I was trying to pop view controller twice by mistake. you can check this crash by setting breakpoints on push and pop View controllers
1) Perhaps you could try passing the necessary variables as properties before pushing the UIViewController rather than using the init methods with parameters. Most likely you will need these parameters beyond your init method anyway.
Also, in your initWithCoordinates: method you are missing the parameters. Possibly your custom init methods are a part of the problem.
2) Just because you mentioned viewDidLoad -- this method is for initialization after a view has loaded . If you create the UIViewController in code, as it seems you do, you should use loadView to set up your subviews.
This was happening for me because of my UIControlEvents
[button addTarget:self action:#selector(callSecondView) forControlEvents:UIControlEventAllTouchEvents];
I had to change the UIControlEventAllTouchEvents to UIControlEventTouchUpInside or however you want your button to work if you had the issue because of a UIButton call.
My Solution was
[self performSelector:#selector(moveTo) withObject:nil
afterDelay:0.5];
Don't know about other's. I think most of the People using StoryBoard is facing such Problem. I am using XIB.
In my case The Problem Was, when I was moving to another view using push,
I was also using
[self.navigationController popViewControllerAnimated:YES];
in the ViewWillDisappear of the current View at the same time. Just remove it and it works fine.
I was using POP, because of the requirement and the Flow.
The Hierarchy was 1 -> 2 ->3
I was on view 2 and wanted to move to view 3. In that case I encountered this error.
In my case I was both setting the push segue from the storyboard and programatically. Hopefully that'll help anyone
I had this error message too, and the navigation bar and navigation controller transitions were weird. My setup was a bunch of Navigation Controllers embedded in a Tab bar Controller. The problem was that I didn't call super.viewDidLoad() in my Tab bar Controller implementation of viewDidLoad.
Calling super is something the docs clearly point out that you should do when overriding viewDidLoad, and I learned this the hard way.
Maybe this can help someone else too!
I know that this was answered, but it could help others.
I had the same problem, but it was caused because I was using a bad event for an info button.
I was using "UIControlEventAllTouchEvents" and this generated two push of the same view into the navigation controller. The correct event was "UIControlEventTouchUpInside". I'm new to iOS.
This resolves the problem:
https://github.com/nexuspod/SafeTransition
If you push (or pop) a view controller with animation(animated:YES) it doesn't complete right away, and bad things happen if you do another push or pop before the animation completes.
To reproduce this bug, try pushing or popping two view controllers at the same time. Example:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIViewController *vc = [[UIViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
You will receive this error:
2014-07-03 11:54:25.051 Demo[2840:60b] nested push animation can
result in corrupted navigation bar 2014-07-03 11:54:25.406
Demo[2840:60b] Finishing up a navigation transition in an unexpected
state. Navigation Bar subview tree might get corrupted.
Just add the code files into your project and makes your navigation controller as a subclass of APBaseNavigationController, and you'll be good to do.
Just to complete the list, here is another reason which can cause "nested push animation can result in corrupted navigation bar":
I did setup several NavigationController within a TabBarController and set
the selectedIndex within the storyboard Identifiy Properties. After moving active Tab to Code error disappeared.

pushViewController only works with animated:YES

I have found a strange behaviour when trying to push a viewcontroller onto the stack. The following works fine:
[self.navigationController pushViewController:myViewController animated:YES];
but if I change it to animated:NO it no longer works, doesn't seem to push at all. I was performing this in a viewWillAppear but I have also tried it in viewDidAppear but with no luck.
Any ideas what could be causing this?
Thanks
The problem is most probably not the call itself, but the placement of the call. Try putting the same action on a UIButton and it should 100% work. I've noticed that putting view controller manipulation routines like presentModal... and pushViewController... don't sometimes work in the viewWill* viewDid* methods. Or try making the calls from those functions with a performSelector:withObject:afterDelay after a short delay and see if that works.
Edit: there are a couple of ways of doing what you want to do. You can directly modify the navigation stack of a navigation controller, so when you are in view N+1, you can replace view N on the stack (by building a new navigation stack array and setting it in to the navigation controller), then pop, and you'll get the effect of "popping back to a different view controller". You can also issue multiple pops and pushes from the view controller you want to leave, but you have to be careful:
// pop back 2 controllers on the stack to the setup screen
//
// locally store the navigation controller since
// self.navigationController will be nil once we are popped
//
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
//
[[self retain] autorelease];
// Pop back 2 controllers to the setup screen
//
[navController popViewControllerAnimated:NO];
[navController popViewControllerAnimated:YES];

How to jump to a specific view?

I have 3 views (xib'd) the third view opens a modal view (also xib'd).
My goal is to dispose the modal view and jump on the view #1.
I used the following code but it does nothing.
self.statusView = [[StatusViewController alloc] initWithNibName:#"StatusViewController" bundle:nil];
[self.navigationController popToViewController:self.statusView animated:YES];
[self.navigationController popToViewController:
I also tried the following, same result.
[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
I'm going crazy...
statusView has an accessor regularly synthetized and it represents the view that I want to jump to.
It is not entirely clear how your views are set up with respect to each other, based on what you've said so far.
I'm guessing you have a navigation controller, and 3 view controllers that are displayed on the navigation stack.
If that's the case, and you want to pop back by two screens at once (from #3 to #1, skipping #2), then you need a pointer to the view controller for #1 (not the view itself). It looks as if the first popViewController: method call in your question is sending in a view.
Sample code to pop to the first view controller:
UINavigationController* navController = self.navigationController;
UIViewController* controller = [navController.viewControllers objectAtIndex:0];
[navController popToViewController:controller animated:YES];
If you've tried this, and it doesn't work, a few things might be going wrong:
Maybe self.navigationController isn't actually the right object.
Maybe the view you expect isn't actually view #0 on the navigation stack.
Maybe the navigation controller you're working with isn't currently visible.
Here are some further steps you can take to test these hypotheses:
When you first allocate the navigation controller you want to work with, call NSLog(#"Nav controller is at %p", navController); and in this code add a call to NSLog(#"Now my navController is at %p", navController); and check that the addresses match.
If the nav controller is the right one, print out the current navigation stack; something like this (which assumes each view controller has a different class name):
for (UIViewController* viewController in navController.viewControllers) {
NSLog(#"%s", class_getName([viewController class]));
}
Do something visual to the navigation controller you think is visible to make sure it actually is. For example [navController.visibleViewController.view addSubview:aColorFulView]; where aColorFulView is some visually obvious UIView.
-popToViewController lets you pop controllers OFF the stack. What you want to do is push new viewControllers ONTO the stack, so use:
[self.navigationController pushViewController: self.statusView animated: YES];