如何在iOS上创建一个可中断的视图控制器过渡?

3
iOS 10增加了一个自定义动画视图控制器转换的新功能,称为interruptibleAnimator(using:)
许多人似乎正在使用这个新功能,然而他们只是在interruptibleAnimator(using:)中实现旧的animateTransition(using:)方法(请参见2016年的Session 216),并没有真正使用可中断动画器来创建可中断的转换。例如,我使用UIPanGestureRecognizer创建了两个UIViewController之间的自定义转换。两个视图控制器都有设置backgroundColor属性,以及一个位于中间的UIButton,在touchUpInside时更改背景颜色。
现在,我只需将动画实现为:
1.将toViewController.view设置为位于fromViewController.view的左侧/右侧(根据需要的方向)。
2.在UIViewPropertyAnimator的动画块中,我将toViewController.view滑入视图,并将fromViewController.view滑出视图(屏幕外)。
但是,在转换期间,我希望能够按下UIButton,但是无法响应按钮按下事件。奇怪的是,这就是该会话所暗示的方式,我将自定义UIView设置为我的两个UIViewController的视图。
class HitTestView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view is UIButton {
            print("hit button, point: \(point)")
        }
        return view
    }
}

class ViewController: UIViewController {

     let button = UIButton(type: .custom)

     override func loadView() {
         self.view = HitTestView(frame: UIScreen.main.bounds)
     }
    <...>
}

并且记录下了func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 的结果。虽然UIButton正在接受hitTest,但按钮的操作并没有被调用。

有人能让这个工作起来吗?

我想错了吗?中断式转换只是暂停/恢复过渡动画,而不是用于交互吗?

iOS11几乎全部使用了我认为是中断式转换,这使你可以将控制中心拉到50%的位置并与之交互,而不需要释放控制中心窗格 然后再将其滑回去。这正是我想要做的。

提前感谢!今年夏天我花了很长时间尝试让这个工作,或者找到其他人也在尝试同样的事情。


我自己也在为同样的问题苦苦挣扎。很遗憾,这方面没有任何适当的文档资料。你现在有什么发现吗? - Christoph
2个回答

2

我已经发布了示例代码和一个可重复使用的框架,演示了可中断的视图控制器动画转换。它被称为PullTransition,通过向下滑动轻松实现视图控制器的关闭或弹出。如果文档需要改进,请告诉我。希望这能帮到你!


1

这里有一个可中断转换的简短示例。在addAnimation块中添加自己的动画以启动事物。

 class ViewController: UIViewController {
  var dismissAnimation: DismissalObject?
  override func viewDidLoad() {
    super.viewDidLoad()
    self.modalPresentationStyle = .custom
    self.transitioningDelegate = self
    dismissAnimation = DismissalObject(viewController: self)
  }
}

extension ViewController: UIViewControllerTransitioningDelegate {
  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return dismissAnimation
  }

  func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    guard let animator = animator as? DismissalObject else { return nil }
    return animator
  }
}

class DismissalObject: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning {
  fileprivate var shouldCompleteTransition = false
  var panGestureRecongnizer: UIPanGestureRecognizer!
  weak var viewController: UIViewController!
  fileprivate var propertyAnimator: UIViewPropertyAnimator?
  var startProgress: CGFloat = 0.0

  var initiallyInteractive = false
  var wantsInteractiveStart: Bool {
    return initiallyInteractive
  }

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

  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 8.0 // slow animation for debugging
  }

  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}

  func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
    let animator = interruptibleAnimator(using: transitionContext)
    if transitionContext.isInteractive {
        animator.pauseAnimation()
    } else {
        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: .from),
        let toVC = transitionContext.viewController(forKey: .to)
        else { fatalError("fromVC or toVC not found") }

    let containerView = transitionContext.containerView

    // Do prep work for animations

    let duration = transitionDuration(using: transitionContext)
    let timingParameters = UICubicTimingParameters(animationCurve: .easeOut)
    let animator = UIViewPropertyAnimator(duration: duration, timingParameters: timingParameters)
    animator.addAnimations {
        // animations
    }

    animator.addCompletion { [weak self] (position) in
        let didComplete = position == .end
        if !didComplete {
            // transition was cancelled
        }

        transitionContext.completeTransition(didComplete)

        self?.startProgress = 0
        self?.propertyAnimator = nil
        self?.initiallyInteractive = false
    }

    self.propertyAnimator = animator
    return animator
  }

  @objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
    switch gestureRecognizer.state {
    case .began:
        initiallyInteractive = true
        if !viewController.isBeingDismissed {
            viewController.dismiss(animated: true, completion: nil)
        } else {
            propertyAnimator?.pauseAnimation()
            propertyAnimator?.isReversed = false
            startProgress = propertyAnimator?.fractionComplete ?? 0.0
        }
        break
    case .changed:
        let translation = gestureRecognizer.translation(in: nil)

        var progress: CGFloat = translation.y / UIScreen.main.bounds.height
        progress = CGFloat(fminf(fmaxf(Float(progress), -1.0), 1.0))

        let velocity = gestureRecognizer.velocity(in: nil)
        shouldCompleteTransition = progress > 0.3 || velocity.y > 450

        propertyAnimator?.fractionComplete = progress + startProgress
        break
    case .ended:
        if shouldCompleteTransition {
            propertyAnimator?.startAnimation()
        } else {
            propertyAnimator?.isReversed = true
            propertyAnimator?.startAnimation()
        }
        break
    case .cancelled:
        propertyAnimator?.isReversed = true
        propertyAnimator?.startAnimation()
        break
    default:
        break
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接