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

前端 未结 3 1453
礼貌的吻别
礼貌的吻别 2021-02-04 19:05

I have created an interactive transition. My func animateTransition(transitionContext: UIViewControllerContextTransitioning) is quite normal, I get the container

相关标签:
3条回答
  • 2021-02-04 19:21

    I had a similar problem, but with programmatic animation triggers not triggering the animation completion block. My solution was like Sam's, except instead of dispatching after a small delay, manually call finish on the UIPercentDrivenInteractiveTransition instance.

    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(...)
      }
    }
    
    0 讨论(0)
  • 2021-02-04 19:31

    When I was diagnosing this problem, I noticed that the gesture's change and ended state events were taking place before animateTransition even ran. So the animation was canceled/finished before it even started!

    I tried using GCD animation synchronization queue to ensure that the updating of the UIPercentDrivenInterativeTransition doesn't happen until after `animate:

    private let animationSynchronizationQueue = dispatch_queue_create("com.domain.app.animationsynchronization", DISPATCH_QUEUE_SERIAL)
    

    I then had a utility method to use this queue:

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

    And then my gesture handler made sure that changes and ended states were routed through that queue:

    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
        }
    }
    

    So, I have the gesture recognizer's .Began state suspend that queue, and I have the animation controller resume that queue in animationTransition (ensuring that the queue starts again only after that method runs before the gesture proceeds to try to update the UIPercentDrivenInteractiveTransition object.

    0 讨论(0)
  • 2021-02-04 19:34

    Have the same issue, tried to use serialQueue.suspend()/resume(), does not work.

    This issue is because when pan gesture is too fast, end state is earlier than animateTransition starts, then context.completeTransition can not get run, the whole animation is messed up.

    My solution is forcing to run context.completeTransition when this situation happened.

    For example, I have two classes:

    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 is set to true when gesture began, set to false when gesture ends.

    0 讨论(0)
提交回复
热议问题