将父容器的平移手势移交到嵌套的UICollectionView
我试图构建一个复杂的分离视图容器控制器,它有利于两个可变高度的容器,每个容器都有自己的嵌套视图控制器。父控制器上有一个全局平移手势,允许用户拖动视图容器中的任何位置,并上下滑动视图之间的“分隔线”。它还具有将扩大任一视图(或复位分隔位置)的一些智能位置阈值检测逻辑:将父容器的平移手势移交到嵌套的UICollectionView
这工作得很好。还有很多代码可以用来构建这个代码,我很乐意分享这些代码,但我不认为它是相关的,所以暂时忽略它。
现在我想通过增加集合视图的底部视图使事情变得复杂:
我已经能够解决它,这样我可以滚动拆分视图了一个决定性的平移手势,并通过快速轻弹手指来滚动集合视图(一种轻扫手势,我想它是?),但这是一种非常不平凡的体验:您无法平移视图并滚动集合同时观看,并期望用户持续复制类似但不同的手势以控制视图太难以进行交互。
为了尝试解决这个问题,我已经尝试了几种代表/协议解决方案中,我检测拆分视图除法器的位置和使能基于是否底部上的集合视图/禁用canCancelTouchesInView
和/或isUserInteractionEnable
视图完全展开。这个工程到一个点,但不是在以下两种情况下:
- 当拆分视图分频器是在其默认位置,如果用户锅高达船底视图完全展开,然后扫视保持,收集视图应该开始滚动,直到手势结束。
- 当拆分视图分隔符位于顶部(底部容器视图已完全展开)并且顶部的集合视图是而不是时,如果用户平移,集合视图应该滚动而不是移动拆分视图分隔符,直到收集视图到达顶部位置,此时分割视图应该返回到其默认位置。
这里是说明此行为的动画:
鉴于此,我开始思考解决问题的唯一方法是通过在拆分视图中创建一个委托方法它在底部视图处于最大高度时告诉集合视图,然后可以拦截父级的平移手势或将屏幕接触转发到集合视图?但是,我不知道该怎么做。如果我在解决方案的正确轨道上,那么我的问题就是:如何将平移手势转发或切换到集合视图,并使集合视图以与捕获触摸相同的方式进行交互首先呢?我可以用pointInside
或touches____
方法做些什么吗?
如果我不能这样做,我还能怎么解决这个问题?
更新赏金猎人:我有一些零散的运气创建的集合视图的委托方法,并调用它拆分视图容器上设置属性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
}
}
}
这工作,当你开始滚动就OK了,但一旦滚动的集合视图能不能很好地执行,因为滚动手势几乎总是覆盖平移手势。我想知道我是否可以用gestureRecognizer:shouldRecognizeSimultaneouslyWith:
连线,但我还没有。
如何使底视图的子视图实际占用整个屏幕并将集合视图的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?()
}
}
听起来像一个有前途的想法...如果你在代码中刺伤它,它可能是值得的赏金 – brandonscript
由于手势识别器保持相同的对象且其手势识别器保持不变,因此手势识别器无法“手动”切换手势。
但是,没有什么能阻止你告诉其他观点对手势做出什么响应。集合视图是一个滚动视图,因此您知道它如何在每个时刻滚动,并且可以并行执行其他操作。
这是我以前的尝试之一 - 检测手势并使用集合视图的(滚动视图的)内容偏移来处理它。然而,在这样做的时候,我牺牲了收集视图“反弹”的能力,交互感觉非常没有生气。 – brandonscript
您应该能够使用UICollectionViewDelegateFlowLayout
以单个集合视图实现您的目标。如果您需要针对顶部视图的任何特殊滚动行为(例如视差),则仍然可以通过实现从UICollectionViewLayout
继承的自定义布局对象在单个集合视图中实现该行为。
使用UICollectionViewDelegateFlowLayout
方法稍微比实现自定义布局更加简单易懂,所以如果你想给一个镜头,请尝试以下操作:
创建顶视图的
UICollectionViewCell
一个子类,将其注册到您的收藏视图。创建您的“分水岭”的观点作为
UICollectionViewCell
一个子类,并与您的收藏视图注册为使用func register(_ viewClass: AnyClass?, forSupplementaryViewOfKind elementKind: String, withReuseIdentifier identifier: String)
让你的集合视图控制器补充视图符合
UICollectionViewDelegateFlowLayout
,创建布局对象作为一个实例UICollectionViewFlowLayout
将您的集合视图控制器指定为您的流布局实例的委托,并使用流布局初始化您的集合视图。执行
optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
在collecton视图控制器中返回所需的每个不同视图的大小。
在实践中,这听起来像它会工作得很好,但在我的情况下,我使用视图控制器容器的拆分控制器 - 顶部和底部都是独立的视图控制器。我并不完全有信心,我可以连线这样的流程图并使用容器... – brandonscript
好的,在这种情况下,我会在容器控制器视图中添加一个平移手势识别器。然后在其处理程序中执行以下两项操作之一:1)如果平移手势的状态为“.changed”,则根据容器视图中的翻译更新每个子视图控制器视图的框架。 2)如果平移手势的状态为'.ended',则根据平移方向以及您可能决定设置的最小平移阈值来显示或隐藏您的收藏视图。 – nicksweet
你能分享你的代码来测试这种行为吗?在你提到的两种情况中,似乎collectionView应该消耗触摸事件,直到它的水平偏移达到它看起来相当简单的边界。 – Lukas
@Lukas我可以,但我不确定它是否真的能帮助解决问题 - 这是因为很多内置IB的IB约束,并且因为您已经知道我有检测代理的方法可以完全按照您的方式已经描述过:“当水平偏移达到其上边界时,collectionView应该消耗触摸事件。”不过,如果你想要一些代码,我可以添加一些''__ _(ツ)_ /' – brandonscript