22

开始使用时,我UINavigationBar在应用程序中的标题有一个奇怪的问题interactivePopGestureRecognizer。我制作了一个演示应用程序来展示这个错误。

设置:

  • rootViewController 是一个UINavigationController.
  • FirstViewController隐藏了导航栏,并且interactivePopGestureRecognizer.enabled = NO;
  • SecondThirdViewControllers 使导航栏可见并启用 popgesture。

漏洞:

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

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

4

5 回答 5

51

去除红鲱鱼

首先,您的示例可以大大简化。你应该删除所有的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];
    }
}
于 2014-04-26T16:50:40.443 回答
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 的 hidden 属性时,这将解决问题。

于 2014-04-26T08:04:41.123 回答
1

我不知道您如何使“FirstViewController 隐藏了导航栏”。

我有同样的问题,我通过更换解决了它

self.navigationController.navigationBarHidden = YES / NO;

经过

[self.navigationController setNavigationBarHidden:YES / NO animated:animated];
于 2016-02-24T10:45:06.760 回答
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)
}
  • 禁用系统交互PopGestureRecognizer
  • 使用正确的方向创建您自己的 UISwipeGestureRecognizer
  • 检测到滑动时弹出导航堆栈动画
于 2016-03-10T22:55:11.347 回答
0

这是为我解决的问题(Swift)

第一个视图控制器:

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)
}
于 2016-05-16T18:26:40.277 回答