去除红鲱鱼
首先,您的示例可以大大简化。你应该删除所有的viewDidLoad
东西,因为它是一个完全的红鲱鱼,只会使问题复杂化。您不应该在每次更改视图控制器时都使用弹出手势识别器委托;并且关闭和打开弹出手势识别器与示例无关(默认情况下它是打开的,并且在本示例中应该保持打开状态)。所以在所有三个视图控制器中删除这种东西:
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
(不要删除设置的代码,尽管您可以通过在每个视图控制器self.title
的文件中执行此操作来使事情变得更简单。)xib
您还可以完全摆脱其他未使用的方法,例如init...
方法和内存警报方法。
顺便说一句,另一个问题是您忘记super
调用viewWillAppear:
. 您必须这样做。我不认为这会影响错误,但在您开始尝试追踪这些事情之前遵守所有规则是很好的。
现在错误仍然存在,但我们有更简单的代码,所以我们可以开始隔离问题。
流行手势的工作原理
那么问题的原因是什么?我认为理解它的最明显方法是了解弹出手势的工作原理。这是一个交互式视图控制器过渡动画。没错——这是一部动画。它的工作方式是将弹出动画(从左侧滑动)附加到超级视图层,但 aspeed
为 0,因此它实际上不会运行。随着手势的进行,timeOffset
图层的 不断更新,从而出现相应的动画“帧”。因此,看起来您正在拖动视图,但实际上不是;你只是在做一个手势,动画以同样的速度和同样的程度进行。我在这个答案中解释了这个机制:https://stackoverflow。
最重要的是(注意这部分),如果手势在中间被放弃(几乎肯定会),则决定手势是否完成一半以上,并基于此,要么动画快速播放到结尾(即speed
设置为类似3
)或动画向后运行到开头(即speed
设置为类似-3
)。
解决方案及其工作原理
现在让我们谈谈这个错误。这里有两个你不小心撞到的并发症:
当弹出动画和弹出手势开始时,viewWillAppear:
即使视图最终可能不会出现(因为这是一个交互式手势并且手势可能会被取消),也会为前一个视图控制器调用。如果您习惯于实际接管屏幕(并被调用)viewWillAppear:
的视图始终遵循的假设,这可能是一个严重的问题,因为在这种情况下这些事情可能不会发生。(正如 Apple 在 WWDC 2013 视频中所说,“视图将出现”实际上意味着“视图可能出现”。)viewDidAppear:
还有一组辅助动画,即与导航栏相关的所有内容 - 标题的更改(它应该淡入视图),在这种情况下,是未隐藏和隐藏之间的更改。运行时试图协调第二组动画与滑动视图动画。但是,当栏被隐藏或显示时,您要求不使用动画,从而使这变得困难。
因此,正如您已经被告知的那样,一种解决方案animated:NO
是更改animated:YES
整个代码。这样,导航栏的显示和隐藏就作为动画的一部分进行了排序。因此,当取消手势并且动画倒退到开始时,导航的显示/隐藏也会倒退到开始 - 这两件事现在保持协调。
但是,如果您真的不想做出这种改变怎么办?好吧,另一个解决方案是更改viewWillAppear:
为viewDidAppear:
始终。正如我已经说过的,viewWillAppear:
在动画开始时调用,即使手势不会完成,这会导致事情失控。但viewDidAppear:
仅在手势完成(未取消)并且动画已经结束时调用。
我更喜欢这两种解决方案中的哪一种?他们都不是!它们都迫使你做出你不想做出的改变。在我看来,真正的解决方案是使用过渡协调器。
过渡协调员
转换协调器是系统为此目的而提供的对象,即检测我们是否参与了交互式转换,并根据它是否被取消而采取不同的行为。
只关注viewWillAppear:
. 这就是事情变得一团糟的地方。当您在 TwoViewController 中并从左侧开始平移手势时,将viewWillAppear:
调用 OneViewController。但随后你取消,放开手势而不完成它。在这种情况下,您不想做您在 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];
}
}