Também começamos a solucionar esse problema, e era muito provável que as nossas fossem causadas pelo mesmo problema.
Em nosso caso, tivemos que extrair dados do back-end em alguns casos, o que significava que um usuário poderia tocar em algo e, em seguida, haveria um pequeno atraso antes da ocorrência do push de navegação. Se um usuário estivesse digitando rapidamente, ele poderia acabar com dois push de navegação do mesmo controlador de exibição, o que desencadeou essa mesma exceção.
Nossa solução é uma categoria no UINavigationController que evita empurrões / pops, a menos que o vc superior seja o mesmo em um determinado momento.
arquivo .h:
@interface UINavigationController (SafePushing)
- (id)navigationLock; ///< Obtain "lock" for pushing onto the navigation controller
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Uses a horizontal slide transition. Has no effect if the view controller is already in the stack. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops view controllers until the one specified is on top. Returns the popped controllers. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops until there's only a single view controller left on the stack. Returns the popped controllers. Has no effect if navigationLock is not the current lock.
@end
arquivo .m:
@implementation UINavigationController (SafePushing)
- (id)navigationLock
{
return self.topViewController;
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
if (!navigationLock || self.topViewController == navigationLock)
[self pushViewController:viewController animated:animated];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock
{
if (!navigationLock || self.topViewController == navigationLock)
return [self popToRootViewControllerAnimated:animated];
return @[];
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
if (!navigationLock || self.topViewController == navigationLock)
return [self popToViewController:viewController animated:animated];
return @[];
}
@end
Até agora, isso parece ter resolvido o problema para nós. Exemplo:
id lock = _dataViewController.navigationController.navigationLock;
[[MyApi sharedClient] getUserProfile:_user.id success:^(MyUser *user) {
ProfileViewController *pvc = [[ProfileViewController alloc] initWithUser:user];
[_dataViewController.navigationController pushViewController:pvc animated:YES navigationLock:lock];
}];
Basicamente, a regra é: antes de qualquer atraso não relacionado ao usuário pegue um bloqueio do controlador de navegação relevante e inclua-o na chamada para push / pop.
A palavra "bloqueio" pode ter uma formulação um pouco ruim, pois pode indicar que há alguma forma de bloqueio que precisa ser desbloqueada, mas como não há um método de "desbloqueio" em qualquer lugar, provavelmente tudo bem.
(Como uma nota lateral, "atrasos não relacionados ao usuário" são quaisquer atrasos que o código está causando, ou seja, qualquer coisa assíncrona. Os usuários que tocam em um controlador nav que é animado animadamente não contam e não é necessário executar o navigationLock: version para esses casos).