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.

Raptor
fonte
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.

Jesper
fonte
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];
PokerIncome.com
fonte
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)

Peter Lapisu
fonte
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.

mllm
fonte
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.

Alex Cio
fonte
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!

Tolusha
fonte
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"

Adriano Spadoni
fonte
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.

Mogambolal
fonte
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];
pankesh
fonte
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...
}
J. Lopes
fonte
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
Kof
fonte
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.

Gellie Ann
fonte
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.

Dale Moore
fonte
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".

Dan Levy
fonte
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.

Dasoga
fonte
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];
    }
}
ymutlu
fonte
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;
};
johnboiles
fonte
0

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

zszen
fonte
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)
codeedoc
fonte
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
fonte
-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];
amanhecer
fonte
-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!

Capikaw
fonte