Navigation controller popViewControllerAnimated : yes is not working as expected - ios

I am using following line of code:
[self.navigationController popViewControllerAnimated:YES];
But it is not behaving in ios 7 as it doing in ios 6.Some times it does not pop controller while we are pressing back button 2- 3 times in succession.
Resulting in abrupt behaviour in navigation bar and deallocating a controller but showing the same on ui .
So when we press anything on that controller it results to a crash since controller is already deallocated.

Check if you're running the code on the UI thread

[self.navigationController popToRootViewControllerAnimated:YES];
This method will navigate to the root of your navigationController.
You can check your viewController hierachy With following code.
NSLog(#"%#",self.navigationController.viewControllers);

I resolved this problem with this way:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UINavigationController * nav = tabbarControl.selectedViewController;
[nav.viewControllers objectAtIndex:0];
[nav setViewControllers:#[[nav.viewControllers objectAtIndex:0]] animated:NO];
tabbarControl.selectedIndex = 0;
});
When you delay one second the view will pop from UI, then the view will pop from the navigation stack, I think is the problem of the animation serial.

I had the same problem on iOS 8.
I solved by subclassing UINavigationController and adding this code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
return [super popViewControllerAnimated:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
{
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
I basically block all the user interactions during the pop animation. I know it's a dirty solution, but it's the only one that I found that solves the problem.

I think that should be working without dispatch_async.
I got to the same issue, but i got to know the reason.
We should check it if the current scene is assigned to a proper view controller name in the storyboard.(identity inspector -> class)
If you connect a button action to m file and then insert the name of the view controller, that is not working.
So, you should delete the connect, and you insert the proper view controller name, and then you should connect the action to m file again.

I created my project from master-detail template, that uses split view controller. In my case, removing the split view controller resolved this issue.

It is important that that calls to popViewController(animated:), popToRootViewController(animated:) and related calls be made in the main queue, but under some conditions this doesn't seem to be good enough, and the animation doesn't occur.
I was able to fix it as described in some other answers here, performing the pop navigation later in the main queue. The Swift equivalent:
DispatchQueue.main.async {
self.rootViewController.popViewController(animated: true)
}
This might be explained by other animations that are still in progress, and by scheduling the block this way it happens at the end of the current work taking place or currently scheduled in the main queue, which allows the animation to execute correctly.

Try this code for popup a view controller from navigation stack
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count -2] animated:YES];

Related

UISplitViewController - Pop master when detail is popped (and vice versa)

I have this UISplitViewController which both master and detail VCs are UINavigationController subclasses.
The two are supposed to work "in synchrony", ie, when one pushes a new VC, the second has to push one too. When one pops, the other has to pop too. One always triggers the same action to the other.
I'm already able to handle the pushing part of the problem, since the push functions are explicit in each class I use.
Popping, on the other hand, has been a big problem. The action is triggered when user presses the back button, and I don't know how to detect this event. One possible solution is detecting the event.
Another solution I thought of was to override UINavigationController's - popViewControllerAnimated:, making one class pop the other class, just like this:
// On DetailNav
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
// Code to make MasterNav pop
return [super popViewControllerAnimated:animated];
}
// On MasterNav
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
// Code to make DetailNav pop
return [super popViewControllerAnimated:animated];
}
I didn't bother adding the full code because this is enough to notice that this approach would cause an infinite-loop, eventually popping both NavControllers to their roots (and then possibly crashing).
What is the best way to achieve the desired behavior?
For iOS 5+, - (BOOL)isMovingFromParentViewController does the trick:
// Use this in the detail VC
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// the view is being popped.
// so also pop the master VC
}
}
I found the solution on #Chrizzz's answer to another question.
Basically you need two subclasses of UINavigationController, one for master and one for detail.
In both subclasses, you must include UINavigationBarDelegate and set the delegate to self
. Then include the following method:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
[[[[self splitViewController] viewControllers][0 or 1] navigationController] performSelector:#selector(popViewControllerAnimated:) withObject:#YES afterDelay:0];
return YES;
}
In the master, you'll want to pop the detail VC, so put a 1 on the index.In the detail, you'll want to pop the master VC, so put a 0 on the index.
This solution allows you to run a routine before popping the view controller.
Update
I was getting some NavigationBar errors getting corrupted such as nested pop animation can result in corrupted navigation bar. So instead of directly calling popViewControllerAnimated: I called performSelector: with zero delay and nothing bad happens now when I pop my views.

iOS7 UINavigationController pushViewController:animated back to back with animation locks up the main thread

From what I can tell, it seems that pushing into UINavigationController back to back with animation created a dead lock on iOS7.
I initially struggled with a crash on iOS6 and came up with the solution of:
Create an array of view controllers to push onto the navigation controller after the initial push. So if there are three view controllers to be pushed (A, B and C) then this array would have B and C.
Implement UINavigationControllerDelegate's navigationController:didShowViewController:animated:
In the delegate method, simply check if there are more elements in the view controller array. If so, take the first element out of it and push that into the navigation controller.
So essentially if there are B and C view controllers to be pushed, navigationController:didShowViewController:animated: will be called three times; every time after the view controller gets pushed starting with A. Obviously, the last call wouldn't do anything since the array should be empty at that point.
Note that this approach worked fine on iOS6. However, this breaks in iOS7. It seems that when it tries to animate in the second push, the app freezes. After digging a little bit more I came up with the solution of pushing the second view controller in the following manner in the delegate implementation.
dispatch_async(dispatch_get_main_queue(), ^{
[navigationController pushViewController:viewController
animated:YES];
});
This seems to fix the problem, but I was wondering if anyone has experienced a similar thing and have a better explanation of what exactly is going on.
I'm not sure what problem you're having. I created a test app that has a navigation controller and four other controllers. The first controller has a button to push to the second controller, which is FirstPushedViewController. In that controller I have this code to push the next two controllers, and it worked fine in iOS 7:
#interface FirstPushedViewController ()
#property (strong,nonatomic) NSMutableArray *vcs;
#end
#implementation FirstPushedViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
UIViewController *orange = [self.storyboard instantiateViewControllerWithIdentifier:#"Orange"];
UIViewController *red = [self.storyboard instantiateViewControllerWithIdentifier:#"Red"];
self.vcs = [#[red,orange] mutableCopy];
}
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.vcs.count > 0) {
[self.navigationController pushViewController:self.vcs.lastObject animated:YES];
[self.vcs removeLastObject];
}
}

viewWillDisappear not called when calling popToRootViewControllerAnimated

I work on a legacy application, and have found out, that my view[Will/Did]Disappear methods are not always fired properly.
The case is, I have a (custom) UIViewController set as rootViewController in AppDelegate. This rootViewController has a UINavigationController, which has two view controllers pushed on it. When the user presses the home button, the user is logged out. When he later returns to the app, the application calls [UINavigationController popToRootViewControllerAnimated:YES] and then displays a modal UIViewController for logging in.
The problem is: When I push/pop on the UINavigationController normally, my viewWillDisappear method is called properly. But when I use the popToRootViewControllerAnimated: method, viewWillDisappear is not called on any of the viewControllers that are popped off.
Searching on the internet has only given two possible reasons:
If using a UINavigationController as a subview, you must call view[Will/Did]Disappear yourself
Not calling the proper super methods
None of these suggestions are the case in my app. And I have no idea where to look. Anybody has a suggestion to what has been done wrong in the app?
The view probably wasn't onscreen. It has to be onscreen (visible) for the viewWillDisappear: method to be called. If it's coming back from the background, it wasn't visible.
You could try using willMoveToParentViewController: which is called when the view controller is removed from its parent.
such useful to me
[nav performSelector:#selector(popToRootViewControllerAnimated:) withObject:nil afterDelay:0.0];
I rewrote UITabBarController
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UINavigationController *navigationController = [originalViewController as:[UINavigationController class]];
if (navigationController.presentedViewController) {
[navigationController dismissViewControllerAnimated:NO completion:^{
[navigationController popToRootViewControllerAnimated:NO];
}];
}else if (navigationController.topViewController){
[navigationController popToRootViewControllerAnimated:NO];
}
});
}

iOS: Pushing a view controller with animation only works once

I am creating an iPhone client for one of my apps that has an API. I am using the GTMOAuth2 library for authentication. The library takes care of opening a web view for me with the correct url. However I have to push the view controller myself. Let me show you some code to make things more clear:
- (void)signInWithCatapult
{
[self signOut];
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithAuthentication:[_account catapultAuthenticaiton]
authorizationURL:[NSURL URLWithString:kCatapultAuthURL]
keychainItemName:kCatapultKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[self.navigationController pushViewController:viewController animated:YES];
}
I have a "plus"/"add" button that I add to the view dynamically and that points to that method:
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(signInWithCatapult)];
When I press the "add" button, what is supposed to happen is to open the web view with an animation, and then add an account to the accounts instance variable which populates the table view. This works fine if I add one account, but as soon as I try to add a second account, the screen goes black and two errors appear in the console:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The only way that I found to avoid this problem was to disable animations when pushing the view controller.
What am I doing wrong please?
Typical situations
You push or pop controllers inside viewWillAppear: or similar methods.
You override viewWillAppear: (or similar methods) but you are not calling [super viewWillAppear:].
You are starting two animations at the same time, e.g. running an animated pop and then immediately running an animated push. The animations then collide. In this case, using [UINavigationController setViewControllers:animated:] must be used.
Have you tried the following for dismissing once you're in?
[self dismissViewControllerAnimated:YES completion:nil];
I got the nested pop animation can result in corrupted navigation bar message when I was trying to pop a view controller before it had appeared. Override viewDidAppear to set a flag in your UIViewController subclass indicating that the view has appeared (remember to call [super viewDidAppear] as well). Test that flag before you pop the controller. If the view hasn't appeared yet, you may want to set another flag indicating that you need to immediately pop the view controller, from within viewDidAppear, as soon as it has appeared. Like so:
#interface MyViewController : UIViewController {
bool didAppear, needToPop;
}
...and in the #implementation...
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
didAppear = YES;
if (needToPop)
[self.navigationController popViewControllerAnimated:YES];
}
- (void)myCrucialBackgroundTask {
// this task was presumably initiated when view was created or loaded....
...
if (myTaskFailed) { // o noes!
if (didAppear)
[self.navigationController popViewControllerAnimated:YES];
else
needToPop = YES;
}
}
The duplicated popViewControllerAnimated call is a bit ugly, but the only way I could get this to work in my currently-tired state.

"Unbalanced calls to begin/end appearance transitions for DetailViewController" when pushing more than one detail view controller

I have a view controller that contains a table view, the items in the table can be selected and a detail view controller duly created.
The items in the table represent items that can have a time based trigger associated with them and a local notification is scheduled for each item, if the app is in the foreground when a local notification expires then the detail view for the item is automatically displayed.
I have a problem that manifests when two notifications expire at the same time which results in the views not being displayed properly and in addition the console logs:
"Unbalanced calls to begin/end appearance transitions for NNN" where NNN is my detail view controller.
The table view controller is created as follows:
self.tableViewController = [[TableViewController alloc] initWithNibName:#"TableView" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.tableViewController];
self.window.rootViewController = navController;
When a local notification expires and didReceiveLocalNotification: is invoked the app broadcasts a notification using NSNotifcationCenter postNotificationName: and to which the table view controller is listening for. When the table view controller receives that notification it creates the detail view controller and pushes it to the stack as:
[self.navigationController pushViewController:detailViewController animated:YES];
I read somewhere that there could be a problem if a view controller pushes another view controller when it itself is not on the top of the stack - so I thought this must be the problem, because when the table view controller receives the 2nd notification it will no longer be on the top of the navigation stack because it will have previously just pushed a detail view controller onto the stack when the first notification arrived.
So I changed the push code to this:
[[self.navigationController topViewController].navigationController pushViewController:detailController animated:YES];
But it made no difference.
So I next thought there could be a problem because the first detail view controller was not getting the chance to fully display before the 2nd
view controller was pushed - so I changed my app's notification posting from using:
[[NSNotificationCenter defaultCenter] postNotificationName:
to
[[NSNotificationQueue defaultQueue] enqueueNotification: postingStyle:NSPostWhenIdle]
So that the pushes wouldn't occur within the same iteraction of the app loop. But that made no difference, nor did attempting to introduce a delay to the pushing of the detail view controlle:
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[[self.navigationController topViewController].navigationController pushViewController:detailController animated:YES];
});
I've no idea what the problem is or what to try next, any ideas?
"The unbalanced calls to begin/end appearance transitions"
occurs when you try and display a new viewcontroller before the current view controller is finished displaying. You can reproduce it by navigating in viewWillAppear.
Basically you are trying to push two view controllers onto the stack at almost the same time. Suggest you maintain a queue in the tableview controller which maintains a list of the detail views which need displaying. Push one at a time on to the stack and check on exit from the current detail view whether there are any queued detail views which need displaying.
This kind of navigation is going to be confusing for the user. It may be better to consider making your detail view support multiple items.
'Unbalanced calls to begin/end appearance transitions for '
Says an animation is started before the last related animation isnt done. So, are you popping any view controller before pushing the new one ? Or may be popping to root ? if yes try doing so without animation. i.e,
[self.navigationController popToRootViewControllerAnimated:NO];
And see if this resolves the issue, In my case this did the trick.
"The unbalanced calls to begin/end appearance transitions" error occurs when you try and display a new viewcontroller before the current view controller is finished displaying.
So, you have to be sure you wont present a new VC until the first finishes its animations.
Use didShowViewController and willShowViewController to block presenting a new VC before the old one completes its animation. It's for backButtonAction who makes a popViewController with Animation: YES.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[self.myNavView.backButton addTarget:self action:#selector(backButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[self.myNavView.backButton removeTarget:self action:#selector(backButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
In my case, I was implementing a custom container view controller, and I was getting the warning when swapping child view controllers, despite the fact that I was calling both beginAppearanceTransition(_:animated:) and endAppearanceTransition() on both the entering and the exiting view controllers.
I solved it by reordering the method calls for the entering view controller; from:
addChildViewController(newContent)
targetContainer.insertSubview(newContent.view, at: 0)
newContent.beginAppearanceTransition(true, animated: animated)
// Called AFTER adding subview
to:
// Called BEFORE adding subview
newContent.beginAppearanceTransition(true, animated: animated)
addChildViewController(newContent)
targetContainer.insertSubview(newContent.view, at: 0)
It can also happen when you try to pop a VC from the stack more than once. On my case, the method that popped the VC was mistakenly being called multiple times. Once I cleaned that up the issue went away.
Actually you need to wait till the push animation ends. So you can delegate UINavigationController and prevent pushing till the animation ends.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
waitNavigation = NO;
}
-(void)showGScreen:(id)gvc{
if (!waitNavigation) {
waitNavigation = YES;
[_nav popToRootViewControllerAnimated:NO];
[_nav pushViewController:gvc animated:YES];
}
}
As mentioned in above answers this error accuses when we try to push a view controller before the previous view controller is finish loading, so one solution is that try to push controller after it finish loading, i.e. move your push view controller code from viewWillAppear to viewDidAppear
You can add a breakpoint to
-[UIViewController _endAppearanceTransition:]
from where UIKit prints
"Unbalanced calls to begin/end appearance transitions for %#."
Look at these overloads:
If they are empty, then remark them and rebuild.
- (void) beginAppearanceTransition:(BOOL) isAppearing animated:(BOOL)animated {}
- (void) becomeActive:(NSNotification *) notification {}
In my case the problem is that I am presenting a viewController (alert) on an already presented viewController (full screen sheet).
UIKit walks up the viewController parent chain but not the presenting chain so there is a mismatch when reaching the presented and the window root view controller.
Manually calling beginAppearanceTransition in these cases made the message go away, though is seems rather a patch for a symptom than a real remedy for the error.
In React native I am getting this error while opening galley or camera screen. I set timeout before opening this screen and its works for me.
My UINavigationController was being presented, so had to replace UIModalPresentationCurrentContext with UIModalPresentationFullScreen on the presentation mechanism. Or you can use UIModalPresentationAutomatic if you have iOS 13.0 or more recent.
Are you using the appearance proxy feature?
I have found very problematic this feature, last time I have a
"Unbalanced calls to begin/end appearance transitions for"
I solved it removing the [[UITextField appearance] ..] methods.

Resources