Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ios

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?

like image 938
Gruntcakes Avatar asked Jan 31 '12 23:01

Gruntcakes


4 Answers

"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.

like image 68
railwayparade Avatar answered Oct 19 '22 21:10

railwayparade


'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.

like image 12
infiniteLoop Avatar answered Oct 19 '22 20:10

infiniteLoop


"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];
}
like image 7
Gabriel Avatar answered Oct 19 '22 19:10

Gabriel


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)
like image 4
Nicolas Miari Avatar answered Oct 19 '22 20:10

Nicolas Miari