交互式关闭模态视图控制器时出现故障

时间:2018-02-07 14:42:05

标签: ios swift modalviewcontroller interactive

我正在使用UIViewControllerAnimatedTransitioningUIPercentDrivenInteractiveTransition以交互方式关闭模态呈现的视图控制器。没什么太花哨的。但我注意到,当交互开始时偶尔会出现一个小故障。如果使用.curveEaseOut选项进行动画处理,则会更加明显。我正在关注的一些在线教程(https://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/)也会发生同样的事情。当我第一次向下拖动时,你可以看到gif中的故障。有什么建议吗?

enter image description here

MyDismissAnimator

class SlideInDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    // ...

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        guard let toVC = transitionContext.viewController(forKey: .to), 
        let presentedVC = transitionContext.viewController(forKey: .from) else {return}

        let presentedFrame = transitionContext.finalFrame(for: presentedVC)
        var dismissedFrame = presentedFrame
        dismissedFrame.origin.y = transitionContext.containerView.frame.size.height

        transitionContext.containerView.insertSubview(toVC.view, belowSubview: presentedVC.view)
        }
        let duration = transitionDuration(using: transitionContext)

        UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
            presentedVC.view.frame = dismissedFrame
        }) { _ in
            if transitionContext.transitionWasCancelled {
                toVC.view.removeFromSuperview()
            }
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

MyInteractor

class SwipeInteractionController: UIPercentDrivenInteractiveTransition {

    var interactionInProgress = false
    private var shouldCompleteTransition = false
    private weak var viewController: UIViewController!

    init(viewController: UIViewController) {
        self.viewController = viewController
        super.init()
        let gesture = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
        viewController.view?.addGestureRecognizer(gesture)
    }

    @objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
        var progress = (translation.y / viewController.view.bounds.height)
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            viewController.dismiss(animated: true, completion: nil)

        case .changed:
            shouldCompleteTransition = progress > 0.3
            update(progress)

        case .cancelled:
            interactionInProgress = false
            cancel()

        case .ended:
            interactionInProgress = false
            if shouldCompleteTransition {
                finish()
            } else {
                cancel()
            }
        default:
            break
        }
    }
}

5 个答案:

答案 0 :(得分:6)

我很确定这是UIPercentDrivenInteractiveTransition的错误,但我能够解决这个问题:

在调用dismiss()后,如果未尽快更新进度,则会发生错误。发生这种情况是因为平移手势识别器的状态仅在您拖动时触发。因此,如果你慢慢拖动,在调用时间.begin和调用第一个.changed之间,解雇转换将开始动画。

你可以在模拟器中通过非常缓慢地逐个像素地拖动视图来看到这一点,直到调用.begin。只要.changed不再被调用,转换将实际完成,您将看到该视图一直向下动画并完成转换。

然而,简单地在.dismiss()之后调用update(progress)也不起作用,我相信因为动画还没有开始。 (直到下一个runloop或其他东西)

我的" hack"解决方案是在非常短的时间内调度异步,有效地设置进度并在动画开始前暂停动画。

case .began:
    interactionInProgress = true
    viewController.dismiss(animated: true, completion: nil)
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
        self.update(progress)
    }

答案 1 :(得分:1)

如果您使用自定义解雇过渡动画师,则不应将toViewController的视图作为子视图添加到transitionContext的容器视图中。它已经处于层次结构中。那个错误发生在我同样的情况下,但我使用了解雇和出现自定义动画。我认为只有自定义解雇动画也会发生这种情况 删除这一行:

transitionContext.containerView.insertSubview(toVC.view, belowSubview: presentedVC.view)

所有你应该这样做 - 将动画块中fromViewController的帧更改为你想要的。

答案 2 :(得分:1)

对于临时解散,iOS 11上的动画延迟必须大于0。

ref:https://github.com/jonkykong/SideMenu/blob/master/Pod/Classes/SideMenuTransition.swift#L510

答案 3 :(得分:0)

我通过“拥抱”故障解决了同样的问题。 问题是旧的视图在交互操作员开始之前向下移动了一下。然后控制权交给了交互者,它将位置重置为动画的开头,将其移回。 如果交互操作员在旧视图被控制的同一位置开始动画,那么故障几乎不明显。

为此,我从UIView.animate中删除了缓动曲线参数,并在调用update(progress)之前将自己的缓动函数应用于进度。

我的自定义缓动函数有一个偏移参数,用于补偿交互操作员控制之前发生的移动。

您必须根据应用的转换持续时间找到offset参数的正确数量。对于0.3的转变持续时间,最佳点是0.05的偏移。

您可以在此处下载一个有效的示例:https://github.com/francosolerio/InteractiveModal

缓解功能

struct Curves {
    static func quadraticEaseIn<T: FloatingPoint>(t: T) -> T {
        return t * t
    }

    static func quadraticEaseOut<T: FloatingPoint>(t: T) -> T {
        return 1 - quadraticEaseIn(t: 1 - t)
    }

    static func QuadraticEaseInOut<T: FloatingPoint>(t: T) -> T {
        if t < 1/2 {
            return quadraticEaseIn(t: t * 2) / 2
        } else {
            return 1 - quadraticEaseIn(t: (1 - t) * 2) / 2
        }
    }

    static func quadraticEaseOut<T: FloatingPoint>(t: T, offset: T) -> T {
        return quadraticEaseOut(t: t + offset)
    }    
}

<强>动画

UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
                fromVC.view.frame = finalFrame
            },
            completion: { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        )

<强>交互器

var progress = Curves.quadraticEaseOut(t: translation.y / viewController.view.bounds.height, offset: 0.05)
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

答案 4 :(得分:0)

我有一个完全相同的问题,在被UIPercentDrivenInteractiveTransition控制之前,解雇动画“自己”开始(忽略任何交互)。 然后我认为在最初几毫秒内发生的消除动画实际上是您自己的实现的自定义动画:

animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

所以你可以做的是增加自定义动画的持续时间,例如10秒,并且不再发生初始抖动。 为了使“后交互”动画以常规速度发生(即,当您放开视图控制器时),您可以使用以下行增加完成速度或UIPercentDrivenInteractiveTransition实例:

percentDrivenTransition.completionSpeed = 10.0

没有它,动画的剩余部分将需要几秒钟,而这不是我们想要的。

希望有所帮助。