将 UIPercentDrivenInteractiveTransition 与 CABasicAnimation 一起使用时出现奇怪的故障

Using UIPercentDrivenInteractiveTransition with CABasicAnimation has weird glitch

我正在使用 CABasicAnimationUIView.animate 实现自定义转换。还需要使用 UIPercentDrivenInteractiveTransition 实现自定义交互过渡,它完全复制了原生 iOS 向后滑动的行为。没有向后滑动手势的动画(当我通过向后箭头推动和弹出时)工作正常且流畅。此外,向后滑动也很流畅,除非手势速度大于900

手势识别功能:

@objc func handleBackGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
        guard animationTransition != nil else { return }
        switch gesture.state {
        case .began:
            interactionController = TransparentNavigationControllerTransitionInteractor(duration: anumationDuration)
            popViewController(animated: true)

        case .changed:
            guard let view = gesture.view?.superview else { return }
            let translation = gesture.translation(in: view)
            var percentage = translation.x / view.bounds.size.width
            percentage = min(1.0, max(0.0, percentage))
            shouldCompleteTransition = percentage > 0.5
            interactionController?.update(percentage)

        case .cancelled, .failed, .possible:
            if let interactionController = self.interactionController {
                isInteractiveStarted = false
                interactionController.cancel()
            }

        case .ended:
            interactionController?.completionSpeed = 0.999
            let greaterThanMaxVelocity = gesture.velocity(in: view).x > 800
            let canFinish = shouldCompleteTransition || greaterThanMaxVelocity
            canFinish ? interactionController?.finish() : interactionController?.cancel()
            interactionController = nil

        @unknown default: assertionFailure()
        }
    }

UIPercentDrivenInteractiveTransition class。这里我同步图层动画。

final class TransparentNavigationControllerTransitionInteractor: UIPercentDrivenInteractiveTransition {

    // MARK: - Private Properties

    private var context: UIViewControllerContextTransitioning?
    private var pausedTime: CFTimeInterval = 0
    private let animationDuration: TimeInterval

    // MARK: - Initialization

    init(duration: TimeInterval) {
        self.animationDuration = duration * 0.4 // I dk why but layer duration should be less 
        super.init()
    }

    // MARK: - Public Methods

    override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
        super.startInteractiveTransition(transitionContext)
        context = transitionContext

        pausedTime = transitionContext.containerView.layer.convertTime(CACurrentMediaTime(), from: nil)
        transitionContext.containerView.layer.speed = 0
        transitionContext.containerView.layer.timeOffset = pausedTime
    }

    override func finish() {
        restart(isFinishing: true)
        super.finish()
    }

    override func cancel() {
        restart(isFinishing: false)
        super.cancel()
    }

    override func update(_ percentComplete: CGFloat) {
        super.update(percentComplete)
        guard let transitionContext = context else { return }
        let progress = CGFloat(animationDuration) * percentComplete
        transitionContext.containerView.layer.timeOffset = pausedTime + Double(progress)
    }

    // MARK: - Private Methods 

    private func restart(isFinishing: Bool) {
        guard let transitionLayer = context?.containerView.layer else { return }
        transitionLayer.beginTime = transitionLayer.convertTime(CACurrentMediaTime(), from: nil)
        transitionLayer.speed = isFinishing ? 1 : -1
    }
}

这是我在 UIViewControllerAnimatedTransitioning class[ 中的 关闭动画 函数=14=]


private func runDismissAnimationFrom(
        _ fromView: UIView,
        to toView: UIView,
        in transitionContext: UIViewControllerContextTransitioning) {

        guard let toViewController = transitionContext.viewController(forKey: .to) else { return }
        toView.frame = toView.frame.offsetBy(dx: -fromView.frame.width / 3, dy: 0)

        let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
        let fromViewFinalFrame = fromView.frame.offsetBy(dx: fromView.frame.width, dy: 0)

        // Create mask to hide bottom view with sliding
        let slidingMask = CAShapeLayer()
        let initialMaskPath = UIBezierPath(rect: CGRect(
            x: fromView.frame.width / 3,
            y: 0,
            width: 0,
            height: toView.frame.height)
        )
        let finalMaskPath = UIBezierPath(rect: toViewFinalFrame)
        slidingMask.path = initialMaskPath.cgPath
        toView.layer.mask = slidingMask
        toView.alpha = 0

        let slidingAnimation = CABasicAnimation(keyPath: "path")
        slidingAnimation.fromValue = initialMaskPath.cgPath
        slidingAnimation.toValue = finalMaskPath.cgPath
        slidingAnimation.timingFunction = .init(name: .linear)
        slidingMask.path = finalMaskPath.cgPath
        slidingMask.add(slidingAnimation, forKey: slidingAnimation.keyPath)

        UIView.animate(
            withDuration: duration,
            delay: 0,
            options: animationOptions,
            animations: {
                fromView.frame = fromViewFinalFrame
                toView.frame = toViewFinalFrame
                toView.alpha = 1
        },
            completion: { _ in
                toView.layer.mask = nil
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        })
    }

我注意到只有当滑动速度很快时才会出现故障。 这是一个视频,其结果是正常速度下动画流畅而高速下不流畅 - https://youtu.be/1d-kTPlhNvE

UPD: 我已经尝试使用 UIViewPropertyAnimatorinterruptibleAnimator(使用 transitionContext:UIViewControllerContextTransitioning)-> UIViewImplicitlyAnimating

但结果是另一种故障。

我已经解决了问题,只需更改重启函数的一部分:

        transitionLayer.beginTime = 
transitionLayer.convertTime(CACurrentMediaTime(), from: nil) - transitionLayer.timeOffset
        transitionLayer.speed = 1

我不太明白为什么,但看起来 timeOffset 减法有效!