如何在iOS上创建可操作的可中断视图控制器转换?

问题描述 投票:3回答:2

iOS 10为自定义动画视图控制器转换添加了一个名为interruptibleAnimator(using:)的新功能

很多人似乎都在使用新功能,但是只需在interruptableAnimator(使用:)中的UIViewPropertyAnimator的动画块中实现旧的animateTransition(使用:)(请参阅Session 216 from 2016

但是,我找不到一个实际使用可中断动画师来创建可中断转换的例子。每个人似乎都支持它,但没有人真正使用它。

例如,我使用UIPanGestureRecognizer在两个UIViewControllers之间创建了一个自定义转换。两个视图控制器都有一个backgroundColor设置,中间的UIButton改变了touchUpInside上的backgroundColour。

现在我将动画简单地实现为:

  1. 设置toViewController.view以定位到fromViewController.view的左/右(取决于所需的方向)
  2. 在UIViewPropertyAnimator动画块中,我将toViewController.view滑入视图,将fromViewController.view滑出视图(关闭屏幕)。

现在,在过渡期间,我希望能够按下那个UIButton。但是,没有调用按钮按下。奇怪的是,这就是会话暗示事情应该如何工作的原因,我将自定义UIView设置为我的两个UIViewControllers的视图,如下所示:

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,事件:UIEvent?) - > UIView?结果。 UIButton正在被测试,但是,不会调用按钮操作。

有没有人得到这个工作?

我是否正在考虑这个错误并且是可中断的过渡只是为了暂停/恢复过渡动画,而不是用于交互?

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

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

ios animation uiviewcontroller transitions
2个回答
2
投票

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


0
投票

干得好!可中断转换的简短示例。在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
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.