导航栏标题bug与interactivePopGestureRecognizer

interactivePopGestureRecognizer发挥interactivePopGestureRecognizer时,我在应用程序的UINavigationBar标题中出现了一个奇怪的问题。 我做了一个演示应用程序来展示这个错误。

build立:

  • rootViewController是一个UINavigationController
  • FirstViewController隐藏导航栏, interactivePopGestureRecognizer.enabled = NO;
  • SecondThirdViewController具有可见的导航栏并启用了popup窗口。

错误:

从第二个视图返回到第一个视图时,使用popup窗口会出现该错误。 如果您将第二个视图中途拉回第二个视图,则导航标题将显示“第二个视图”(如预期的那样)。但是当您转到第三个视图时,标题将不会更改为“第三个视图”。 然后点击第三个视图的后退button,导航栏会变得混乱。

请查看我的演示应用程序。 任何帮助解释为什么这个错误发生将不胜感激。 谢谢!

去除红鲱鱼

首先,你的例子可以大大简化。 你应该删除所有的viewDidLoad东西,因为它是一个完整的红色鲱鱼,只是复杂的问题。 你不应该在视图控制器的每一个改变上玩popup手势识别器委托; 并closures和打开pop手势识别器与示例无关(默认情况下已启用,并且应该仅针对此示例)。 所以在所有三个视图控制器中删除这种事情:

 - (void)viewDidLoad { [super viewDidLoad]; if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; self.navigationController.interactivePopGestureRecognizer.delegate = self; } } 

(不要删除设置self.title的代码,尽pipe通过在每个视图控制器的xib文件中这样做可以使事情变得更简单。)

你也可以摆脱其他未使用的方法,如init...方法和内存警报方法。

顺便提一下,另一个问题是,你忘了在viewWillAppear:的实现中调用super viewWillAppear: 这是要求你这样做。 我不认为这会影响错误,但是在开始尝试跟踪这些事情之前,遵守所有的规则是很好的。

现在错误仍然存​​在,但我们有更简单的代码,所以我们可以开始隔离问题。

stream行的手势如何工作

那么问题的原因是什么? 我认为了解它的最明显的方法是认识到stream行的手势是如何工作的。 这是一个交互式视图控制器转换animation。 没错 – 这是一个animation 。 它的工作方式是popup式animation(从左侧滑动)被附加到超视图层,但speed为0,以便它实际上不运行。 随着手势的进行,层的timeOffset不断被更新,使得animation的相应“帧”出现。 因此, 看起来你拖着视图,但你不是; 你只是在做一个手势,animation的速度和程度都是一样的。 我在这个答案中解释了这个机制: https : //stackoverflow.com/a/22677298/341994

最重要的是(注意这一部分),如果手势在中间被放弃(几乎可以肯定会被放弃),则做出关于该手势是否已经完成一半以上的决定,并且基于此animation被快速播放到最后(即speed设置为3 ),或者animation向后移动到起始位置 (即speed设置为-3 )。

解决scheme及其工作原因

现在我们来讨论一下这个bug。 这里有两个你偶然遇到的复杂问题:

  • 当stream行animation和stream行手势开始时,即使视图可能最终不出现(因为这是交互式手势并且手势可能被取消),也会为先前的视图控制器调用viewWillAppear: 这可能是一个严重的问题,如果你习惯了viewWillAppear:的假设viewWillAppear:总是跟着实际接pipe屏幕(和viewDidAppear:被调用)的视图,因为这是一种情况,在这种情况下,这些事情可能不会发生。 (正如苹果在WWDC 2013video中所说的,“观看会出现”实际上意味着“观看可能会出现”。)

  • 还有第二组animation,即与导航栏相关的所有内容 – 标题的变化(应该淡入视图),在这种情况下,不隐藏和隐藏之间的变化。 运行时正在尝试将辅助animation集与滑动视图animation进行协调。 但是,如果隐藏或显示条形图,则通过调用animation来实现这一点非常困难。

因此,正如你已经被告知的, 一个解决scheme是在整个代码中改变animated:NOanimated:YES 。 这样,导航栏的显示和隐藏就作为animation的一部分进行sorting。 因此,当手势被取消并且animation向后运行到开始时,导航的显示/隐藏向后运行到开始 – 两件事现在保持协调。

但是如果你真的不想做这个改变呢? 那么另一个解决办法是改变viewWillAppear: viewDidAppear:贯穿始终。 正如我已经说过的, viewWillAppear:在animation开始的时候被调用,即使手势不会被完成,这会导致事情失控。 但是viewDidAppear:只有当手势完成(未取消)和animation已经结束时才会被调用。

我更喜欢哪两种解决scheme? 他们都不是! 他们都强迫你做出你不想做的改变。 在我看来, 真正的解决scheme是使用过渡协调器

过渡协调员

过渡协调员是系统为此目的而提供的一个对象,即检测到我们参与了一个交互式过渡,并根据它是否被取消而performance出不同的行为。

只关注viewWillAppear:的OneViewController实现。 这是事情变得混乱的地方。 当您在TwoViewController中,并从左侧开始平移手势时,将调用OneViewController的viewWillAppear: :。 但是,然后你取消,放弃手势而没有完成。 在这种情况下,你不想在OneViewController的viewWillAppear:中做你正在做的事情。 而这正是过渡协调员允许你做的事情

在这里,是重写OneViewController的viewWillAppear: :。 这解决了这个问题,而无需做任何其他更改:

 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator; if (tc && [tc initiallyInteractive]) { [tc notifyWhenInteractionEndsUsingBlock: ^(id<UIViewControllerTransitionCoordinatorContext> context) { if ([context isCancelled]) { // do nothing! } else { // not cancelled, do it [self.navigationController setNavigationBarHidden:YES animated:NO]; } }]; } else { // not interactive, do it [self.navigationController setNavigationBarHidden:YES animated:NO]; } } 

解决方法很简单,但目前我没有任何解释为什么会发生这种情况。

一个你的OneViewController改变你的viewWillAppear

 -(void)viewWillAppear:(BOOL)animated{ // [self.navigationController setNavigationBarHidden:YES animated:NO]; self.navigationController.navigationBar.hidden = YES; } 

并在第二个和第三个视图控制器改变它,

  -(void)viewWillAppear:(BOOL)animated{ //[self.navigationController setNavigationBarHidden:NO animated:NO]; self.navigationController.navigationBar.hidden = NO; } 

奇怪,但是这将解决这个问题,当我们直接使用UINavigationBar的隐藏属性。

我不知道你怎么做“FirstViewController隐藏导航栏”。

我有同样的问题,我通过replace来解决它

 self.navigationController.navigationBarHidden = YES / NO; 

通过

 [self.navigationController setNavigationBarHidden:YES / NO animated:animated]; 

我放弃了试图使这个工作使用我自己的滑动识别器popup导航堆栈:

 override func viewDidLoad() { super.viewDidLoad() // disable system swipe back gesture and add our own navigationController?.interactivePopGestureRecognizer?.enabled = false let swipeBackGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeBackAction:") swipeBackGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right tableView.addGestureRecognizer(swipeBackGestureRecognizer) } func swipeBackAction(sender: UISwipeGestureRecognizer) { navigationController?.popViewControllerAnimated(true) } 
  • 禁用系统interactivePopGestureRecognizer
  • 用正确的方向创build你自己的UISwipeGestureRecognizer
  • 检测到滑动时popup导航堆栈

这是什么修复了我(斯威夫特)

第一视图控制器:

 override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: animated) } 

第二和第三视图控制器:

 override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(false, animated: animated) } 
Interesting Posts