132

I read SO about another user encountering similar error, but this error is in different case.

I received this message when I added a View Controller initially:

Unbalanced calls to begin/end appearance transitions for 
<UITabBarController: 0x197870>

The structure of the app is as follow:

I got a 5-tab TabBarController linked to 5 View Controllers. In the initial showing tab, I call out a new View Controller to overlay as an introduction of the app.

I use this code to call the introduction view controller:

IntroVC *vc = [[IntroVC alloc] init];
[self presentModalViewController:vc animated:YES];
[vc release]; 

After this IntroVC view controller shows up, the above error shows.

p.s. I am using xCode 4.2 & iOS 5.0 SDK, developing iOS 4.3 app.

Community
  • 1
  • 1
Raptor
  • 53,206
  • 45
  • 230
  • 366
  • Hi Shivan, I have the same problem with you. But I still can't fix it after view below answers. May I know where you call the introduction view controller? – ZYiOS Dec 15 '11 at 09:49

24 Answers24

98

Without seeing more of the surrounding code I can't give a definite answer, but I have two theories.

  1. You're not using UIViewController's designated initializer initWithNibName:bundle:. Try using it instead of just init.

  2. Also, self may be one of the tab bar controller's view controllers. Always present view controllers from the topmost view controller, which means in this case ask the tab bar controller to present the overlay view controller on behalf of the view controller. You can still keep any callback delegates to the real view controller, but you must have the tab bar controller present and dismiss.

Jesper
  • 7,477
  • 4
  • 40
  • 57
  • 2
    #1 fixed this problem for me, I used initWithNibName:nil bundle:nil instead of init. – Hua-Ying Oct 31 '11 at 21:15
  • Getting in the habit of using the designated initializer (or convenience initializers in the same class) is never a bad thing. Some classes don't override init to call through the designated initializer and the object you get back won't be properly initialized. – Jesper Nov 01 '11 at 08:48
  • 179
    You can generate this warning by presenting the modal vc before the app is done initializing. i.e. Start a tabbed application template app and present a modal vc on top of self.tabBarController as the last line in application:didFinishLaunching. Warning appears. Solution: let the stack unwind first, present the modal vc in another method, invoked with a performSelector withDelay:0.0. – danh Feb 11 '12 at 03:21
  • 10
    And here is another question explains why performSelector withDelay works. http://stackoverflow.com/questions/1922517/how-does-performselectorwithobjectafterdelay-work – fatih Jul 24 '12 at 17:04
  • 2
    danh's solution worked for me, but I had to use 0.1 rather than 0.0. – Brandon O'Rourke Sep 11 '12 at 15:53
  • I had this issue when I was presenting (with animation) a modal view and at the same time, immediately popping the the navigation controller - i.e. because if the person cancelled on the modal view, I didn't want them to be on the same view they came from any more, since in this app that would be slightly confusing. The delay method worked for me as follows: [self.navigationController performSelector:@selector(popViewControllerAnimated:) withObject:NO afterDelay:0.5]; – adriandz Mar 28 '13 at 15:45
  • 11
    Rather than use a performSelectorWithDelay of zero, perform this in viewDidAppear rather than viewDidLoad or whatnot. – tooluser Jan 31 '14 at 00:15
40

I fixed this error by changing animated from YES to NO.

From:

[tabBarController presentModalViewController:viewController animated:YES];

To:

[tabBarController presentModalViewController:viewController animated:NO];
PokerIncome.com
  • 1,708
  • 2
  • 19
  • 30
  • 4
    This fixes the problem if you don't care about animation, but if you need animated:YES, try danh's comment on the accepted answer: http://stackoverflow.com/questions/7886096/unbalanced-calls-to-begin-end-appearance-transitions-for-uitabbarcontroller-0x#comment11635438_7886348 – wxactly Mar 06 '13 at 20:52
  • 3
    FYI: presentModalViewController:animated: was deprecated in iOS6. – Z S Sep 12 '14 at 20:22
  • For me it fixed it if I changed animation from no to yes....hmm. – Eric Nov 26 '20 at 02:43
16

As posted by danh

You can generate this warning by presenting the modal vc before the app is done initializing. i.e. Start a tabbed application template app and present a modal vc on top of self.tabBarController as the last line in application:didFinishLaunching. Warning appears. Solution: let the stack unwind first, present the modal vc in another method, invoked with a performSelector withDelay:0.0

Try to move the method into the viewWillAppear and guard it so it does get executed just once (would recommend setting up a property)

Community
  • 1
  • 1
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
7

Another solution for many cases is to make sure that the transition between UIViewControllers happens after the not-suitable (like during initialization) procedure finishes, by doing:

__weak MyViewController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
    [weakSelf presentViewController:vc animated:YES];
});

This is general for also pushViewController:animated:, etc.

mllm
  • 17,068
  • 15
  • 53
  • 64
4

I had the same problem. I called a method inside viewDidLoad inside my first UIViewController

- (void)viewDidLoad{
    [super viewDidLoad];

    [self performSelector:@selector(loadingView)
               withObject:nil afterDelay:0.5];
}

- (void)loadingView{

    [self performSegueWithIdentifier:@"loadedData" sender:self];
}

Inside the second UIViewController I did the same also with 0.5 seconds delay. After changing the delay to a higher value, it worked fine. It's like the segue can't be performed too fast after another segue.

Community
  • 1
  • 1
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
  • 7
    The view lifecycle method viewDidAppear is provided for exactly this purpose, and would be more reliable than introducing an artificial delay, fwiw. – tooluser Jan 31 '14 at 00:16
  • 1
    This is the right answer except a delay of 0 is enough to wait until the navigation controller is ready for a new navigation. – malhal Feb 28 '14 at 18:56
  • It is totally right, you have to call it inside `viewDidAppear` so the `UINavigationController` is ready to handle it. I changed my post to this ;) – Alex Cio Mar 02 '14 at 00:21
  • I feel like this should be moved to viewWillAppear then you dont have to worry about wether the view has initialized or not. – horsejockey Apr 27 '15 at 20:58
3

I had the same problem when I need to Present My Login View Controller from another View Controller If the the User is't authorized, I did it in ViewDidLoad Method of my Another View Controller ( if not authorized -> presentModalViewController ). When I start to make it in ViewDidAppear method, I solved this problem. I Think that ViewDidLoad only initialize properties and after that the actual showing view algorithm begins! Thats why you must use viewDidAppear method to make modal transitions!

Tolusha
  • 39
  • 1
3

I had this problem because of a typo:

override func viewDidAppear(animated: Bool) {
    super.viewWillAppear(animated)

instead of

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

It was calling "WillAppear" in the super instead of "DidAppear"

shim
  • 9,289
  • 12
  • 69
  • 108
Adriano Spadoni
  • 4,540
  • 1
  • 25
  • 25
3

If you're using transitioningDelegate (not the case in this question's example), also set modalPresentationStyle to .Custom.

Swift

let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Kof
  • 23,893
  • 9
  • 56
  • 81
2

I solved it by writing

[self.navigationController presentViewController:viewController 
                                        animated:TRUE 
                                      completion:NULL];
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
pankesh
  • 29
  • 2
2

I had this problem with a third party code. Someone forgot to set the super inside of viewWillAppear and viewWillDisappear in a custom TabBarController class.

- (void) viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];
    // code...
}

or

- (void) viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];
    // code...
}
J. Lopes
  • 1,336
  • 16
  • 27
2

I had lot of problem with the same issue. I solved this one by

  1. Initiating the ViewController using the storyboad instantiateViewControllerWithIdentifier method. i.e Intro *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"introVC"];
  2. [self.tabBarController presentModalViewController : vc animated:YES];

I have the viewcontroller in my storyboard, for some reason using only [[introvc alloc] init]; did not work for me.

Oleks
  • 31,955
  • 11
  • 77
  • 132
Mogambolal
  • 29
  • 1
  • 1
    nice to see you using new storyboarding feature. but I was not using storyboard in my case... – Raptor Nov 07 '11 at 02:30
  • Just wanted to point this out that "instantiateViewControllerWithIdentifier" takes controller's Identifier. for more details check out http://stackoverflow.com/questions/8186375/storyboard-refer-to-viewcontroller-in-appdelegate – Kishor Kundan Sep 15 '12 at 16:37
1

I had the same error. I have a tab bar with 3 items and I was unconsciously trying to call the root view controller of item 1 in the item 2 of my tab bar using performSegueWithIdentifier.

What happens is that it calls the view controller and goes back to the root view controller of item 2 after a few seconds and logs that error.

Apparently, you cannot call the root view controller of an item to another item.

So instead of performSegueWithIdentifier

I used [self.parentViewController.tabBarController setSelectedIndex:0];

Hope this helps someone.

Gellie Ann
  • 439
  • 1
  • 6
  • 10
1

I had the same problem and thought I would post in case someone else runs into something similar.

In my case, I had attached a long press gesture recognizer to my UITableViewController.

UILongPressGestureRecognizer *longPressGesture = [[[UILongPressGestureRecognizer alloc]
                                                   initWithTarget:self
                                                   action:@selector(onLongPress:)]
                                                  autorelease];
[longPressGesture setMinimumPressDuration:1];
[self.tableView addGestureRecognizer:longPressGesture];

In my onLongPress selector, I launched my next view controller.

- (IBAction)onLongPress:(id)sender {

    SomeViewController* page = [[SomeViewController alloc] initWithNibName:@"SomeViewController" bundle:nil];

    [self.navigationController pushViewController:page animated:YES];

    [page release];

}

In my case, I received the error message because the long press recognizer fired more than one time and as a result, my "SomeViewController" was pushed onto the stack multiple times.

The solution was to add a boolean to indicate when the SomeViewController had been pushed onto the stack. When my UITableViewController's viewWillAppear method was called, I set the boolean back to NO.

Dale Moore
  • 985
  • 7
  • 10
1

I found that, if you are using a storyboard, you will want to put the code that is presenting the new view controller in viewDidAppear. It will also get rid of the "Presenting view controllers on detached view controllers is discouraged" warning.

Dan Levy
  • 3,931
  • 4
  • 28
  • 48
1

In Swift 2+ for me works:

I have UITabBarViewController in storyboard and I had selectedIndex property like this:

enter image description here

But I delete it, and add in my viewDidLoad method of my initial class, like this:

override func viewDidLoad() {
   super.viewDidLoad()
   self.tabBarController?.selectedIndex = 2
}

I hope I can help someone.

Dasoga
  • 5,489
  • 4
  • 33
  • 40
1

This error will be displayed when trying to present an UINavigationController that is lazily initialized via a closure.

nsinvocation
  • 7,559
  • 3
  • 41
  • 46
0

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];
    }
}
ymutlu
  • 6,585
  • 4
  • 35
  • 47
0

As @danh suggested, my issue was that I was presenting the modal vc before the UITabBarController was ready. However, I felt uncomfortable relying on a fixed delay before presenting the view controller (from my testing, I needed to use a 0.05-0.1s delay in performSelector:withDelay:). My solution is to add a block that gets called on UITabBarController's viewDidAppear: method:

PRTabBarController.h:

@interface PRTabBarController : UITabBarController

@property (nonatomic, copy) void (^viewDidAppearBlock)(BOOL animated);

@end

PRTabBarController.m:

#import "PRTabBarController.h"

@implementation PRTabBarController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (self.viewDidAppearBlock) {
        self.viewDidAppearBlock(animated);
    }
}

@end

Now in application:didFinishLaunchingWithOptions:

PRTabBarController *tabBarController = [[PRTabBarController alloc] init];

// UIWindow initialization, etc.

__weak typeof(tabBarController) weakTabBarController = tabBarController;
tabBarController.viewDidAppearBlock = ^(BOOL animated) {
    MyViewController *viewController = [MyViewController new];
    viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    [weakTabBarController.tabBarController presentViewController:navigationController animated:NO completion:nil];
    weakTabBarController.viewDidAppearBlock = nil;
};
johnboiles
  • 3,494
  • 1
  • 32
  • 26
0

you need make sure -(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated and -(void)endAppearanceTransition is create together in the class.

zszen
  • 1,428
  • 1
  • 14
  • 18
0

I had the same issue. When developing I wanted to bypass screens. I was navigating from one view controller to another in viewDidLoad by calling a selector method.

The issue is that we should let the ViewController finish transitioning before transitioning to another ViewController.

This solved my problem: The delay is necessary to allow ViewControllers finish transitioning before transitioning to another.

self.perform(#selector(YOUR SELECTOR METHOD), with: self, afterDelay: 0.5)

codeedoc
  • 1
  • 1
0

For me this error occurred because i didn't have UIWindow declared in the upper level of my class when setting a root view controller

            rootViewController?.showTimeoutAlert = showTimeOut
            let navigationController = SwipeNavigationController(rootViewController: rootViewController!)
            self.window = UIWindow(frame: UIScreen.main.bounds)
            self.window?.rootViewController = navigationController
            self.window?.makeKeyAndVisible()

Ex if I tried declaring window in that block of code instead of referencing self then I would receive the error

grantespo
  • 2,233
  • 2
  • 24
  • 62
-1

I had this problem when I had navigated from root TVC to TVC A then to TVC B. After tapping the "load" button in TVC B I wanted to jump straight back to the root TVC (no need to revisit TVC A so why do it). I had:

//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:YES];
//Pop self to return to root
[self.navigationController popViewControllerAnimated:YES];

...which gave the error "Unbalanced calls to begin/end etc". The following fixed the error, but no animation:

//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root
[self.navigationController popViewControllerAnimated:NO];

This was my final solution, no error and still animated:

//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root, only works if first pop above is *not* animated
[self.navigationController popViewControllerAnimated:YES];
dawid
  • 374
  • 5
  • 9
-1

I encountered this error when I hooked a UIButton to a storyboard segue action (in IB) but later decided to have the button programatically call performSegueWithIdentifier forgetting to remove the first one from IB.

In essence it performed the segue call twice, gave this error and actually pushed my view twice. The fix was to remove one of the segue calls.

Hope this helps someone as tired as me!

capikaw
  • 12,232
  • 2
  • 43
  • 46
-1

Swift 5

 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {


//Delete or comment the below lines on your SceneDelegate.

//        guard let windowScene = (scene as? UIWindowScene) else { return }
//        window?.windowScene = windowScene
//        window?.makeKeyAndVisible()

        let viewController = ListVC()
        let navViewController = UINavigationController(rootViewController: viewController)
        window?.rootViewController = navViewController

    }
Marlhex
  • 1,870
  • 19
  • 27