UIPercentDrivenInteractiveTransition结束一半时在“取消”上触发动画

时间:2019-12-27 02:08:16

标签: ios uiviewanimationtransition

如果交互结束并且取消了动画,则将视图转换强制自定义过渡回到原始位置是一个好习惯。我有以下实现,它决定何时以平移手势关闭视图控制器。如果平移手势较早结束,则我希望在考虑持续时间与平移手势上的progress值成比例之前,先像原来一样设置动画位置。

protocol AnimationControllerDelegate: AnyObject {
  func shouldHandlePanelInteractionGesture() -> Bool
}

typealias PanGestureHandler = AnimationControllerDelegate & UIViewController & Animatable

final class CustomInteractionController: UIPercentDrivenInteractiveTransition, UIGestureRecognizerDelegate {
    var interactionInProgress: Bool = false
    private var shouldCompleteTransition: Bool = false
    private var startTransitionY: CGFloat = 0
    private var panGestureRecognizer: UIPanGestureRecognizer?
    private weak var viewController: PanGestureHandler?

    func wireToViewController(viewController: Any) {
        guard let viewControllerDelegate = viewController as? PanGestureHandler else {
            return
        }

        self.viewController = viewControllerDelegate

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGestureRecognizer(_:)))

        panGestureRecognizer = panGesture
        panGestureRecognizer?.delegate  = self

        self.viewController?.view.addGestureRecognizer(panGesture)
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                           shouldRecognizeSimultaneouslyWith
        otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    @objc
    func handlePanGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer) {
        guard let childView = gestureRecognizer.view,
            let parentView = childView.superview,
            let panGestureHandler = viewController else {
            return
        }

        switch gestureRecognizer.state {
        case .began:
            break
        case .changed:
            let translation = gestureRecognizer.translation(in: parentView)
            let velocity = gestureRecognizer.velocity(in: parentView)
            let state = gestureRecognizer.state

            if !panGestureHandler.shouldHandlePanelInteractionGesture() && percentComplete == 0 {
                return
            }

            let verticalMovement = translation.y / childView.bounds.height
            let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
            let downwardMovementPercent = fminf(downwardMovement, 1.0)
            let progress = CGFloat(downwardMovementPercent)

            let alphaValue = (1 - progress) * 0.4

            panGestureHandler.shadowView.backgroundColor = Safety.Colors.backgroundViewColor(for: alphaValue)

            if abs(velocity.x) > abs(velocity.y) && state == .began {
                return
            }

            if !interactionInProgress {
                interactionInProgress = true
                startTransitionY = translation.y
                viewController?.dismiss(animated: true, completion: nil)
            } else {
                shouldCompleteTransition = progress > 0.3
                update(progress)
            }
        case .cancelled:
            interactionInProgress = false
            startTransitionY = 0
            cancel()
        case .ended:
            interactionInProgress = false
            startTransitionY = 0
            if !shouldCompleteTransition {
                // Can I call a custom transition here back to original position?
                cancel()
            } else {
                finish()
            }
        case .failed:
            interactionInProgress = false
            startTransitionY = 0
            cancel()
        default:
            break
        }
    }
}

1 个答案:

答案 0 :(得分:1)

  

如果交互结束并且取消了动画,是否将视图转换强制自定义过渡回到原始位置是一个好习惯。

是的,我认为,如果用户取消手势,将其反转是一种很好的做法。但是您不必“强迫”它。您完成了过渡,仅指示您是要完成还是撤消过渡。因此,如果您取消动画,它将自动反转并返回到自动为您恢复的位置。除了cancel,您无需执行其他任何操作。

当然,这假定是在动画制作者的动画完成块中,您在completeTransition中指示它是否完成:

UIView.animate(withDuration: 0.25, animations: {
    ...
}, completion: { _ in
    transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})

因此,如果不取消动画,动画将完成。但是,如果取消,它将反转。


个人而言,在我的手势识别器中,我通常会执行以下操作:

  • 如果手势state.began,我将:

    • 实例化UIPercentDrivenInteractiveTransitionUIViewControllerTransitioningDelegate返回的interactionControllerForPresentation(using);和
    • 执行present / dismiss UIPercentDrivenInteractiveTransition
  • 当手势以state的{​​{1}}更新时,.changedupdate;和

  • 完成后,您UIPercentDrivenInteractiveTransitionfinish。并且cancel将自动触发反向动画。

例如,我从左到右的“关闭”手势最终看起来像:

cancel

@objc func handleLeftToRightPan(_ gesture: UIPanGestureRecognizer) { let percent = (gesture.translation(in: gesture.view).x) / gesture.view!.bounds.width switch gesture.state { case .began: interactionController = UIPercentDrivenInteractiveTransition() dismiss(animated: true) interactionController?.update(percent) case .changed: interactionController?.update(percent) case .ended, .cancelled: let velocity = gesture.velocity(in: gesture.view).x if velocity > 0 || (velocity == 0 && percent > 0.5) { interactionController?.finish() } else { interactionController?.cancel() } interactionController = nil default: break } } finish的逻辑而言,我个人检查一下是否其中一个:

  • 是手势方向上的cancel(以从左到右的“ dismiss”手势,这意味着我检查它是否为velocity正速度) ...这意味着,即使用户在屏幕上滑动了很小的一部分,我仍然会认为这是完全过渡的意图;

  • 仅当x为零时,我才检查完成百分比(例如,如果它们跨过¾,停下来放手,我假设他们想velocity手势,但如果它们只走过1/4步并停下来放手,我假设他们打算取消手势。

  • 不用说,如果他们反转手势的方向,我认为他们取消了完成过渡的意图

但是您可以看到,除了finish我什么也没做,动画会自动反转。因此,在这里,我开始从第二个视图控制器返回到第一个视图的解雇转换,但是cancel cancel(在这种情况下,通过反转手势的方向并放开)和我的动画师会在其完成处理程序中将适当的UIPercentDrivenInteractiveTransition值传递给其完成处理程序中的Bool,并且它会自动执行动画,而无需我做任何工作:

enter image description here