UIPercentDrivenInteractiveTransition 无法通过快速手势完成动画

UIPercentDrivenInteractiveTransition doesn't get to animation's completion on fast gesture

我创建了一个交互式过渡。我的 func animateTransition(transitionContext: UIViewControllerContextTransitioning) 很正常,我得到容器 UIView,我添加两个 UIViewControllers 然后我在 UIView.animateWithDuration(duration, animations, completion) 中做动画变化。

我在 UIViewController 中添加了一个 UIScreenEdgePanGestureRecognizer。它运作良好,除非我快速平底锅。

在最后一个场景中,应用程序没有响应,仍然在相同的 UIViewController(转换似乎没有工作)但后台任务 运行。当我 运行 和 Debug View Hierarchy 时,我看到新的 UIViewController 而不是前一个,而前一个(至少它的 UIView)站在它应该站在的地方在过渡结束时。

我做了一些打印和检查点,据此我可以说当问题发生时,动画完成(我的 animateTransition 方法中的那个) 未达到,所以我无法调用transitionContext.completeTransition方法来完成或不完成转换。

我也看到平底锅有时从 UIGestureRecognizerState.Began 直接到 UIGestureRecognizerState.Ended 而没有经过 UIGestureRecognizerState.Changed

当它经过 UIGestureRecognizerState.Changed 时, translation velocity 都会留下来每个 UIGestureRecognizerState.Changed 个州都一样。

编辑:

代码如下:

animateTransition方法

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    self.transitionContext = transitionContext

    let containerView = transitionContext.containerView()
    let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!, transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!)

    let parentViewController = presenting ? screens.from : screens.to
    let childViewController = presenting ? screens.to : screens.from

    let parentView = parentViewController.view
    let childView = childViewController.view

    // positionning the "to" viewController's view for the animation
    if presenting {
        offStageChildViewController(childView)
    }

    containerView.addSubview(parentView)    
    containerView.addSubview(childView)

    let duration = transitionDuration(transitionContext)

    UIView.animateWithDuration(duration, animations: {

        if self.presenting {
            self.onStageViewController(childView)
            self.offStageParentViewController(parentView)
        } else {
            self.onStageViewController(parentView)
            self.offStageChildViewController(childView)
        }}, completion: { finished in
            if transitionContext.transitionWasCancelled() {
                transitionContext.completeTransition(false)
            } else {
                transitionContext.completeTransition(true)
            }
    })
}

手势和手势处理程序:

weak var fromViewController: UIViewController! {
    didSet {
        let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: "presentingViewController:")
        screenEdgePanRecognizer.edges = edge
        fromViewController.view.addGestureRecognizer(screenEdgePanRecognizer)
    }
}

func presentingViewController(pan: UIPanGestureRecognizer) {
    let percentage = getPercentage(pan)

    switch pan.state {

    case UIGestureRecognizerState.Began:
        interactive = true
        presentViewController(pan)

    case UIGestureRecognizerState.Changed:
        updateInteractiveTransition(percentage)

    case UIGestureRecognizerState.Ended:
        interactive = false

        if finishPresenting(pan, percentage: percentage) {
            finishInteractiveTransition()
        } else {
            cancelInteractiveTransition()
        }

    default:
        break
    }
}

知道会发生什么吗?

编辑 2:

以下是未公开的方法:

override func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat {
    let translation = pan.translationInView(pan.view!)
    return abs(translation.x / pan.view!.bounds.width)
}

override func onStageViewController(view: UIView) {
    view.transform = CGAffineTransformIdentity
}

override func offStageParentViewController(view: UIView) {
    view.transform = CGAffineTransformMakeTranslation(-view.bounds.width / 2, 0)
}

override func offStageChildViewController(view: UIView) {
    view.transform = CGAffineTransformMakeTranslation(view.bounds.width, 0)
}

override func presentViewController(pan: UIPanGestureRecognizer) {
    let location = pan.locationInView((fromViewController as! MainViewController).tableView)
    let indexPath = (fromViewController as! MainViewController).tableView.indexPathForRowAtPoint(location)

    if indexPath == nil {
        pan.state = .Failed
        return
    }
    fromViewController.performSegueWithIdentifier("chartSegue", sender: pan)
}

But the question is why, when doing a fast interactive gesture, is it not responding quickly enough

只要我的手指离开足够长的时间,它实际上可以使用快速交互手势。例如,如果我在超过(比方说)1 厘米的范围内非常快速地平移,没关系。如果我在小于 1 厘米的小表面(再说一遍)上快速平移是不行的

Possible candidates include the views being animated are too complicated (or have complicated effects like shading)

我也想过一个复杂的观点,但我不认为我的观点真的很复杂。有一堆按钮和标签,一个自定义 UIControl 作为分段,一个图表(一旦控制器出现就加载)和一个 xib 在 viewController.[=41= 中加载]


好的,我刚刚用 MINIMUM 类 和对象创建了一个 project 来触发问题。因此,要触发它,您只需从右向左快速而短暂地滑动即可。

我注意到它第一次工作起来很容易,但是如果你第一次正常拖动视图控制器,那么触发它会变得更加困难(甚至不可能?)。在我的整个项目中,这并不重要。

当我诊断这个问题时,我注意到手势的变化和结束状态事件发生在animateTransition甚至运行之前。所以动画还没开始就canceled/finished了!

我尝试使用 GCD 动画同步队列来确保 UIPercentDrivenInterativeTransition 的更新不会在 `animate:

之后发生
private let animationSynchronizationQueue = dispatch_queue_create("com.domain.app.animationsynchronization", DISPATCH_QUEUE_SERIAL)

然后我有了一个实用方法来使用这个队列:

func dispatchToMainFromSynchronizationQueue(block: dispatch_block_t) {
    dispatch_async(animationSynchronizationQueue) {
        dispatch_sync(dispatch_get_main_queue(), block)
    }
}

然后我的手势处理程序确保更改和结束状态通过该队列路由:

func handlePan(gesture: UIPanGestureRecognizer) {
    switch gesture.state {

    case .Began:
        dispatch_suspend(animationSynchronizationQueue)
        fromViewController.performSegueWithIdentifier("segueID", sender: gesture)

    case .Changed:
        dispatchToMainFromSynchronizationQueue() {
            self.updateInteractiveTransition(percentage)
        }

    case .Ended:
        dispatchToMainFromSynchronizationQueue() {
            if isOkToFinish {
                self.finishInteractiveTransition()
            } else {
                self.cancelInteractiveTransition()
            }
        }

    default:
        break
    }
}

因此,我让手势识别器的 .Began 状态暂停该队列,并且我让动画控制器在 animationTransition 中恢复该队列(确保队列仅在该方法运行之前再次启动手势继续尝试更新 UIPercentDrivenInteractiveTransition 对象。

有同样的问题,尝试使用serialQueue.suspend()/resume(),没有用。

这个问题是因为pan手势太快时,结束状态早于animateTransition开始,然后context.completeTransition得不到运行,整个动画就乱了。

当这种情况发生时,我的解决方案是强制 运行 context.completeTransition。

比如我有两个类:

class SwipeInteractor: UIPercentDrivenInteractiveTransition {
    var interactionInProgress = false

    ...
}

class AnimationController: UIViewControllerAnimatedTransitioning {
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        if !swipeInteractor.interactionInProgress {
            DispatchQueue.main.asyncAfter(deadline: .now()+transitionDuration) {
                if context.transitionWasCancelled {
                    toView?.removeFromSuperview()
                } else {
                    fromView?.removeFromSuperview()
                }
                context.completeTransition(!context.transitionWasCancelled)
            }
        }

        ...
    }

    ...
}

interactionInProgress 在手势开始时设置为 true,在手势结束时设置为 false。

我遇到了类似的问题,但是程序化动画触发器没有触发动画完成块。我的解决方案类似于 Sam 的解决方案,除了不是在稍有延迟后进行调度,而是在 UIPercentDrivenInteractiveTransition 实例上手动调用 finish

class SwipeInteractor: UIPercentDrivenInteractiveTransition {
  var interactionInProgress = false
  ...
}

class AnimationController: UIViewControllerAnimatedTransitioning {
  private var swipeInteractor: SwipeInteractor
  ..
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    ...
    if !swipeInteractor.interactionInProgress {
      swipeInteractor.finish()
    }
    ...
    UIView.animateWithDuration(...)
  }
}