取消交互式UINavigationControllerpopup手势不会调用UINavigationControllerDelegate方法

如果拖动UIViewController以在UINavigationController开始交互式popup转换,则当前窗口下方的UIViewController具有viewWillAppear: called,随后是UINavigationControllerDelegate方法navigationController:willShowViewController:animated:

如果取消转换, viewWillAppear:预期方式在顶部视图控制器上调用viewWillAppear:viewDidAppear:

但是, navigationController:willShowViewController:animated:委托方法navigationController:willShowViewController:animated:navigationController:didShowViewController:animated:

至less应该调用其中的一个或两个,考虑调用UIViewController视图生命周期方法。 我想知道这是故意的还是UINavigationController的错误。

我真正需要的是能够在UINavigationController子类或其UINavigationControllerDelegate看到何时取消交互式popup窗口。 有没有一个明显的方法来做到这一点?

编辑

我仍然在寻找一个解决scheme,但要提一下,我已经把这个问题报告为苹果的一个bug。 查看文档,这些委托方法不应该被调用,特别是考虑到调用等效的视图生命周期方法。

EDIT2

我的雷达票(16823313)今天(2015年5月21日)closures,并按预期标记。 🙁

工程部门根据以下信息确定此问题的行为如预期的那样:

这实际上是正确的行为。 从B – > A发生的导航转换,如果在转换过程中取消它,则不会得到didShowViewController:方法。 这个过渡的取消不应该被认为是从A→B的过渡,因为你从来没有真正达到A.

查看[Will / Did]出现仍然应该如期调用。

这是一个令人失望的情况,因为这是违反直觉的,但是我的回答中的解决方法在可预见的未来应该可以正常工作,至less对我来说是这样。

对于任何感兴趣的人,我已经find了2种方法在UINavigationControllerDelegate级别解决这个问题。

  1. 使用KVO观察interactivePopGestureRecognizerstate属性。 不幸的是,取消转换并不会将状态更改为UIGestureRecognizerStateFailed ,而只是UIGestureRecognizerStateEnded ,因此如果需要识别已取消或已完成的stream行音乐,则需要编写一些附加代码以跟踪发生的情况。

  2. 在testing之后,这可能是更好的解决scheme:使用navigationController:willShowViewController:animated:方法将通知块添加到过渡协调器。 它看起来像这样:

     - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { [[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) { if([context isCancelled]) { UIViewController *fromViewController = [context viewControllerForKey:UITransitionContextFromViewControllerKey]; [self navigationController:navigationController willShowViewController:fromViewController animated:animated]; if([self respondsToSelector:@selector(navigationController:didShowViewController:animated:)]) { NSTimeInterval animationCompletion = [context transitionDuration] * (double)[context percentComplete]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((uint64_t)animationCompletion * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self navigationController:navigationController didShowViewController:fromViewController animated:animated]; }); } } }]; } 

起初,我对这个解决scheme犹豫不决,因为关于你是否可以设置其中的一个以上(因为在这种情况下,如果一个不知名的视图控制器也设置了自己的通知块,它可能会替代这个一个或被这个replace)。 经过testing,似乎它不是1:1的关系,你可以安全地添加多个通知块。

编辑

我编辑了上面的代码来延迟navigationController:didShowViewController:animated: call只在应该完成animation以更接近预期的默认行为时被调用。

Swift 3:

 func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { transitionCoordinator?.notifyWhenInteractionEnds { context in guard context.isCancelled, let fromViewController = context.viewController(forKey: UITransitionContextViewControllerKey.from) else { return } self.navigationController(self, willShow: fromViewController, animated: animated) let animationCompletion: TimeInterval = context.transitionDuration * Double(context.percentComplete) DispatchQueue.main.asyncAfter(deadline: .now() + animationCompletion) { self.navigationController(self, didShow: fromViewController, animated: animated) } } } 

我翻译了@Dima对我的项目的Swift 答案 :

 func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) { transitionCoordinator()?.notifyWhenInteractionEndsUsingBlock { context in guard context.isCancelled(), let fromViewController = context.viewControllerForKey(UITransitionContextFromViewControllerKey) else { return } self.navigationController(self, willShowViewController: fromViewController, animated: animated) let animationCompletion: NSTimeInterval = context.transitionDuration() * Double(context.percentComplete()) let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))) dispatch_after(delayTime, dispatch_get_main_queue()) { self.navigationController(self, didShowViewController: fromViewController, animated: animated) } } /* Your normal behavior goes here */ } 

请注意,我不检查是否存在navigationController(_:didShowViewController:animated:) ,尽pipe我相信这是在Swift编译时检查的,而且如果尝试在未实现时调用它。