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

时间:2017-08-01 16:53:12

标签: ios animation uiviewcontroller transitions

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

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

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

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

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

  1. 将toViewController.view设置为定位到 左/右(取决于所需的方向) fromViewController.view

  2. 在UIViewPropertyAnimator动画块中,我滑动了 toViewController.view进入视图,并将fromViewController.view输出 视线(屏幕外)。

  3. 现在,在过渡期间,我希望能够按下那个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?) - &gt; UIView?结果。 UIButton 被命中,但是,按钮操作未被称为

    有没有人得到这个工作?

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

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

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

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分: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
    }
  }
}