使用interactivePopGestureRecognizer导航栏标题错误

时间:2014-04-24 06:23:31

标签: ios uinavigationbar interactivepopgesture

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

设置:

  • rootViewController是UINavigationController
  • FirstViewController隐藏了导航栏,interactivePopGestureRecognizer.enabled = NO;
  • SecondThirdViewController可以看到导航栏并启用了popgesture。

错误:

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

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

5 个答案:

答案 0 :(得分:48)

删除红色鲱鱼

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

- (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:。你需要这样做。我认为这不会影响bug,但在开始尝试跟踪这些事情之前,最好遵守所有规则。

现在错误仍然存​​在,但我们有更简单的代码,因此我们可以开始解决问题。

流行手势如何运作

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

最重要的(注意这一部分),如果手势在中间放弃(几乎可以肯定),则决定手势是否超过一半完成,并基于这个,要么动画快速播放到最后(即speed设置为3),要么动画向后跑到(即{ {1}}设置为speed)。

解决方案及其工作原理

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

  • 当弹出动画和弹出手势开始时,即使视图最终未出现(因为这是交互式手势并且手势可能被取消),也会为前一个视图控制器调用-3。如果您习惯于viewWillAppear:始终跟随视图实际接管屏幕(并且viewWillAppear:被调用),这可能是一个严重的问题,因为这这种情况可能不会发生。 (正如Apple在WWDC 2013视频中所说,"视图将出现"实际上意味着"视图可能出现&#34 ;.)

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

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

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

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

过渡协调员

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

专注于viewDidAppear:的OneViewController实现。这是事情变得混乱的地方。当您在TwoViewController中并从左侧开始平移手势时,OneViewController的viewWillAppear:正在被调用。但是你取消了,放弃了手势而没有完成它。在这种情况下,您希望在OneViewController viewWillAppear:中执行您正在执行的操作。这就是转换协调员允许你做的

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

viewWillAppear:

答案 1 :(得分:6)

修复很简单,但我现在没有任何解释为什么会发生这种情况。

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的隐藏属性时,这将解决问题。

答案 2 :(得分:1)

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

我有同样的问题,我通过替换

来修复它
self.navigationController.navigationBarHidden = YES / NO;

通过

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

答案 3 :(得分:1)

我放弃尝试使用我自己的滑动识别器来弹出导航堆栈:

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
  • 使用正确的方向创建您自己的UISwipeGestureRecognizer
  • 检测到滑动时弹出导航堆栈

答案 4 :(得分:0)

这是为我解决的问题(斯威夫特)

第一个视图控制器:

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)
}