将父容器的平移手势移交给嵌套的UICollectionView

bra*_*ipt 5 iphone uiscrollview ios uipangesturerecognizer swift

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

     

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

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

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

为了尝试解决这个问题,我尝试了几种委托/协议解决方案,其中我在拆分视图中检测分隔符的位置,canCancelTouchesInViewisUserInteractionEnable根据底部视图是否完全展开来启用/禁用和/或在集合视图上.这在某种程度上有效,但在以下两种情况下则不然:

  1. 当拆分视图分隔符处于其默认位置时,如果用户平移到底部视图完全展开的位置,然后继续向上平移,则集合视图应开始滚动直到手势结束.
  2. 当拆分视图分隔符位于顶部(底部容器视图完全展开)且集合视图不在顶部时,如果用户平移,则集合视图应滚动而不是拆分视图分隔符移动,直到集合视图到达其顶部位置,此时拆分视图应返回其默认位置.

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

在此输入图像描述

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

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


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

// 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

HMH*_*ero 7

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

在此处输入图片说明

更新:试试这个!!

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?()
    }
}
Run Code Online (Sandbox Code Playgroud)