【问题标题】:Slowly panning in UIPercentDrivenInteractiveTransition results in glitch在 UIPercentDrivenInteractiveTransition 中缓慢平移会导致故障
【发布时间】:2018-01-12 10:26:10
【问题描述】:

在我的应用程序中,我使用由平移手势触发的 UIPercentDrivenInteractiveTransition 来关闭 viewController。我希望我的 viewController 在平移时被拖到右侧。然而,当我慢慢平移时,我遇到了一个小故障:viewController 快速地从左向右跳了一下。 下面是 transition 的代码:

class FilterHideTransition: UIPercentDrivenInteractiveTransition {

    let viewController: FilterViewController
    var enabled = false

    private let panGesture = UIPanGestureRecognizer()
    private let tapGesture = UITapGestureRecognizer()

    init(viewController: FilterViewController) {
        self.viewController = viewController
        super.init()
        panGesture.addTarget(self, action: #selector(didPan(with:)))
        panGesture.cancelsTouchesInView = false
        panGesture.delegate = self

        tapGesture.addTarget(self, action: #selector(didTap(with:)))
        tapGesture.cancelsTouchesInView = false
        tapGesture.delegate = self

        viewController.view.addGestureRecognizer(panGesture)
        viewController.view.addGestureRecognizer(tapGesture)
    }
}

//MARK: - Actions
private extension FilterHideTransition {

    @objc func didPan(with recognizer: UIPanGestureRecognizer) {
        let translation = recognizer.translation(in: viewController.view)
        let percentage = translation.x / viewController.view.frame.size.width

        print(percentage)

        switch recognizer.state {
        case .began:
            enabled = true
            viewController.dismiss(animated: true, completion: nil)
            break
        case .changed:
            update(percentage)
            break
        case .ended:
            completionSpeed = 0.3
            if percentage > 0.5 {
                finish()
            } else {
                cancel()
            }
            enabled = false
            break
        case .cancelled:
            cancel()
            enabled = false
            break
        default:
            cancel()
            enabled = false
            break
        }
    }

    @objc func didTap(with recognizer: UITapGestureRecognizer) {
        viewController.dismiss(animated: true, completion: nil)
    }

    func isTouch(touch: UITouch, in view: UIView) -> Bool {
        let touchPoint = touch.location(in: view)
        return view.hitTest(touchPoint, with: nil) != nil
    }
}

extension FilterHideTransition: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if gestureRecognizer == tapGesture {
            return !isTouch(touch: touch, in: viewController.panel)
        } else if gestureRecognizer == panGesture {
            return  !isTouch(touch: touch, in: viewController.heightSlider) &&
                !isTouch(touch: touch, in: viewController.widthSlider) &&
                !isTouch(touch: touch, in: viewController.priceSlider)
        } else {
            return true
        }
    }
}

这是 animator 的代码:

class FilterHideAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
              let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)  as? OverlayTabBarController
              else { return }

        let startFrame = fromVC.view.frame
        let endFrame = CGRect(x: startFrame.size.width, y: 0, width: startFrame.size.width, height: startFrame.size.height)

        UIView.animate(withDuration: transitionDuration(using: transitionContext),
                   delay: 0.0,
                   options: .curveEaseIn,
                   animations: {
                        fromVC.view.frame = endFrame
                        toVC.overlay.alpha = 0
                    },
                   completion: {
                        _ in
                        if transitionContext.transitionWasCancelled {
                            transitionContext.completeTransition(false)
                        } else {
                            transitionContext.completeTransition(true)
                        }
                    })
    }
}

我的问题:如何防止这种故障发生?

【问题讨论】:

  • 你能添加动画实现吗?
  • 好的,来了! -> 添加
  • 你有没有想过这个问题? UIPercentDrivenInteractiveTransition 刚刚坏了吗?
  • @AlexMedearis 当我发现 UIViewPropertyAnimator 对我有用时,我不再关心了。这实际上与 UIPercentDrivenInteractiveTransition 一起工作得很好,所以绝对没有坏。我猜 UIView.Animate 在交互中使用时并不是那么好。

标签: ios swift uipangesturerecognizer


【解决方案1】:

我测试了你的最小工作示例,同样的问题再次出现。我无法使用 UIView.animate API 修复它,但如果您使用 UIViewPropertyAnimator,问题不会出现 - 唯一的缺点是 UIViewPropertyAnimator 仅适用于 iOS 10+。

iOS 10+ 解决方案

首先重构 HideAnimator 以实现 interruptibleAnimator(using:) 以返回执行转换动画器的 UIViewPropertyAnimator 对象(请注意,根据文档,我们应该为正在进行的转换返回相同的动画器对象):

class HideAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    fileprivate var propertyAnimator: UIViewPropertyAnimator?

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25
    }

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

    func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
        // as per documentation, we need to return existing animator
        // for ongoing transition
        if let propertyAnimator = propertyAnimator {
            return propertyAnimator
        }

        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
            else { fatalError() }

        let startFrame = fromVC.view.frame
        let endFrame = CGRect(x: startFrame.size.width, y: 0, width: startFrame.size.width, height: startFrame.size.height)

        let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: UICubicTimingParameters(animationCurve: .easeInOut))
        animator.addAnimations {
            fromVC.view.frame = endFrame
        }
        animator.addCompletion { (_) in
            if transitionContext.transitionWasCancelled {
                transitionContext.completeTransition(false)
            } else {
                transitionContext.completeTransition(true)
            }
            // reset animator because the current transition ended
            self.propertyAnimator = nil
        }
        self.propertyAnimator = animator
        return animator
    }
}

最后一件事让它工作,在didPan(with:)删除以下行:

completionSpeed = 0.3

这将使用默认速度(即1.0,或者您可以明确设置)。使用interruptibleAnimator(using:)时,完成速度会根据动画师的fractionComplete自动计算。

【讨论】:

  • 感谢您的回答,但不幸的是,这两个建议都没有运气:(
  • @Menno 你能提供一个最小的工作示例吗?
  • 好的,今天晚些时候会确保有一个。
  • @Menno 您针对哪些 iOS 版本? iOS10+方案可以吗?
  • Nosál 我的目标是 11,但 10 也可能有同样的问题。
【解决方案2】:

所以问题实际上是当您启动交互式过渡时,动画会尝试完整运行。如果您在手势更改状态下设置断点,您可以看到整个动画运行,当您恢复时,它会重新开始跟踪您的手指。我尝试了很多技巧,将交互式过渡的进度设置为 0,但似乎没有任何效果。

解决方案包括在过渡期间将过渡上下文的容器视图的层速度设置为 0,并在过渡准备完成时将其设置回 1。我将这段代码抽象为UIPercentDrivenInteractiveTransition 的一个简单子类。代码如下所示:

@implementation InteractiveTransition {
  id<UIViewControllerContextTransitioning> _transitionContext;
}

- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
  _transitionContext = transitionContext;
  [_transitionContext containerView].layer.speed = 0;
  [super startInteractiveTransition:transitionContext];
}

- (void)finishInteractiveTransition {
  [_transitionContext containerView].layer.speed = 1;
  [super finishInteractiveTransition];
}

- (void)cancelInteractiveTransition {
  [_transitionContext containerView].layer.speed = 1;
  [super cancelInteractiveTransition];
}

@end

这将暂停动画,直到您准备好完成或取消交互式过渡。

【讨论】:

  • 恕我直言,这看起来比上面的解决方案更干净。我刚刚测试过,我工作正常。谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-30
相关资源
最近更新 更多