Chamadas não balanceadas para iniciar / terminar transições de aparência para <UITabBarController: 0x197870>


119

Li SO sobre outro usuário que encontrou um erro semelhante , mas esse erro ocorre em um caso diferente.

Recebi esta mensagem quando adicionei um View Controller inicialmente:

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

A estrutura do aplicativo é a seguinte:

Eu tenho um TabBarController de 5 guias vinculado a 5 controladores de visualização. Na guia de exibição inicial, chamo um novo View Controller para sobrepor como uma introdução ao aplicativo.

Eu uso este código para chamar o controlador de visualização de introdução:

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

Após este IntroVCcontrolador de visualização aparecer, o erro acima será mostrado.

ps Estou usando o xCode 4.2 e iOS 5.0 SDK, desenvolvendo o aplicativo iOS 4.3.


Oi Shivan, tenho o mesmo problema com você. Mas ainda não consigo consertar depois de ver as respostas abaixo. Posso saber onde você chama o controlador de visualização de introdução?
— ZYiOS

Respostas:


98

Sem ver mais do código circundante, não posso dar uma resposta definitiva, mas tenho duas teorias.

  1. Você não está usando UIViewControllero inicializador designado deinitWithNibName:bundle: . Tente usá-lo em vez de apenas init.

  2. Além disso, selfpode ser um dos controladores de visualização do controlador da barra de guias. Sempre apresente controladores de visualização do controlador de visualização superior, o que significa, neste caso, pedir ao controlador da barra de guias para apresentar o controlador de visualização de sobreposição em nome do controlador de visualização. Você ainda pode manter qualquer delegado de retorno de chamada para o controlador de exibição real, mas deve ter o controlador da barra de guias presente e dispensar.


2
# 1 corrigiu este problema para mim, usei initWithNibName: nil bundle: nil em vez de init.
— Hua-Ying,

172
Você pode gerar esse aviso apresentando o vc modal antes que a inicialização do aplicativo seja concluída. ou seja, inicie um aplicativo de modelo de aplicativo com guias e apresente um vc modal no topo de self.tabBarController como a última linha no aplicativo: didFinishLaunching. Aviso aparece. Solução: deixe a pilha desenrolar primeiro, apresente o modal vc em outro método, invocado com um performSelector withDelay: 0.0.
— danh

9
E aqui está outra pergunta que explica por que performSelector withDelay funciona. stackoverflow.com/questions/1922517/…
— fatih

1
A solução de danh funcionou para mim, mas eu tive que usar 0.1 em vez de 0.0.
— Brandon O'Rourke

11
Em vez de usar um performSelectorWithDelay de zero, execute isso em viewDidAppear em vez de viewDidLoad ou outros enfeites.
— usuário da ferramenta

40

Corrigi esse erro alterando animado de SIM para NÃO.

De:

[tabBarController presentModalViewController:viewController animated:YES];

Para:

[tabBarController presentModalViewController:viewController animated:NO];

4
Isso corrige o problema se você não se preocupa com animação, mas se precisa de animação: SIM, tente o comentário de danh sobre a resposta aceita: stackoverflow.com/questions/7886096/…
— wxactly

3
Para sua informação: presentModalViewController: animado: tornou-se obsoleto no iOS6.
— ZS

16

Postado por danh

Você pode gerar esse aviso apresentando o vc modal antes que a inicialização do aplicativo seja concluída. ou seja, inicie um aplicativo de modelo de aplicativo com guias e apresente um vc modal no topo de self.tabBarController como a última linha no aplicativo: didFinishLaunching. Aviso aparece. Solução: deixe a pilha se desenrolar primeiro, apresente o modal vc em outro método, invocado com um performSelector withDelay: 0.0

Tente mover o método para viewWillAppear e guarde-o para que seja executado apenas uma vez (recomendaria configurar uma propriedade)


Por que viewWillAppeare não viewDidAppear?
— CyberMew

6

Outra solução para muitos casos é garantir que a transição entre UIViewControllers aconteça após o procedimento não adequado (como durante a inicialização) terminar, fazendo:

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

Isso é geral para também pushViewController:animated:etc.


4

Eu tive o mesmo problema. Eu chamei um método viewDidLoaddentro do meu primeiroUIViewController

- (void)viewDidLoad{
    [super viewDidLoad];

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

- (void)loadingView{

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

Dentro do segundo UIViewControllerfiz o mesmo também com 0,5 segundos de atraso. Depois de alterar o atraso para um valor mais alto, funcionou bem. É como se a sequência não pudesse ser realizada muito rápido após outra sequência.


7
O método de ciclo de vida da visualização viewDidAppear é fornecido exatamente para esse propósito e seria mais confiável do que introduzir um atraso artificial, fwiw.
— usuário da ferramenta

1
Esta é a resposta certa, exceto que um atraso de 0 é suficiente para esperar até que o controlador de navegação esteja pronto para uma nova navegação.
— malhal

É totalmente certo, você tem que chamar para dentro viewDidAppearpara que UINavigationControlleresteja pronto para lidar com isso. Mudei minha postagem para esta;)
— Alex Cio

Eu sinto que isso deve ser movido para viewWillAppear, então você não precisa se preocupar se a visualização foi inicializada ou não.
— Horsejockey

3

Tive o mesmo problema quando preciso apresentar meu Login View Controller de outro View Controller. Se o usuário não estiver autorizado, fiz isso no método ViewDidLoad de meu outro View Controller (se não autorizado -> presentModalViewController). Quando comecei a fazer no método ViewDidAppear, resolvi esse problema. Acho que o ViewDidLoad inicializa apenas as propriedades e depois disso o algoritmo de exibição real começa! É por isso que você deve usar o método viewDidAppear para fazer transições modais!


3

Tive este problema devido a um erro de digitação:

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

ao invés de

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

Ele estava chamando "WillAppear" no super em vez de "DidAppear"


2

Eu tive muitos problemas com o mesmo problema. Eu resolvi este por

  1. Iniciando o ViewController usando o método storyboad instantiateViewControllerWithIdentifier. ieIntro *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"introVC"];
  2. [self.tabBarController presentModalViewController : vc animated:YES];

Eu tenho o viewcontroller no meu storyboard, por algum motivo, usar only [[introvc alloc] init];não funcionou para mim.


1
bom ver você usando o novo recurso de storyboard. mas eu não estava usando storyboard no meu caso ...
— Raptor

Só queria salientar que "instantiateViewControllerWithIdentifier" leva o identificador do controlador. para mais detalhes, acesse stackoverflow.com/questions/8186375/…
— Kishor Kundan

2

Eu resolvi escrevendo

[self.navigationController presentViewController:viewController 
                                        animated:TRUE 
                                      completion:NULL];

3
Para sua informação, para ser mais idiomático (e mais seguro!), Você deve fazer: animado: SIM conclusão: nil
— powerj1984

2
Vou conceder a você mais idiomática, mas como é mais seguro?
— Zev Eisenberg

2

Tive este problema com um código de terceiros. Alguém se esqueceu de definir o super dentro de viewWillAppear e viewWillDisappear em uma classe TabBarController personalizada.

- (void) viewWillAppear:(BOOL)animated {

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

or

- (void) viewWillDisappear:(BOOL)animated {

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

2

Se você estiver usando transitioningDelegate(não é o caso no exemplo desta pergunta), defina também modalPresentationStylecomo .Custom.

Rápido

let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom

1

Eu tive o mesmo erro. Eu tenho uma barra de guias com 3 itens e estava inconscientemente tentando chamar o controlador de visualização raiz do item 1 no item 2 da minha barra de guias usando performSegueWithIdentifier.

O que acontece é que ele chama o controlador de visualização e volta ao controlador de visualização raiz do item 2 após alguns segundos e registra esse erro.

Aparentemente, você não pode chamar o controlador de visualização raiz de um item para outro item.

Então, em vez de performSegueWithIdentifier

eu usei [self.parentViewController.tabBarController setSelectedIndex:0];

Espero que isso ajude alguém.


1

Tive o mesmo problema e pensei em postar caso alguém encontrasse algo parecido.

No meu caso, anexei um reconhecedor de gestos para pressionar longamente ao meu UITableViewController.

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

Em meu seletor onLongPress, iniciei meu próximo controlador de visualização.

- (IBAction)onLongPress:(id)sender {

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

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

    [page release];

}

No meu caso, recebi a mensagem de erro porque o reconhecedor de pressionamento longo disparou mais de uma vez e, como resultado, meu "SomeViewController" foi colocado na pilha várias vezes.

A solução foi adicionar um booleano para indicar quando SomeViewController havia sido colocado na pilha. Quando o método viewWillAppear do meu UITableViewController foi chamado, eu defini o booleano de volta para NÃO.


1

Descobri que, se você estiver usando um storyboard, deverá colocar o código que está apresentando o novo controlador de visualização em viewDidAppear. Ele também eliminará o aviso "Não é recomendado apresentar controladores de visualização em controladores de visualização desconectados".


1

No Swift 2+ para mim funciona:

Tenho UITabBarViewController no storyboard e selecionei a propriedade Index como esta:

insira a descrição da imagem aqui

Mas eu o excluo e adiciono meu método viewDidLoad da minha classe inicial, assim:

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

Espero poder ajudar alguém.


0

Na verdade, você precisa esperar até que a animação push termine. Assim, você pode delegar UINavigationController e evitar o push até que a animação termine.

- (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];
    }
}

Eu chamo quando um celular selecionado. Na verdade, depende de você
— ymutlu

0

Como @danh sugeriu, meu problema era que eu estava apresentando o modal vc antes que ele UITabBarControllerestivesse pronto. No entanto, eu me senti desconfortável em confiar em um atraso fixo antes de apresentar o controlador de visualização (em meus testes, precisei usar um atraso de 0,05-0,1s performSelector:withDelay:). Minha solução é adicionar um bloco que é chamado de UITabBarController's viewDidAppear:método:

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

Agora em 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;
};

0

você precisa certificar-se de que - (void) beginAppearanceTransition: (BOOL) isAppearing animated: (BOOL) animated e - (void) endAppearanceTransition é criado junto na classe.


0

Eu tive o mesmo problema. Ao desenvolver, queria ignorar as telas. Eu estava navegando de um controlador de visualização para outro em viewDidLoad chamando um método seletor.

O problema é que devemos deixar o ViewController terminar a transição antes de fazer a transição para outro ViewController.

Isso resolveu meu problema: o atraso é necessário para permitir que ViewControllers concluam a transição antes de fazer a transição para outro.

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

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

    }

-1

Tive esse problema quando naveguei do TVC raiz para o TVC A e depois para o TVC B. Depois de tocar no botão "carregar" no TVC BI, quis voltar direto para o TVC raiz (não há necessidade de revisitar o TVC A, então por que fazer isso) . Eu tinha:

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

... que gerou o erro "Chamadas não balanceadas para iniciar / encerrar etc". O seguinte corrigiu o erro, mas nenhuma animação:

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

Esta foi minha solução final, sem erros e ainda animada:

//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];

-1

Eu encontrei esse erro quando conectei um UIButton a uma ação segue de storyboard (em IB), mas depois decidi fazer com que o botão chamasse programaticamente, performSegueWithIdentifieresquecendo de remover o primeiro do IB.

Em essência, ele executou a chamada segue duas vezes, deu esse erro e realmente empurrou meu ponto de vista duas vezes. A correção foi remover uma das chamadas de segue.

Espero que isso ajude alguém tão cansado quanto eu!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.