将父容器的平移手势移交给嵌套的UICollectionView [英] Hand off parent container's pan gesture to nested UICollectionView

查看:135
本文介绍了将父容器的平移手势移交给嵌套的UICollectionView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个复杂的拆分视图容器控制器,它有助于两个可变高度容器,每个容器都有自己的嵌套视图控制器。父控制器上有一个全局平移手势,允许用户拖动视图容器中的任何位置,并在视图之间向上和向下滑动分隔符。它还有一些智能位置阈值检测逻辑,可以扩展任一视图(或重置分频器位置):

I'm trying to build a complex split view container controller that facilitates two variable height containers, each with their own nested view controller. There's a global pan gesture on the parent controller that allows the user to drag anywhere in the view container and slide the "divider" between views up and down. It also has some intelligent position threshold detection logic that will expand either view (or reset the divider position):

     

     

这很好用。还有很多代码可以构建它,我很乐意与大家分享,但我认为它不相关,所以暂时我会省略它。

This works fine. There's also a lot of code to construct this, which I'm happy to share, but I don't think it's relevant, so I'll omit it for the time being.

我现在试图通过在底部视图中添加集合视图来使事情变得复杂:

I'm now trying to complicate things by adding a collection view to the bottom view:

我已经能够解决这个问题,这样我就可以用一个决定性的平底锅滚动拆分视图手势,并快速轻弹手指滚动集合视图(滑动手势,我想它是?),但这是一个非常低级别的体验:你无法平移视图并滚动集合视图同时,并期望用户一致地复制相似但不同的手势以控制视图对于交互来说太难了。

I've been able to work it out so that I can scroll the split view up with a decisive pan gesture, and scroll the collection view with a quick flick of the finger (a swipe gesture, I suppose it is?), but this is a really sub-par experience: you can't pan the view and scroll the collection view at the same time, and expecting a user to consistently replicate similar, yet different gestures in order to control the view is too difficult of an interaction.

为了尝试解决这个问题,我我已经尝试了几个委托/协议解决方案,我在分裂视图中检测分隔符的位置并启用/禁用 canCanc根据底视图是否完全展开,集合视图上的elTouchesInView 和/或 isUserInteractionEnable 。这有点适用,但不是在以下两种情况下:

To attempt to solve this, I've tried several delegate/protocol solutions in which I detect the position of the divider in the split view and enable/disable canCancelTouchesInView and/or isUserInteractionEnable on the collection view based on whether the bottom view is fully expanded. This works to a point, but not in the following two scenarios:


  1. 当拆分视图分隔符处于其默认位置时,如果用户平移到底部视图完全展开的位置,然后继续向上平移,集合视图应该开始滚动直到手势结束。

  2. 当拆分视图分隔符位于顶部时(底部)容器视图已完全展开)并且集合视图在顶部,如果用户平移,则集合视图应滚动而不是拆分视图分隔符移动,直到集合视图到达其顶部位置,此时拆分视图应返回其默认位置。

  1. When the split view divider is in its default position, if the user pans up to where the bottom view is fully expanded, then keeps on panning up, the collection view should begin scrolling until the gesture ends.
  2. When the split view divider is at the top (bottom container view is fully expanded) and the collection view is not at the top, if the user pans down, the collection view should scroll instead of the split view divider moving, until the collection view reaches its top position, at which point the split view should return to its default position.

这是一个说明此行为的动画:

Here is an animation that illustrates this behavior:

鉴于此,我开始认为解决问题的唯一方法是在分割视图上创建一个委托方法,告诉集合视图何时底部视图处于最大高度,然后可以拦截父级的平移手势或将屏幕触摸转发到集合视图?但是,我不知道该怎么做。如果我使用解决方案走在正确的轨道上,那么我的问题很简单:如何将平移手势转发或移交到集合视图,并使集合视图以与触摸时相同的方式进行交互它首先被它捕获了吗?我可以用 pointInside 触及____ 方法吗?

Given this, I'm starting to think the only way to solve the problem is by creating a delegate method on the split view that tells the collection view when the bottom view is at maximum height, which then can intercept the parent's pan gesture or forward the screen touches to the collection view instead? But, I'm not sure how to do that. If I'm on the right track with a solution, then my question is simply: How can I forward or hand off a pan gesture to a collection view and have the collection view interact the same way it would if the touches had been captured by it in the first place? can I do something with pointInside or touches____ methods?

如果我不能这样做,我还能怎样解决这个问题?

If I can't do it this way, how else can I solve this problem?

赏金猎人的更新:我有一些零碎的运气在集合视图上创建委托方法,并在拆分视图容器上调用它来设置属性 shouldScroll ,通过它我可以使用一些平移方向和定位信息来确定滚动视图是否应该滚动。然后我在 UIGestureRecognizerDelegate gestureRecognizer中返回此值:shouldReceive touch:委托方法:

Update for bounty hunters: I've had some fragmented luck creating a delegate method on the collection view, and calling it on the split view container to set a property shouldScroll, by which I use some pan direction and positioning information to determine whether or not the scroll view should scroll. I then return this value in UIGestureRecognizerDelegate's gestureRecognizer:shouldReceive touch: delegate method:

// protocol delegate
protocol GalleryCollectionViewDelegate {
    var shouldScroll: Bool? { get }
}

// shouldScroll property
private var _shouldScroll: Bool? = nil
var shouldScroll: Bool {
    get {
        // Will attempt to retrieve delegate value, or self set value, or return false
        return self.galleryDelegate?.shouldScroll ?? self._shouldScroll ?? false
    }
    set {
        self._shouldScroll = newValue
    }
}

// UIGestureRecognizerDelegate method
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return shouldScroll
}

// ----------------
// Delegate property/getter called on the split view controller and the logic:
var shouldScroll: Bool? {
    get {
        return panTarget != self
    }
}

var panTarget: UIViewController! {
    get {
        // Use intelligent position detection to determine whether the pan should be
        // captured by the containing splitview or the gallery's collectionview
        switch (viewState.currentPosition,
                viewState.pan?.directionTravelled,
                galleryScene.galleryCollectionView.isScrolled) {
        case (.top, .up?, _), (.top, .down?, true): return galleryScene
        default: return self
        }
    }
}

这在开始滚动时可以正常工作,但在集合视图上启用滚动后效果不佳,因为滚动手势几乎总是覆盖平移手势。我想知道我是否可以用 gestureRecognizer连接:shouldRecognizeSimultaneouslyWith:,但我还没有。

This works OK for when you begin scrolling, but doesn't perform well once scrolling is enabled on the collection view, because the scroll gesture almost always overrides the pan gesture. I'm wondering if I can wire something up with gestureRecognizer:shouldRecognizeSimultaneouslyWith:, but I'm not there yet.

推荐答案

如何使底视图的子视图实际占用整个屏幕并将集合视图的contentInset.top设置为顶视图高度。然后在底部视图上方添加其他子视图控制器。然后,您唯一需要做的就是让父视图控制器成为委托,以监听底部视图的集合视图的滚动偏移并更改顶视图的位置。没有复杂的手势识别器的东西。只有一个滚动视图(集合视图)

What about making the child view for bottom view actually takes up the entire screen and set the collection view's contentInset.top to top view height. And then add the other child view controller above the bottom view. Then the only thing you need to do is make the parent view controller the delegate to listen to the bottom view's collection view's scroll offset and change the top view's position. No complicated gesture recognizer stuff. Only one scroll view(collection view)

更新:试试这个!

import Foundation
import UIKit

let topViewHeight: CGFloat = 250

class SplitViewController: UIViewController, BottomViewControllerScrollDelegate {

    let topViewController: TopViewController = TopViewController()
    let bottomViewController: BottomViewController = BottomViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        automaticallyAdjustsScrollViewInsets = false

        bottomViewController.delegate = self
        addViewController(bottomViewController, frame: view.bounds, completion: nil)
        addViewController(topViewController, frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: topViewHeight), completion: nil)
    }

    func bottomViewScrollViewDidScroll(_ scrollView: UIScrollView) {
        print("\(scrollView.contentOffset.y)")

        let offset = (scrollView.contentOffset.y + topViewHeight)
        if offset < 0 {
            topViewController.view.frame.origin.y = 0
            topViewController.view.frame.size.height = topViewHeight - offset
        } else {
            topViewController.view.frame.origin.y = -(scrollView.contentOffset.y + topViewHeight)
            topViewController.view.frame.size.height = topViewHeight
        }
    }
}

class TopViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        automaticallyAdjustsScrollViewInsets = false
        view.backgroundColor = UIColor.red

        label.text = "Top View"
        view.addSubview(label)
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        label.sizeToFit()
        label.center = view.center
    }
}

protocol BottomViewControllerScrollDelegate: class {
    func bottomViewScrollViewDidScroll(_ scrollView: UIScrollView)
}

class BottomViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    var collectionView: UICollectionView!

    weak var delegate: BottomViewControllerScrollDelegate?

    let cellPadding: CGFloat = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.yellow
        automaticallyAdjustsScrollViewInsets = false

        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = cellPadding
        layout.minimumLineSpacing = cellPadding
        layout.scrollDirection = .vertical
        layout.sectionInset = UIEdgeInsets(top: cellPadding, left: 0, bottom: cellPadding, right: 0)

        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.contentInset.top = topViewHeight
        collectionView.scrollIndicatorInsets.top = topViewHeight
        collectionView.alwaysBounceVertical = true
        collectionView.backgroundColor = .clear
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: String(describing: UICollectionViewCell.self))
        view.addSubview(collectionView)
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 30
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: UICollectionViewCell.self), for: indexPath)
        cell.backgroundColor = UIColor.darkGray
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = floor((collectionView.frame.size.width - 2 * cellPadding) / 3)
        return CGSize(width: width, height: width)
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        delegate?.bottomViewScrollViewDidScroll(scrollView)
    }
}

extension UIViewController {

    func addViewController(_ viewController: UIViewController, frame: CGRect, completion: (()-> Void)?) {
        viewController.willMove(toParentViewController: self)
        viewController.beginAppearanceTransition(true, animated: false)
        addChildViewController(viewController)
        viewController.view.frame = frame
        viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(viewController.view)
        viewController.didMove(toParentViewController: self)
        viewController.endAppearanceTransition()
        completion?()
    }
}

这篇关于将父容器的平移手势移交给嵌套的UICollectionView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆