Appels déséquilibrés pour commencer / terminer les transitions d'apparence pour <UITabBarController: 0x197870>

119

J'ai lu SO au sujet d'un autre utilisateur rencontrant une erreur similaire , mais cette erreur est dans un cas différent.

J'ai reçu ce message lorsque j'ai initialement ajouté un contrôleur de vue:

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

La structure de l'application est la suivante:

J'ai un TabBarController à 5 onglets lié à 5 contrôleurs de vue. Dans l'onglet d'affichage initial, j'appelle un nouveau contrôleur de vue à superposer en tant qu'introduction de l'application.

J'utilise ce code pour appeler le contrôleur de vue d'introduction:

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

Une fois que ce IntroVCcontrôleur de vue apparaît, l'erreur ci-dessus s'affiche.

ps J'utilise le SDK xCode 4.2 et iOS 5.0 pour développer l'application iOS 4.3.

Raptor
la source
Salut Shivan, j'ai le même problème avec toi. Mais je ne peux toujours pas le réparer après avoir vu les réponses ci-dessous. Puis-je savoir où vous appelez le contrôleur de vue d'introduction?
ZYiOS

Réponses:

98

Sans voir davantage le code environnant, je ne peux pas donner de réponse définitive, mais j'ai deux théories.

  1. Vous n'utilisez UIViewControllerest désigné initialiseurinitWithNibName:bundle: . Essayez de l'utiliser au lieu de simplement init.

  2. En outre, selfpeut être l' un des contrôleurs de vue de la barre onglet contrôleur. Présentez toujours les contrôleurs de vue depuis le contrôleur de vue le plus haut, ce qui signifie que dans ce cas, demandez au contrôleur de la barre d'onglets de présenter le contrôleur de vue de superposition au nom du contrôleur de vue. Vous pouvez toujours conserver les délégués de rappel vers le contrôleur de vue réel, mais vous devez avoir le contrôleur de la barre d'onglets présent et le fermer.

Jesper
la source
2
# 1 a corrigé ce problème pour moi, j'ai utilisé initWithNibName: nil bundle: nil au lieu de init.
Hua-Ying
172
Vous pouvez générer cet avertissement en présentant le vc modal avant que l'application ne soit initialisée. par exemple, démarrez une application de modèle d'application à onglets et présentez un vc modal au-dessus de self.tabBarController comme dernière ligne de l'application: didFinishLaunching. L'avertissement apparaît. Solution: laissez d'abord la pile se dérouler, présentez le vc modal dans une autre méthode, invoquée avec un performSelector withDelay: 0.0.
danh
9
Et voici une autre question qui explique pourquoi performSelector withDelay fonctionne. stackoverflow.com/questions/1922517/…
fatih
1
La solution de danh a fonctionné pour moi, mais j'ai dû utiliser 0,1 plutôt que 0,0.
Brandon O'Rourke
11
Plutôt que d'utiliser un performSelectorWithDelay de zéro, effectuez cette opération dans viewDidAppear plutôt que viewDidLoad ou autre.
tooluser
40

J'ai corrigé cette erreur en changeant l'animation de OUI à NON.

De:

[tabBarController presentModalViewController:viewController animated:YES];

À:

[tabBarController presentModalViewController:viewController animated:NO];
PokerIncome.com
la source
4
Cela résout le problème si vous ne vous souciez pas de l'animation, mais si vous avez besoin d'animation: OUI, essayez le commentaire de danh sur la réponse acceptée: stackoverflow.com/questions/7886096/…
wxactly
3
Pour info: presentModalViewController: animated: était obsolète dans iOS6.
ZS
16

Publié par danh

Vous pouvez générer cet avertissement en présentant le vc modal avant que l'application ne soit initialisée. par exemple, démarrez une application de modèle d'application à onglets et présentez un vc modal au-dessus de self.tabBarController comme dernière ligne de l'application: didFinishLaunching. L'avertissement apparaît. Solution: laissez la pile se dérouler en premier, présentez le vc modal dans une autre méthode, appelée avec un performSelector withDelay: 0.0

Essayez de déplacer la méthode dans viewWillAppear et gardez-la pour qu'elle ne soit exécutée qu'une seule fois (nous vous recommandons de configurer une propriété)

Peter Lapisu
la source
Pourquoi viewWillAppearet pas viewDidAppear?
CyberMew
6

Une autre solution dans de nombreux cas est de s'assurer que la transition entre UIViewControllers se produit après la fin de la procédure non appropriée (comme lors de l'initialisation), en faisant:

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

Ceci est général pour aussi pushViewController:animated:, etc.

mllm
la source
4

J'ai eu le même problème. J'ai appelé une méthode à l' viewDidLoadintérieur de mon premierUIViewController

- (void)viewDidLoad{
    [super viewDidLoad];

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

- (void)loadingView{

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

Dans le second, UIViewControllerj'ai fait la même chose avec un retard de 0,5 seconde. Après avoir changé le délai à une valeur plus élevée, cela a bien fonctionné. C'est comme si la séquence ne pouvait pas être effectuée trop vite après une autre séquence.

Alex Cio
la source
7
La méthode viewDidAppear du cycle de vie de la vue est fournie exactement dans ce but et serait plus fiable que l'introduction d'un délai artificiel, fwiw.
tooluser
1
C'est la bonne réponse sauf qu'un délai de 0 suffit pour attendre que le contrôleur de navigation soit prêt pour une nouvelle navigation.
malhal
C'est tout à fait vrai, vous devez l'appeler à l'intérieur viewDidAppearpour que le UINavigationControllersoit prêt à le gérer. J'ai changé mon message pour ceci;)
Alex Cio
Je pense que cela devrait être déplacé vers viewWillAppear, alors vous n'avez pas à vous soucier de savoir si la vue a été initialisée ou non.
horsejockey
3

J'ai eu le même problème lorsque je devais présenter mon contrôleur de vue de connexion à partir d'un autre contrôleur de vue.Si l'utilisateur n'est pas autorisé, je l'ai fait dans la méthode ViewDidLoad de mon autre contrôleur de vue (s'il n'est pas autorisé -> presentModalViewController). Quand je commence à le faire dans la méthode ViewDidAppear, j'ai résolu ce problème. Je pense que ViewDidLoad n'initialise que les propriétés et après cela, l'algorithme d'affichage réel commence! C'est pourquoi vous devez utiliser la méthode viewDidAppear pour effectuer des transitions modales!

Tolusha
la source
3

J'ai eu ce problème à cause d'une faute de frappe:

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

au lieu de

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

Il appelait "WillAppear" dans le super au lieu de "DidAppear"

Adriano Spadoni
la source
2

J'ai eu beaucoup de problèmes avec le même problème. J'ai résolu celui-ci en

  1. Lancement de ViewController à l'aide de la méthode storyboad instantiateViewControllerWithIdentifier. c'est à direIntro *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"introVC"];
  2. [self.tabBarController presentModalViewController : vc animated:YES];

J'ai le viewcontroller dans mon storyboard, pour une raison quelconque, utiliser uniquement [[introvc alloc] init];ne fonctionnait pas pour moi.

Mogambolal
la source
1
ravi de vous voir utiliser la nouvelle fonctionnalité de storyboard. mais je n'utilisais pas de storyboard dans mon cas ...
Raptor
Je voulais juste souligner que "instantiateViewControllerWithIdentifier" prend l'identificateur du contrôleur. pour plus de détails, consultez stackoverflow.com/questions/8186375/…
Kishor Kundan
2

Je l'ai résolu en écrivant

[self.navigationController presentViewController:viewController 
                                        animated:TRUE 
                                      completion:NULL];
pankesh
la source
3
Pour votre information, pour être plus idiomatique (et plus sûr!), Vous devriez le faire: animé: OUI achèvement: nil
powerj1984
2
Je vais vous accorder plus idiomatique, mais comment est-ce plus sûr?
Zev Eisenberg
2

J'ai eu ce problème avec un code tiers. Quelqu'un a oublié de définir le super intérieur de viewWillAppear et viewWillDisappear dans une classe TabBarController personnalisée.

- (void) viewWillAppear:(BOOL)animated {

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

or

- (void) viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];
    // code...
}
J. Lopes
la source
2

Si vous utilisez transitioningDelegate(ce n'est pas le cas dans l'exemple de cette question), définissez également modalPresentationStylesur .Custom.

Rapide

let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom
Kof
la source
1

J'ai eu la même erreur. J'ai une barre d'onglets avec 3 éléments et j'essayais inconsciemment d'appeler le contrôleur de vue racine de l'élément 1 dans l'élément 2 de ma barre d'onglets en utilisant performSegueWithIdentifier.

Ce qui se passe, c'est qu'il appelle le contrôleur de vue et retourne au contrôleur de vue racine de l'élément 2 après quelques secondes et enregistre cette erreur.

Apparemment, vous ne pouvez pas appeler le contrôleur de vue racine d'un élément vers un autre élément.

Donc au lieu de performSegueWithIdentifier

j'ai utilisé [self.parentViewController.tabBarController setSelectedIndex:0];

J'espère que cela aide quelqu'un.

Gellie Ann
la source
1

J'ai eu le même problème et j'ai pensé que je publierais au cas où quelqu'un d'autre rencontrerait quelque chose de similaire.

Dans mon cas, j'avais attaché un outil de reconnaissance de geste de pression longue à mon UITableViewController.

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

Dans mon sélecteur onLongPress, j'ai lancé mon prochain contrôleur de vue.

- (IBAction)onLongPress:(id)sender {

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

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

    [page release];

}

Dans mon cas, j'ai reçu le message d'erreur parce que le module de reconnaissance de pression longue s'est déclenché plus d'une fois et, par conséquent, mon "SomeViewController" a été poussé plusieurs fois sur la pile.

La solution était d'ajouter un booléen pour indiquer quand le SomeViewController avait été poussé sur la pile. Lorsque la méthode viewWillAppear de mon UITableViewController a été appelée, j'ai remis le booléen à NO.

Dale Moore
la source
1

J'ai trouvé que, si vous utilisez un storyboard, vous voudrez mettre le code qui présente le nouveau contrôleur de vue dans viewDidAppear. Il supprimera également l'avertissement «La présentation de contrôleurs de vue sur des contrôleurs de vue détachés est déconseillée».

Dan Levy
la source
1

Dans Swift 2+ pour moi fonctionne:

J'ai UITabBarViewController dans le storyboard et j'avais sélectionné la propriété d'Index comme ceci:

entrez la description de l'image ici

Mais je le supprime et ajoute dans ma méthode viewDidLoad de ma classe initiale, comme ceci:

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

J'espère que je peux aider quelqu'un.

Dasoga
la source
0

En fait, vous devez attendre la fin de l'animation push. Ainsi, vous pouvez déléguer UINavigationController et empêcher de pousser jusqu'à la fin de l'animation.

- (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
la source
Je l'appelle quand une cellule est sélectionnée. Cela dépend de vous en fait
ymutlu
0

Comme @danh l'a suggéré, mon problème était que je présentais le vc modal avant que le ne UITabBarControllersoit prêt. Cependant, je me sentais mal à l'aise de compter sur un délai fixe avant de présenter le contrôleur de vue (à partir de mes tests, j'avais besoin d'utiliser un délai de 0,05-0,1 s performSelector:withDelay:). Ma solution est d'ajouter un bloc qui est appelé sur UITabBarControllerla viewDidAppear:méthode de:

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

Maintenant en 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
la source
0

vous devez vous assurer que - (void) beginAppearanceTransition: (BOOL) isAppearing animated: (BOOL) animated et - (void) endAppearanceTransition est créé ensemble dans la classe.

zszen
la source
0

J'ai eu le même problème. Lors du développement, je voulais contourner les écrans. Je naviguais d'un contrôleur de vue à un autre dans viewDidLoad en appelant une méthode de sélection.

Le problème est que nous devons laisser le ViewController terminer la transition avant de passer à un autre ViewController.

Cela a résolu mon problème: le délai est nécessaire pour permettre aux ViewControllers de terminer la transition avant de passer à un autre.

self.perform(#selector(YOUR SELECTOR METHOD), with: self, afterDelay: 0.5)
codeedoc
la source
0

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
la source
-1

J'ai eu ce problème lorsque j'avais navigué de la racine TVC à TVC A puis à TVC B.Après avoir appuyé sur le bouton "charger" dans TVC BI voulait revenir directement à la racine TVC (pas besoin de revoir TVC A alors pourquoi le faire) . J'ai eu:

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

... qui a donné l'erreur "Appels déséquilibrés pour commencer / terminer etc". Ce qui suit a corrigé l'erreur, mais aucune animation:

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

C'était ma solution finale, sans erreur et toujours animée:

//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
la source
-1

J'ai rencontré cette erreur lorsque j'ai accroché un UIButton à une action de segue de storyboard (dans IB) mais j'ai ensuite décidé que le bouton appelle par programme en performSegueWithIdentifieroubliant de supprimer le premier d'IB.

Essentiellement, il a effectué l'appel de segue deux fois, a donné cette erreur et a en fait poussé mon point de vue deux fois. Le correctif consistait à supprimer l'un des appels de segue.

J'espère que cela aide quelqu'un d'aussi fatigué que moi!

capikaw
la source