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



iOS 10 为自定义动画视图控制器过渡添加了一个名为可中断动画器(使用:(

很多人似乎都在使用这个新功能,但是通过简单地在 interruptibleAnimator(using:( 中的 UIViewPropertyAnimator 的动画块中实现他们的旧animateTransition(using:((参见 2016 年的会话 216(

但是,我找不到一个人实际使用可中断动画器来创建可中断过渡的单个示例。每个人似乎都支持它,但实际上没有人使用它。

例如,我使用 UIPanGestureRecognizer 在两个 UIViewControllers 之间创建了一个自定义过渡。两个视图控制器都有一个背景颜色集,中间有一个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(_ 点:CGPoint,事件:UIEvent?( -> UIView?结果。正在测试UIButton,但是,不会调用按钮操作。

有没有人让这个工作?

我是否想错了,可中断的过渡只是为了暂停/恢复过渡动画,而不是为了交互?

几乎所有的iOS11都使用我认为是可中断的过渡,例如,允许您将控制中心拉起50%并与之交互,而无需释放控制中心窗格然后将其滑回。这正是我想做的。

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

我已经发布了示例代码和一个可重用的框架,用于演示可中断的视图控制器动画过渡。它被称为PullTransition,只需向下滑动即可轻松关闭或弹出视图控制器。如果文档需要改进,请告诉我。我希望这有帮助!

你去吧!可中断转换的简短示例。在 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
}
}
}

最新更新