使用(自定义、交互式)视图控制器呈现和解除处理

Handling scroll views with (custom, interactive) view controller presentation and dismissal(使用(自定义、交互式)视图控制器呈现和解除处理滚动视图)

本文介绍了使用(自定义、交互式)视图控制器呈现和解除处理滚动视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试自定义交互式视图控制器的展示和解除(使用 UIPresentationControllerUIPercentDrivenInteractiveTransitionUIViewControllerAnimatedTransitioning 的组合>UIViewControllerTransitioningDelegate) 并且大多数情况下都可以很好地满足我的需求.

但是,在我阅读的任何教程或文档中,我还没有找到一种常见的情况,这导致我提出以下问题:

...

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

...

基本上,我想要的是以下内容:

  1. 视图控制器可以通过向下平移以交互方式关闭.这是许多应用程序中常见的用户体验.

  2. 如果关闭的视图控制器包含(垂直滚动)滚动视图,则向下平移会按预期滚动该视图,直到用户到达顶部,然后滚动停止并发生平移关闭.

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

我知道这在技术上是可能的 - 我在其他应用程序中看到过它,例如 Overcast 和 Apple 自己的音乐应用程序 - 但我无法找到协调我的平移手势的行为与滚动视图的行为.

我自己的大部分尝试都集中在尝试在滚动时根据其 contentOffset.y 有条件地启用/禁用滚动视图(或其关联的平移手势识别器)并让视图控制器解除的平移手势识别器从那里接管,但这充满了问题,我担心我想多了.

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

funcgestureRecognizer(_gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) ->布尔 {//...}

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

I have been experimenting with custom interactive view controller presentation and dismissal (using a combination of UIPresentationController, UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning, and UIViewControllerTransitioningDelegate) and have mostly gotten things working well for my needs.

However, there is one common scenario that I've yet to find addressed in any of the tutorials or documentation that I've read, leading me to the following question:

...

What is the proper way of handling custom interactive view controller dismissal, via a pan gesture, when the dismissed view contains a UIScrollView (ie. UITableView, UICollectionView, WKWebView, etc)?

...

Basically, what I'd like is for the following:

  1. View controllers are interactively dismissible by panning them down. This is common UX in many apps.

  2. If the dismissed view controller contains a (vertically-scrolling) scroll view, panning down scrolls that view as expected until the user reaches the top, after which the scrolling ceases and the pan-to-dismiss occurs.

  3. Scroll views should otherwise behave as normal.

I know that this is technically possible - I've seen it in other apps, such as Overcast and Apple's own Music app - but I've not been able to find the key to coordinating the behavior of my pan gesture with that of the scroll view(s).

Most of my own attempts center on trying to conditionally enable/disable the scrollview (or its associated pan gesture recognizer) based on its contentOffset.y while scrolling and having the view controller dismissal's pan gesture recognizer take over from there, but this has been fraught with problems and I fear that I am overthinking it.

I feel like the secret mostly lies in the following pan gesture recognizer delegate method:

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

I have created a reduced sample project which should demonstrate the scenario more clearly. Any code suggestions are highly welcome!

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

解决方案

Solution

  • Make scrollView stop scrolling after it reached top by using UIScrollView's bounces property and scrollViewDidScroll(_:) method.

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

    Don't forget to set scrollView.delegate = self

  • Only handle panGestureRecognizer when scrollView reached top - It means when scrollView.contentOffset.y == 0 by using a protocol.

    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;
        }
      }
    }
    

Result

For more detail, you can take a look at my sample project

这篇关于使用(自定义、交互式)视图控制器呈现和解除处理滚动视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:使用(自定义、交互式)视图控制器呈现和解除处理

基础教程推荐