【问题标题】:Canceling interactive UINavigationController pop gesture does not call UINavigationControllerDelegate methods取消交互式 UINavigationController 弹出手势不会调用 UINavigationControllerDelegate 方法
【发布时间】:2014-06-22 10:53:09
【问题描述】:

如果拖动UIViewController 的边缘以在UINavigationController 内开始交互式弹出过渡,则当前下方的UIViewController 将调用viewWillAppear:,然后调用UINavigationControllerDelegate 方法navigationController:willShowViewController:animated:

如果您取消转换(即被拖动的控制器被放回原来的位置而不是弹出),viewWillAppear:viewDidAppear: 将按预期在顶视图控制器上调用,但委托方法 navigationController:willShowViewController:animated: 和 @ 987654330@ 不是。考虑到调用了UIViewController 视图生命周期方法,似乎至少应该调用其中一个或两个。我想知道这是故意的还是UINavigationController 中的错误。

我真正需要的是能够在我的UINavigationController 子类或其UINavigationControllerDelegate 中看到何时取消交互式弹出。有没有明显的方法可以做到这一点?

编辑

我仍在寻找解决方案,但想提一下,我已将此问题报告为 Apple 的错误。查看文档,没有理由不调用这些委托方法,尤其是考虑到等效的视图生命周期方法确实会被调用。

edit2

我的雷达票 (16823313) 今天(2015 年 5 月 21 日)关闭并标记为预期。 :(

工程部门已确定此问题的行为符合预期 关于以下信息:

这实际上是正确的行为。导航过渡 这是从 B -> A 发生的,如果你在过渡中取消它,你 不会得到 didShowViewController: 方法。取消这个 过渡不应被视为从 A -> B 的过渡,因为 你从未真正到达过 A。

view[Will/Did]Appear 仍应按预期调用。

这种情况真是太糟糕了,因为它违反直觉,但我下面答案中的解决方法在可预见的未来应该可以正常工作,至少对于我的用例而言。

【问题讨论】:

  • 你有我可以欺骗的错误报告编号吗?
  • 嗨蒂姆。我的错误报告编号是16823313。去年 6 月,他们尝试使用第一个 iOS 8 测试版将其标记为已修复,我在确认它仍然损坏后将其打开。此后没有任何活动。
  • 为什么第 1 段说 navigationController:willShowViewController: 被调用,而第 3 段却说没有被调用?
  • @malhal 方法会在您启动交互式弹出时调用,但不会在您取消时调用。
  • 谢谢,我现在明白了,希望你不介意我编辑了这个问题,将第 2 段和第 3 段结合起来,因为他们谈论的是同一个案例。我仍在弄清楚我对这个问题的回答,但只是为了让你知道在取消手势后我确实接到了 willShowViewController 的电话(Xcode 11.1 iPhone 8 Plus 模拟器肖像)。

标签: ios objective-c uinavigationcontroller uikit


【解决方案1】:

对于任何感兴趣的人,我在UINavigationControllerDelegate 级别找到了两种解决此问题的方法。

  1. 使用 KVO 观察 interactivePopGestureRecognizerstate 属性。不幸的是,取消转换不会将状态更改为UIGestureRecognizerStateFailed,而只会更改为UIGestureRecognizerStateEnded,因此如果您需要区分已取消或已完成的弹出,您需要编写一些额外的代码来跟踪发生的情况。

  2. 经过测试,这可能是更好的解决方案:使用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];
                    });
                }
    
    
            }
        }];
    }
    

起初我对使用此解决方案犹豫不决,因为文档不清楚您是否可以设置多个这些(因为在这种情况下,如果不知情的视图控制器也设置了自己的通知块,它可能要么替换这个,要么被这个替换)。不过经过测试,似乎不是1:1的关系,你可以安全地添加多个通知块。

编辑

我编辑了上面的代码以延迟 navigationController:didShowViewController:animated: 调用,使其仅在动画应该完成以更接近预期的默认行为时调用。

【讨论】:

  • 很好的解决方案!我面临着同样的问题。但是 viewControllers/topViewController 呢?在您调用这些方法时,视图控制器并未添加到导航控制器堆栈中,因此 topViewController 保持旧值。
  • 刚刚注意到它在手势结束但视图控制器继续移动时触发
  • 你也可以使用stackoverflow.com/questions/21298051的解决方案在没有KVO的情况下获得状态
  • 你永远不会得到一致的结果与延迟。但是,您的第二个解决方案对我来说也足够了。
  • 如果你真的想知道过渡结束的时间,你可以把你的didShow代码放在[UIViewControllerTransitionCoordinator animateAlongsideTransition:completion]的完成块中,而不是[UIViewControllerTransitionCoordinator notifyWhenInteractionEndsUsingBlock]。当然,这适用于 iOS 8 及更高版本。
【解决方案2】:

我为我的项目将 @Dima 的 answer 翻译成 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:) 的实现,尽管我相信这是在 Swift 的编译时检查的,并且如果您尝试在以下时间调用它,您将收到编译器错误它没有实现。

【讨论】:

  • 干得好!不过我想补充一点,这个编译时错误也会出现在 Objective-C 中。我在原始答案中加入此检查的原因是,您不必根据是否实现 ...didShow... 方法来修改此补丁代码。基本上,这很方便,因为无论你有没有补丁,它都可以工作。
【解决方案3】:

斯威夫特 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)
        }
    }
}

【讨论】:

  • 感谢您的加入!
  • 老兄,这太棒了。非常感谢您的回答。您应该对其进行编辑并包含一些关于这件事的作用的 cmets,以便人们知道。对于任何阅读的人来说,这基本上是,如果导航被取消,它会反转控制器并再次调用代表。因此,您可以将其包含在 willShow 函数的顶部以及下面的所有逻辑中。它执行的逻辑很好,如果取消,它会反向执行逻辑,这样你就不会失去你的状态/动画
  • notifyWhenInteractionEnds 在 iOS 10 中已被弃用
  • @strangetimes 只需将其更改为notifyWhenInteractionChanges,它的工作原理完全相同
猜你喜欢
  • 2013-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-20
  • 1970-01-01
  • 2014-10-15
  • 1970-01-01
相关资源
最近更新 更多