使用interactivePopGestureRecognizer导航栏标题错误

aks*_*h1t 21 uinavigationbar ios interactivepopgesture

进场时,我UINavigationBar在应用程序中的标题出现了一个奇怪的问题interactivePopGestureRecognizer.我制作了一个演示应用来展示这个bug.

建立:

  • rootViewController是一个UINavigationController.
  • FirstViewController 隐藏了导航栏,并且 interactivePopGestureRecognizer.enabled = NO;
  • Second并且ThirdViewController可以看到导航栏并启用了popgesture.

错误:

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

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

mat*_*att 49

删除红鲱鱼

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

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

(不要删除设置的代码self.title,尽管通过在xib每个视图控制器的文件中执行此操作可以使事情变得更简单.)

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

顺便说一下,另一个问题是你忘了打电话给super你的实施viewWillAppear:.你需要这样做.我不认为这会影响bug,但在开始尝试跟踪这些事情之前,最好遵守所有规则.

现在错误仍然存​​在,但我们有更简单的代码,所以我们可以开始隔离问题.

流行手势如何运作

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

最重要的(注意这一部分),如果手势在中间放弃(几乎肯定会是这样),则决定手势是否超过一半完成,并基于此,动画快速播放到最后(即speed设置为类似的东西3)或动画向后跑到开始(即speed设置为类似的东西-3).

解决方案及其工作原理

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

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

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

因此,正如您已经被告知的那样,一种解决方案animated:NOanimated:YES整个代码中进行更改.这样,导航栏的显示和隐藏将作为动画的一部分进行排序.因此,当手势被取消并且动画向后运行到开始时,导航的显示/隐藏也会向后运行到开始 - 这两件事现在保持协调.

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

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

过渡协调员

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

专注于OneViewController的实现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];
    }
}
Run Code Online (Sandbox Code Playgroud)


rus*_*ord 6

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

一个你的OneViewController改变你viewWillAppear的,

-(void)viewWillAppear:(BOOL)animated{
   // [self.navigationController setNavigationBarHidden:YES animated:NO];
    self.navigationController.navigationBar.hidden = YES;
}
Run Code Online (Sandbox Code Playgroud)

并在第二和第三视图控制器上将其更改为,

 -(void)viewWillAppear:(BOOL)animated{
         //[self.navigationController setNavigationBarHidden:NO animated:NO];
         self.navigationController.navigationBar.hidden = NO;
    }
Run Code Online (Sandbox Code Playgroud)

奇怪但这将解决我们直接使用UINavigationBar的隐藏属性时的问题.