导航栏标题bug与interactivePopGestureRecognizer
当interactivePopGestureRecognizer
发挥interactivePopGestureRecognizer
时,我在应用程序的UINavigationBar
标题中出现了一个奇怪的问题。 我做了一个演示应用程序来展示这个错误。
build立:
- rootViewController是一个
UINavigationController
。 -
FirstViewController
隐藏导航栏,interactivePopGestureRecognizer.enabled = NO;
-
Second
和ThirdViewController
具有可见的导航栏并启用了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:NO
为animated: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) }