【问题标题】:Glitch when interactively dismiss a modal view controller交互式关闭模态视图控制器时出现故障
【发布时间】:2018-02-07 14:42:05
【问题描述】:

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

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)
        }
    }
}

我的交互者

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

【问题讨论】:

  • 您是从后台线程调用animateTransition 函数吗?你能确定它是从主线程调用的吗?
  • @Honey, animateTransition 在dismiss animator中,由UIKit自动调用。所以我想这不是问题吗?让我知道你认为应该怎么做
  • 哈哈。我认为这是您自己编写的函数。我的错。不,这不是问题。只需确保:您没有在 Xcode 上启用慢速动画吗?为了避免模拟器>>调试>>慢动画...
  • 不,我现在什至无法在 Xcode 中找到该选项
  • 它在模拟器应用上,而不是 xcode...

标签: ios swift modalviewcontroller interactive


【解决方案1】:

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

在调用dismiss()后没有尽快更新进度时会出现该错误。发生这种情况是因为平移手势识别器的 .changed 状态仅在您拖动时触发。所以如果你拖得很慢,在 .begin 被调用和第一个 .changed 被调用之间,dismiss 过渡将开始动画。

您可以在模拟器中看到这一点,方法是非常缓慢地逐个像素地拖动视图,直到调用 .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)
    }

【讨论】:

    【解决方案2】:

    对于交互式关闭,iOS 11 上的动画延迟必须大于 0。

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

    【讨论】:

      【解决方案3】:

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

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

      你应该做的就是把动画块中fromViewController的帧改成你想要的。

      【讨论】:

      • 嗨@Sergey。每次交互开始时都会发生故障,问题会变得更糟。另外,在过渡过程中背景是完全黑色的,然后在完成时突然变回来......
      • @JGuo,好的,我认为它已经在层次结构中是错误的。当您禁用交互式动画师时会发生什么?如果故障不会出现 - 这不是交互式动画师的问题。另一个想法:您提供的 VC(布局/帧大小,如果它通过 viewWillAppear 中的某些约束动态更改)可能有问题,因为如果您的交互式动画师调整了特殊方式,它可能会调用手势识别器的“取消”状态。这将调用您解除 VC 的 viewWillAppear ,并且如果解除动画代码,它可能会跳转,因为 VWA 中的帧与帧不同。只是想法
      • 如果我在没有交互的情况下将其关闭,一切都会完美运行。不,它只是一个普通的UIViewController,除了backgroundColor = .white
      • @JGuo 附上交互式过渡控制器代码
      • 添加,非常标准的实现,没什么花哨的
      【解决方案4】:

      我通过“接受”故障解决了同样的问题。 问题是在交互器启动之前旧视图被向下移动了一点。然后控制权被赋予交互器,将位置重置为动画的开始位置,将其向后移动。 如果交互器在被赋予控制权时在旧视图所在的相同位置开始动画,则故障几乎不会引起注意。

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

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

      您必须根据您应用的过渡持续时间找到偏移参数的正确数量。对于 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))
      

      【讨论】:

        【解决方案5】:

        我遇到了完全相同的问题,即关闭动画在由 UIPercentDrivenInteractiveTransition 控制之前“自行”开始(忽略任何交互)。 然后我认为在最初几毫秒内发生的关闭动画实际上是您自己的自定义动画,由您的实现提供:

        animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
        

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

        percentDrivenTransition.completionSpeed = 10.0
        

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

        希望对您有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-12-04
          • 2013-01-01
          • 1970-01-01
          相关资源
          最近更新 更多