取消交互式UINavigationController弹出手势不会调用UINavigationControllerDelegate方法 [英] Canceling interactive UINavigationController pop gesture does not call UINavigationControllerDelegate methods

查看:206
本文介绍了取消交互式UINavigationController弹出手势不会调用UINavigationControllerDelegate方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果拖动UIViewController的边缘以开始在UINavigationController中进行交互式弹出过渡,则调用当前下方的UIViewController,然后调用UINavigationControllerDelegate方法navigationController:willShowViewController:animated:. /p>

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

我真正需要的是能够查看在我的UINavigationController子类或其UINavigationControllerDelegate中取消交互式弹出窗口的时间.有明显的方法吗?

修改

我仍在寻找解决方案,但我想指出,我已将此问题报告为Apple的错误.查看文档,没有理由不应该调用这些委托方法,特别是考虑到必须调用等效的视图生命周期方法.

edit2

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

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

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

view [Will/Did] Appear仍应按预期方式调用.

很遗憾,这是事实,因为这违反直觉,但下面我的答案中的解决方法在可预见的将来(至少对于我的用例)应该可以正常工作.

解决方案

对于感兴趣的人,我发现了在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:调用延迟到仅在应该完成动画以更加符合预期的默认行为时才调用.

If you drag the edge of a UIViewController to begin an interactive pop transition within a UINavigationController, the UIViewController underneath the current has viewWillAppear: called, followed by the UINavigationControllerDelegate method navigationController:willShowViewController:animated:.

If you cancel the transition (i.e. the dragged controller is placed back where it was and not popped), viewWillAppear: and viewDidAppear: are called on the top view controller as expected, but the delegate methods navigationController:willShowViewController:animated: and navigationController:didShowViewController:animated: aren't. It seems like at least one or both of these should be called considering the UIViewController view lifecycle methods are called. I am wondering whether this is deliberate or a bug in UINavigationController.

What I really need is to be able to see when an interactive pop is cancelled, either within my UINavigationController subclass, or its UINavigationControllerDelegate. Is there an obvious way to do this?

edit

I'm still looking for a solution to this but would like to mention that I have reported this issue as a bug with Apple. Looking at the documentation, there is no reason these delegate methods should not get called, especially considering the equivalent view lifecycle methods DO get called.

edit2

My radar ticket (16823313) was closed today (May 21st, 2015) and marked as intended. :(

Engineering has determined that this issue behaves as intended based on the following information:

This is actually the correct behavior. The navigation transition that's happening from B -> A, if you cancel it mid-transition, you won't get the didShowViewController: method. A cancellation of this transition shouldn't be considered a transition from A -> B because you never actually reached A.

view[Will/Did]Appear should still be called as expected too.

Quite a bummer this is the case as it is counterintuitive but the workaround in my answer below should work fine for the foreseeable future, at least for my use-case.

解决方案

For anyone interested, I have found 2 ways to work around this at the UINavigationControllerDelegate level.

  1. Use KVO to observe the state property of the interactivePopGestureRecognizer. Unfortunately, canceling the transition does not change the state to UIGestureRecognizerStateFailed but instead just UIGestureRecognizerStateEnded, so you would need to write a bit of additional code to keep track of what happened if you needed to discern between a cancelled or completed pop.

  2. After testing it, this is probably the better solution: Use the navigationController:willShowViewController:animated: method to add a notification block to the transition coordinator. It looks something like this:

    - (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];
                    });
                }
    
    
            }
        }];
    }
    

I was hesitant to use this solution at first because the documentation was unclear about whether or not you could set more than one of these (since in that case, if an unknowing view controller also set its own notification block, it could potentially either replace this one or get replaced by this one). After testing it though, it appears that it is not a 1:1 relationship and you can add multiple notification blocks safely.

edit

I edited the code above to delay the navigationController:didShowViewController:animated: call to only be called when the animation is supposed to be completed to more closely match the expected default behavior.

这篇关于取消交互式UINavigationController弹出手势不会调用UINavigationControllerDelegate方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆