交互式视图控制器转换 - 奇怪的行为(陷入过渡)

问题描述 投票:0回答:1

我正在尝试实现一个自定义的pan手势,以交互式过渡到新的view controller。它的工作方式是我有一个按钮(标记为“模板编辑器”,见下文),你可以在其上启动一个pan将当前的view controller移动到右边,显示它旁边的新view controller(我记录了我的问题, 见下文)。

一切正常,但有一个我根本不懂的错误:

有时候,当我只是轻扫按钮(触发pan手势)然后再次抬起我的手指(触摸 - >快速,向右轻扫 - >触摸)交互式过渡故障。它开始非常缓慢地完成过渡,之后,我不能解雇所呈现的view controller,我也不能提出任何有关view controller的内容。

我不知道为什么。这是我的代码:

首先,UIViewControllerAnimatedTransitioning类。它是使用UIViewPropertyAnimator实现的,只是使用transform添加动画:

class MovingTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    enum Direction {
        case left, right
    }

    // MARK: - Properties
    // ========== PROPERTIES ==========
    private var animator: UIViewImplicitlyAnimating?

    var duration = 0.6
    var presenting = true

    var shouldAnimateInteractively: Bool = false

    public var direction: Direction = .left
    private var movingMultiplicator: CGFloat {
        return direction == .left ? -1 : 1
    }
    // ====================

    // MARK: - Initializers
    // ========== INITIALIZERS ==========

    // ====================

    // MARK: - Overrides
    // ========== OVERRIDES ==========

    // ====================


    // MARK: - Functions
    // ========== FUNCTIONS ==========
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let animator = interruptibleAnimator(using: transitionContext)
        animator.startAnimation()
    }

    func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {

        // If the animator already exists, return it (important, see documentation!)
        if let animator = self.animator {
            return animator
        }

        // Otherwise, create the animator

        let containerView = transitionContext.containerView
        let fromView = transitionContext.view(forKey: .from)!
        let toView = transitionContext.view(forKey: .to)!

        if presenting {
            toView.frame = containerView.frame
            toView.transform = CGAffineTransform(translationX: movingMultiplicator * toView.frame.width, y: 0)
        } else {
            toView.frame = containerView.frame
            toView.transform = CGAffineTransform(translationX: -movingMultiplicator * toView.frame.width, y: 0)
        }

        containerView.addSubview(toView)

        let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.9, animations: nil)

        animator.addAnimations {
            if self.presenting {
                toView.transform = .identity
                fromView.transform = CGAffineTransform(translationX: -self.movingMultiplicator * toView.frame.width, y: 0)
            } else {
                toView.transform = .identity
                fromView.transform = CGAffineTransform(translationX: self.movingMultiplicator * toView.frame.width, y: 0)
            }
        }

        animator.addCompletion { (position) in
            // Important to set frame above (device rotation will otherwise mess things up)
            toView.transform = .identity
            fromView.transform = .identity

            if !transitionContext.transitionWasCancelled {
                self.shouldAnimateInteractively = false
            }

            self.animator = nil
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }

        self.animator = animator
        return animator
    }
    // ====================


}

这是增加交互性的部分。这是我添加到按钮的UIPanGestureRecognizer调用的方法。

    public lazy var transitionAnimator: MovingTransitionAnimator = MovingTransitionAnimator()
    public lazy var interactionController = UIPercentDrivenInteractiveTransition()

    ...

    @objc private func handlePan(pan: UIPanGestureRecognizer) {
        let translation = pan.translation(in: utilityView)
        var progress = (translation.x / utilityView.frame.width)
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

        switch pan.state {
        case .began:

            // This is a flag that helps me distinguish between when a user taps on the button and when he starts a pan
            transitionAnimator.shouldAnimateInteractively = true

            // Just a dummy view controller that's dismissing as soon as its been presented (the problem occurs with every view controller I use here)
            let vc = UIViewController()
            vc.view.backgroundColor = .red
            vc.transitioningDelegate = self
            present(vc, animated: true, completion: {
                self.transitionAnimator.shouldAnimateInteractively = false
                vc.dismiss(animated: true, completion: nil)
            })
        case .changed:
            interactionController.update(progress)
        case .cancelled:
            interactionController.cancel()
        case .ended:
            if progress > 0.55 || pan.velocity(in: utilityView).x > 600 
                interactionController.completionSpeed = 0.8
                interactionController.finish()
            } else {
                interactionController.completionSpeed = 0.8
                interactionController.cancel()
            }
        default:
            break
        }
    }

我还实现了所有必要的委托方法:

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transitionAnimator.presenting = true
        return transitionAnimator
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transitionAnimator.presenting = false
        return transitionAnimator
    }

    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        guard let animator = animator as? MovingTransitionAnimator, animator.shouldAnimateInteractively else { return nil }
        return interactionController
    }

    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        guard let animator = animator as? MovingTransitionAnimator, animator.shouldAnimateInteractively else { return nil }
        return interactionController
    }

而已。它背后没有更多的逻辑(我想;如果你需要更多的信息,请告诉我),但它仍然有这个错误。这是一个错误的录音。你无法真正看到我的触摸,但我正在做的就是降低 - >快速,很快向右滑动 - >接触。在这个真正缓慢的过渡完成之后,我无法解雇红色的view controller。它停留在那里:

demonstration

这是更奇怪的事情:

当发生这种情况时,interactionController.finish()interactionController.cancel()都没有被调用(至少不是来自我的handlePan(_:)method)。

在发生这个错误之后,我检查了view hierarchy中的Xcode,我得到了这个:

view hierarchy

首先,它似乎停留在过渡期(一切都在UITransitionView内)。

其次,在左侧你会看到第一个viewsview controller(我开始转换的那个)。然而,在图像上只有可见的红色view controller,即将呈现的那个。

你知道发生了什么吗?我一直试图在过去的3个小时内解决这个问题,但我无法让它正常工作。我很感激任何帮助

谢谢!

编辑

好的,我找到了一种方法来100%重现它。我还创建了一个展示问题的孤立项目(它的结构有点不同,因为我尝试了很多东西,但结果仍然完全一样)

这是项目:https://github.com/d3mueller/InteractiveTransitionDemo2

如何重现问题:

从右向左滑动,然后从左向右快速滑动。这将触发错误。

此外,当您从右向左快速滑动多次时,会出现类似的错误。然后它将实际运行转换并正确完成它(但它甚至不应该开始,因为从右向左移动使progress保持在0.0)

ios swift uigesturerecognizer
1个回答
0
投票

您可以尝试设置:

/// Set this to NO in order to start an interruptible transition non
/// interactively. By default this is YES, which is consistent with the behavior
/// before 10.0.
@property (nonatomic) BOOL wantsInteractiveStart NS_AVAILABLE_IOS(10_0);

你的NO上的interactionController

祝你好运并好奇,如果你弄明白的话。

© www.soinside.com 2019 - 2024. All rights reserved.