Переход контроллера интерактивного представления - странное поведение (застревание в переходе) - PullRequest
0 голосов
/ 11 января 2019

Я пытаюсь реализовать собственный жест 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(_:) метода).

Я проверил view hierarchy в Xcode после того, как произошла эта ошибка, и я получил это:

view hierarchy

Во-первых, он, похоже, застрял в переходе (все еще внутри UITransitionView).

Во-вторых, с левой стороны вы видите views первого view controller (с которого я начинаю переход). Однако на изображении виден только красный view controller, который должен был быть представлен.

Ты хоть представляешь, что происходит? Я пытался понять это последние 3 часа, но не могу заставить его работать должным образом. Буду признателен за любую помощь

Спасибо!

EDIT

Хорошо, я нашел способ воспроизвести его 100% времени. Я также создал отдельный проект, демонстрирующий проблему (он немного отличается по структуре, потому что я пробовал много вещей, но результат все тот же)

Вот проект: https://github.com/d3mueller/InteractiveTransitionDemo2

Как воспроизвести проблему:

Проведите справа налево, а затем быстро слева направо. Это вызовет ошибку.

Также, похожая ошибка появится, когда вы проведете справа налево очень быстро несколько раз. Затем он фактически запустит переход и завершит его правильно (но он даже не должен начинаться, потому что при движении справа налево значение progress остается равным 0,0)

1 Ответ

0 голосов
/ 05 апреля 2019

Вы можете попробовать установить:

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

Удачи и любопытно услышать, если вы поймете это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...