【问题标题】:UIPercentDrivenInteractiveTransition doesn't get to animation's completion on fast gestureUIPercentDrivenInteractiveTransition 无法在快速手势上完成动画
【发布时间】:2015-07-17 00:26:43
【问题描述】:

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

我从UIViewController 向我的UIScreenEdgePanGestureRecognizer 添加了一个UIScreenEdgePanGestureRecognizer。它工作得很好,除非我做一个非常快速的平底锅。

在最后一种情况下,应用程序没有响应,仍然在同一 UIViewController 上(转换似乎没有工作)但后台任务运行。当我运行Debug View Hierarchy 时,我看到的是新的UIViewController 而不是之前的UIViewController,而之前的UIView 位于过渡结束时它应该位于的位置。

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

我还可以看到,平底锅有时会从 UIGestureRecognizerState.Began 直接转到 UIGestureRecognizerState.Ended 而没有经过 UIGestureRecognizerState.Changed

当它通过UIGestureRecognizerState.Changed 时,both 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)
}
  • 我删除了“over”添加行 => 没有修复它
  • 我在.Began.Ended 中都添加了updateInteractiveTransition => 没有修复它
  • 我在 toViewController 的视图层上打开了 shouldRasterize 并一直打开它 => 没有修复它

但问题是,为什么在做快速交互手势时反应不够快

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

可能的候选包括动画视图过于复杂(或具有复杂的效果,如阴影)

我也想过一个复杂的观点,但我不认为我的观点真的很复杂。有一堆按钮和标签,一个自定义的UIControl 充当分段段,一个图表(一旦控制器出现就加载)和一个 xib 在 vi​​ewController 内加载。


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

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

【问题讨论】:

  • @Rob 我尝试了所有方法,但都没有解决问题。唯一没问题的时候是 ToViewController 几乎是空的。我通过链接到触发问题的最小项目更新了问题。感谢您的帮助。
  • @Rob 你是在模拟器上还是在设备上试过?供您参考,在模拟器上,即使使用完整的项目,我也无法重现它。我正在使用我的 iphone 6 和最新版本的 iOS
  • 好的,再次感谢您抽出宝贵的时间。我也注意到了别的东西。如果您尝试呈现 toViewController 并取消转换,则不会取消初始化该对象。下次您呈现任何视图控制器时,它将被取消初始化(即使您调用相同的视图控制器)。

标签: ios swift uiviewcontroller


【解决方案1】:

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

我尝试使用 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 对象。

【讨论】:

  • 谢谢 Rob,问几个问题。首先,有没有办法知道dispatch_suspend(animationSynchronizationQueue) 是否已被调用?我正在使用这个动画器进行多个转换,当我不暂停队列时,我在恢复它时出现错误。二、到达dispatchToMainFromSynchronizationQueue中的代码时速度等于0,正常吗?然后我捕获了队列外的速度来修复它,但不确定这是最好的方法。
  • 不,没有办法知道它是否被暂停,所以添加你自己的Bool ivar 来跟踪它。速度为零时的重新行为,不,dispatchToMainFromSynchronizationQueue 根本不会改变你的velocity == 0 逻辑,所以我怀疑还有其他事情发生。
  • 关于速度,似乎一旦达到 .Ended 并执行其中的代码,速度就会被重置。我可以看到 .Changed 中的一些代码(在动画同步队列中)在达到 .Ended 并且速度值等于 0 之后被调用。
  • 这个解决方案对我不起作用。如果动画没有完成,我最终会从控制器调用 transitionContext.completeTransition() 。因此,通过保存上下文,并在调用 completeTransition 之前等待动画持续时间(在 UIGestureRecognizerState.Ended 中)(如果尚未调用)。一个非常混乱的解决方案。
  • @sdsykes 你能给出一个你正在做什么的小代码示例吗?我面临着完全相同的问题......
【解决方案2】:

遇到同样的问题,尝试使用serialQueue.suspend()/resume(),不行。

这个问题是因为平移手势太快,结束状态早于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。

【讨论】:

    【解决方案3】:

    我遇到了类似的问题,但是程序化动画触发器没有触发动画完成块。我的解决方案类似于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(...)
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-29
      • 2021-12-29
      • 1970-01-01
      相关资源
      最近更新 更多