使用(自定义,交互式)视图控制器显示和解除处理滚动视图

时间:2018-04-24 22:41:40

标签: ios swift uiviewcontroller uiscrollview uipangesturerecognizer

我一直在尝试使用自定义交互式视图控制器演示和解雇(使用UIPresentationControllerUIPercentDrivenInteractiveTransitionUIViewControllerAnimatedTransitioningUIViewControllerTransitioningDelegate的组合,并且主要得到的东西为我的需要而努力。

但是,有一种常见的情况我还没有在我阅读的任何教程或文档中找到解决方案,这引出了以下问题:

...

当被解雇的视图包含UIScrollView(即UITableView,UICollectionView,WKWebView等)时,通过平移手势处理自定义交互式视图控制器解除的正确方法是什么?

...

基本上,我喜欢以下内容:

  1. 通过向下平移来查看控制器是交互式允许的。这是许多应用程序中常见的用户体验。

  2. 如果被解除的视图控制器包含(垂直滚动)滚动视图,则向下平移按预期滚动该视图,直到用户到达顶部,之后滚动停止并发生泛消除。 / p>

  3. 滚动视图应该正常运行。

  4. 我知道这在技术上是可能的 - 我已经在其他应用中看到过它,例如Overcast和Apple自己的音乐应用 - 但我还没有能够找到协调我的平移手势的行为与滚动视图的行为的关键。

    我自己的大多数尝试都集中于尝试在滚动时根据其contentOffset.y有条件地启用/禁用滚动视图(或其关联的平移手势识别器),并让视图控制器解除平移手势识别器从那里开始,但这已经充满了问题,我担心我会过度思考它。

    我觉得这个秘密主要在于以下平移手势识别器委托方法:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
       // ...
    }
    

    我创建了一个简化的示例项目,该项目应该更清楚地演示场景。任何代码建议都非常受欢迎!

    https://github.com/Darchmare/SlidePanel-iOS

2 个答案:

答案 0 :(得分:16)

解决方案

  • 使用UIScrollView's bounces propertyscrollViewDidScroll(_:)方法使scrollView在到达顶部后停止滚动。

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        scrollView.bounces = (scrollView.contentOffset.y > 10);
    }
    

    不要忘记设置scrollView.delegate = self

  • 仅在panGestureRecognizer到达顶部时处理scrollView - 这意味着scrollView.contentOffset.y == 0何时使用协议。

    protocol PanelAnimationControllerDelegate {
        func shouldHandlePanelInteractionGesture() -> Bool
    }
    

    的ViewController

    func shouldHandlePanelInteractionGesture() -> Bool {
        return (scrollView.contentOffset.y == 0);
    }
    

    PanelInteractionController

    class PanelInteractionController: ... {
    
      var startY:CGFloat = 0
    
      private weak var viewController: (UIViewController & PanelAnimationControllerDelegate)?
    
      @objc func handlePanGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer) {
        switch gestureRecognizer.state {
        case .began:
          break
        case .changed:
          let translation    = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
          let velocity    = gestureRecognizer.velocity(in: gestureRecognizer.view!.superview)
          let state      = gestureRecognizer.state
    
          // Don't do anything when |scrollView| is scrolling
          if !(viewController?.shouldHandlePanelInteractionGesture())! && percentComplete == 0 {
            return;
          }
    
          var rawProgress    = CGFloat(0.0)
    
          rawProgress    = ((translation.y - startTransitionY) / gestureRecognizer.view!.bounds.size.height)
    
          let progress    = CGFloat(fminf(fmaxf(Float(rawProgress), 0.0), 1.0))
    
          if abs(velocity.x) > abs(velocity.y) && state == .began {
            // If the user attempts a pan and it looks like it's going to be mostly horizontal, bail - we don't want it... - JAC
            return
          }
    
          if !self.interactionInProgress {
            // Start to pan |viewController| down
            self.interactionInProgress = true
            startTransitionY = translation.y;
            self.viewController?.dismiss(animated: true, completion: nil)
          } else {
            // If the user gets to a certain point within the dismissal and releases the panel, allow the dismissal to complete... - JAC
            self.shouldCompleteTransition = progress > 0.2
    
            update(progress)
          }
        case .cancelled:
          self.interactionInProgress = false
          startTransitionY = 0
    
          cancel()
        case .ended:
          self.interactionInProgress = false
          startTransitionY = 0
    
          if self.shouldCompleteTransition == false {
            cancel()
          } else {
            finish()
          }
        case .failed:
          self.interactionInProgress = false
          startTransitionY = 0
    
          cancel()
        default:
          break;
        }
      }
    }
    

结果

enter link description here

有关详细信息,请查看my sample project

答案 1 :(得分:1)

对我来说,这一小段代码回答了我的很多问题并极大地帮助了我在滚动视图中的自定义转换,它会在尝试启动转换或在顶部显示活动指示时保持负向滚动视图偏移。我的猜测是,这将解决至少一些过渡/动画打嗝:

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    if scrollView.contentOffset.y < -75 {

        scrollView.contentInset.top = -scrollView.contentOffset.y

    }
    // Do animation or transition
}