ios - 将父容器的平移手势传递给嵌套的 UICollectionView

标签 ios iphone swift uiscrollview uipangesturerecognizer

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




这工作正常。还有很多代码来构建这个,我很乐意分享,但我认为它不相关,所以我暂时省略它。

我现在试图通过在底部 View 中添加一个集合 View 来使事情复杂化:



我已经能够解决这个问题,以便我可以使用决定性的平移手势向上滚动拆分 View ,并通过快速滑动手指(滑动手势,我想是?)来滚动集合 View ,但是这个是一种非常差的体验:您不能同时平移 View 和滚动集合 View ,并且期望用户始终如一地复制相似但不同的手势以控制 View 对于交互来说太困难了。

为了尝试解决这个问题,我尝试了几种委托(delegate)/协议(protocol)解决方案,在这些解决方案中我检测分割 View 中分隔符的位置并启用/禁用 canCancelTouchesInView和/或 isUserInteractionEnable在基于底部 View 是否完全展开的集合 View 上。这在一定程度上有效,但不适用于以下两种情况:

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

  • 这是说明此行为的动画:

    enter image description here

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

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

    赏金猎人更新:我在集合 View 上创建了一个委托(delegate)方法,并在拆分 View 容器上调用它以设置属性 shouldScroll ,通过它我使用一些平移方向和定位信息来确定 ScrollView 是否应该滚动。然后我在 UIGestureRecognizerDelegate 中返回这个值的 gestureRecognizer:shouldReceive touch:委托(delegate)方法:
    // 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
            }
        }
    }
    

    当您开始滚动时,这可以正常工作,但一旦在集合 View 上启用滚动,则效果不佳,因为滚动手势几乎总是覆盖平移手势。我想知道我是否可以与 gestureRecognizer:shouldRecognizeSimultaneouslyWith: 联系起来,但我还没到。

    最佳答案

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

    enter image description here

    更新:试试这个!!

    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?()
        }
    }
    

    关于ios - 将父容器的平移手势传递给嵌套的 UICollectionView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46246763/

    相关文章:

    ios - 如何从 Itunes 获取应用评论

    ios - 在启用 iCloud 的情况下传输 iOS 应用程序

    iphone - 为什么我的委托(delegate)方法没有被调用?

    带有 Swift 枚举的 Xcode 6 运行时属性

    ios - 禁用carbonKit半透明效果

    ios - 为什么 Xcode 无法识别我创建的类?

    ios - 没有平移手势的 UIScrollView 水平滚动

    ios - 良好的后端方式来存储iOS应用的歌曲(音频)样本?

    iphone - 如何在 iphone 应用程序中用饼图显示百分比

    ios - 如何让页脚 View 始终位于 UITableViewController 的底部?