使用interactivePopGestureRecognizer导航栏标题错误 [英] Navigation bar title bug with interactivePopGestureRecognizer

查看:109
本文介绍了使用interactivePopGestureRecognizer导航栏标题错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

interactivePopGestureRecognizer 发挥作用时,我在应用中的 UINavigationBar 的标题出现了一个奇怪的问题。我已经制作了 演示应用 来展示此错误。

I am having a weird problem with UINavigationBar's title in an app when interactivePopGestureRecognizer comes into play. I have made a demo app to showcase this bug.


  • rootViewController是 UINavigationController

  • FirstViewController 隐藏了导航栏, interactivePopGestureRecognizer.enabled = NO;

  • 第二个 ThirdViewController 可以看到导航栏并启用了popgesture。

  • The rootViewController is a UINavigationController.
  • FirstViewController has the navigation bar hidden, and interactivePopGestureRecognizer.enabled = NO;
  • Second and ThirdViewControllers have the navigation bar visible and the popgesture enabled.

从第二个回到使用popgesture的第一个视图。如果您中途拉出第二个视图然后返回第二个视图,导航标题将显示第二个视图(如预期的那样)。但是当您转到第三个视图时,标题将不会更改为第三个视图。然后单击第三个视图的后退按钮,导航栏将变得混乱。

The bug occurs when going back from the Second to the First view using the popgesture. If you pull the second view halfway and then go back to the second view, the navigation title will show "Second View" (as expected).But when you go to the Third view, the title will not change to "Third View". And then on clicking the back button of the Third view, the navbar will get messed up.

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

Please check out my demo app. Any help explaining why this bug is happening will be appreciated. Thanks!

推荐答案

删除红色鲱鱼



首先,你的例子可以大大简化。你应该删除所有 viewDidLoad 的东西,因为它是一个完整的红色鲱鱼,只会使问题复杂化。在视图控制器的每次更改时,您都不应该使用pop手势识别器委托;并且关闭和打开弹出手势识别器与示例无关(默认情况下它是打开的,并且应该保留为此示例)。所以在所有三个视图控制器中删除这种东西:

Remove Red Herrings

First of all, your example can be greatly simplified. You should delete all the viewDidLoad stuff, as it is a complete red herring and just complicates the issue. You should not be playing around with the pop gesture recognizer delegate on every change of view controller; and turning the pop gesture recognizer off and on is irrelevant to the example (it is on by default, and should just be left on for this example). So delete this kind of thing in all three view controllers:

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

(不要删除设置<$的代码c $ c> self.title ,尽管你可以通过在每个视图控制器的 xib 文件中做到这一点更简单。)

(Don't delete the code that sets self.title, though you could have made things even simpler by doing that in the xib file for each view controller.)

您还可以完全摆脱其他未使用的方法,例如 init ... 方法和内存警报方法。

You can also get rid of other unused methods throughout, such as the init... methods and memory alert methods.

顺便说一下,另一个问题是你忘记在你的<$实现中调用 super C $ C> viewWillAppear中:。你需要这样做。我认为这不会影响bug,但在你开始尝试跟踪这些事情之前,最好遵守所有规则。

Another issue, by the way, is that you have forgotten to call super in your implementations of viewWillAppear:. It is required that you do this. I don't think that affects the bug, but it is well to obey all the rules before you start trying to track these things down.

现在这个bug仍然会发生但是我们有更简单的代码,所以我们可以开始孤立这个问题。

Now the bug still happens but we have much simpler code, so we can start to isolate the issue.

那么问题的原因是什么?我认为最明显的理解方法是实现流行手势的运作方式。这是一个交互式视图控制器转换动画。那是对的 - 这是一个动画。它的工作方式是弹出动画(从左侧滑动)附加到超视图层,但速度为0,因此它实际上不会运行。随着手势的进行,图层的 timeOffset 会不断更新,以便显示动画的相应框架。因此,看起来就像你在拖动视图一样,但你不是;你只是做一个手势,动画正在以相同的速度和相同的程度进行。我在这个答案中解释了这个机制: https://stackoverflow.com/a/22677298/341994

So what's the cause of the problem? I think the most obvious way to understand it is to realize how the pop gesture works. This is an interactive view controller transition animation. That's right - it's an animation. The way it works is that the pop animation (slide from the left) is attached to the superview layer, but with a speed of 0 so that it doesn't actually run. As the gesture proceeds, the timeOffset of the layer is constantly being updated, so that the corresponding "frame" of the animation appears. Thus it looks like you are dragging the view, but you are not; you are just making a gesture, and animation is proceeding at the same rate and to the same degree. I have explained this mechanism in this answer: https://stackoverflow.com/a/22677298/341994

最重要的(注意这一部分),如果手势在中间放弃(几乎可以肯定),则决定手势是否超过中途完成,并基于此,动画快速播放到最后(即速度设置为 3 )或动画向后运行(即 speed 设置为 -3 )。

Most important (pay attention to this part), if the gesture is abandoned in the middle (which it almost certainly will be), a decision is made as to whether the gesture is more than half-way completed, and based on this, either the animation is rapidly played to the end (i.e. the speed is set to something like 3) or the animation is run backwards to the start (i.e. the speed is set to something like -3).

现在让我们来谈谈错误。这里有两个并发症,你不小心撞到了:

Now let's talk about the bug. There are two complications here that you've accidentally banged into:


  • 随着流行动画和流行手势的开始, viewWillAppear:为前一个视图控制器调用,即使视图最终可能不会出现(因为这是一个交互式手势,手势可能会被取消)。如果你习惯于假设 viewWillAppear:总是跟着视图实际接管屏幕,那么这可能是一个严重的问题(和 viewDidAppear:被调用),因为这种情况可能不会发生。 (正如Apple在WWDC 2013视频中所说,视图将出现实际上意味着视图可能出现。)

  • As the pop animation and pop gesture begin, viewWillAppear: is called for the previous view controller even though the view may not ultimately appear (because this is an interactive gesture and the gesture may be cancelled). This can be a serious issue if you are used to the assumption that viewWillAppear: is always followed by the view actually taking over the screen (and viewDidAppear: being called), because this is a situation in which those things might not happen. (As Apple says in the WWDC 2013 videos, "view will appear" actually means "view might appear".)

那里是一组辅助动画,即与导航栏相关的所有内容 - 标题的更改(它应该淡入视图),在这种情况下,不隐藏和隐藏之间的变化。运行时正在尝试使用滑动视图动画来协调辅助动画集。但是当隐藏或显示栏时,你通过调用 no 动画来解决这个问题。

There is a secondary set of animations, namely, everything connected with the navigation bar - the change of title (it is supposed to fade into view) and, in this case, the change between not hidden and hidden. The runtime is trying to coordinate the secondary set of animations with the sliding view animation. But you have made that difficult by calling for no animation when the bar is hidden or shown.

因此,正如您已经被告知的那样,一个解决方案是将动画:否改为动画:在整个代码中都是。这样,导航栏的显示和隐藏就被命名为动画的一部分。因此,当手势被取消并且动画向后运行到开始时,导航的显示/隐藏向后运行到开始 - 这两件事情现在保持协调。

Thus, as you've already been told, one solution is to change animated:NO to animated:YES throughout your code. This way, the showing and hiding of the navigation bar is ordered up as part of the animation. Therefore, when the gesture is cancelled and the animation is run backwards to the start, the showing/hiding of the navigation is also run backwards to the start - the two things are now staying coordinated.

但如果你真的不想做出改变呢?好吧,另一个解决方案是将 viewWillAppear:更改为 viewDidAppear:。正如我已经说过的那样, viewWillAppear:在动画开始时被调用,即使手势不会完成,这也会导致事情失控。 。但 viewDidAppear:仅在手势完成(未取消)且动画已经结束时被调用。

But what if you really don't want to make that change? Well, another solution is to change viewWillAppear: to viewDidAppear: throughout. As I've already said, viewWillAppear: is called at the start of the animation, even if the gesture won't be completed, which is causing things to get out of whack. But viewDidAppear: is called only if the gesture is completed (not canceled) and when the animation is already over.

我更喜欢这两种解决方案中的哪一种?他们都不是!他们都强迫你做出你不想做的改变。在我看来,真正的解决方案是使用转换协调员

Which of those two solutions do I prefer? Neither of them! They both force you to make changes you don't want to make. The real solution, it seems to me, is to use the transition coordinator.

转换协调器是系统为此目的提供的对象,即检测我们是否参与了交互式转换,并且表现不同取决于它是否被取消。

The transition coordinator is an object supplied by the system for this very purpose, i.e., to detect that we're involved in an interactive transition and to behave differently depending on whether it is canceled or not.

只关注 viewWillAppear:的OneViewController实现。这是事情搞砸的地方。当您在TwoViewController中并从左侧开始平移手势时,正在调用OneViewController的 viewWillAppear:。但是然后你取消,放弃手势而不完成它。在这种情况下,您希望在OneViewController的 viewWillAppear:中执行您正在执行的操作。这就是转换协调员允许你做的事情

Concentrate just on the OneViewController implementation of viewWillAppear:. This is where things are getting messed up. When you're in TwoViewController and you start the pan gesture from the left, OneViewController's viewWillAppear: is being called. But then you cancel, letting go of the gesture without completing it. In just that one case, you want not to do what you were doing in OneViewController's viewWillAppear:. And that is exactly what the transition coordinator allows you to do.

然后,重写OneViewController的 viewWillAppear中:。这可以解决问题而无需进行任何其他更改:

Here, then, is a rewrite of OneViewController's viewWillAppear:. This fixes the problem without your having to make any other changes:

-(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];
    }
}

这篇关于使用interactivePopGestureRecognizer导航栏标题错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆