我当前有一个使用UICollectionViewCompositionalLayout
的UICollectionView
。我想在滚动/滚动停止时在当前可见单元格中设置一些视图的动画。
不幸的是,将节上的orthogonalScrollingBehavior
设置为除.none
之外的任何内容似乎劫持了UICollectionView
附带的UIScrollView
委托方法。
想知道目前是否有解决办法要获取分页行为和UIScrollView
委托吗
设置布局
enum Section {
case main
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.collectionViewLayout = createLayout()
collectionView.delegate = self
}
func configure() {
snapshot.appendSections([.main])
snapshot.appendItems(Array(0..<10))
dataSource.apply(snapshot, animatingDifferences: false)
}
private func createLayout() -> UICollectionViewLayout {
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
)
leadingItem.contentInsets = .zero
let containerGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
),
subitems: [leadingItem])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .groupPaging // WOULD LIKE PAGING & UISCROLLVIEW TO ALSO BE FIRED
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: config)
return layout
}
UICollectionViewDelegate
extension SlidingCardView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// THIS IS FIRED BUT UISCROLLVIEW METHODS NOT
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print(111)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("1111111")
}
}
将orthogonalScrollingBehavior
设置为节,嵌入一个内部_UICollectionViewOrthogonalScrollerEmbeddedScrollView
,用于处理节中的滚动。此内部滚动视图将作为子视图添加到集合视图中。
当您将自己设置为收藏视图的delegate
时,您应该会收到滚动视图委托回调,但仅限于主收藏视图,它在部分之间滚动,而不是在部分中的项目之间滚动。由于内部滚动视图(也可能是collectionViews,不确定(是完全不同的实例,并且您没有将自己设置为它们的委托,因此您不会收到它们的回调。
据我所知,不应该有一种官方的方式来接收来自内部滚动视图的这些回调,这些滚动视图处理分段滚动
但如果你很好奇,并且你想尝试一下,你可以使用这个"黑客入侵"的集合查看类:
import UIKit
final class OrtogonalScrollingCollectionView: UICollectionView {
override var delegate: UICollectionViewDelegate? {
get { super.delegate }
set {
super.delegate = newValue
subviews.forEach { (view) in
guard String(describing: type(of: view)) == "_UICollectionViewOrthogonalScrollerEmbeddedScrollView" else { return }
guard let scrollView = view as? UIScrollView else { return }
scrollView.delegate = newValue
}
}
}
}
这将把您的委托设置为所有带有正交部分的内部滚动视图。你不应该在生产环境中使用它,因为不能保证苹果会以相同的方式保持集合视图的内部工作,所以这种黑客攻击在未来可能不会奏效,而且当你提交发布版本时,你可能会因为在UIKit中使用私有API而被拒绝。
您可能只想使用NSCollectionLayoutSection
的visibleItemsInvalidationHandler
回调,它就像UIScrollViewDelegate
一样,每次部分滚动时都会调用它
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
section.visibleItemsInvalidationHandler = { (visibleItems, point, env) -> Void in
print(point)
}
这里有一个确定哪个单元格位于屏幕中心的解决方案:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
guard let self = self else { return }
for visibleCell in self.collectionView.visibleCells {
let collectionViewCenterPoint = self.collectionView.center
if let relativePoint = visibleCell.superview?.convert(collectionViewCenterPoint, from: nil),
visibleCell.frame.contains(relativePoint)
{
// visibleCell is in the center of the view.
} else {
// visibleCell is outside the center of the view.
}
}
}
根据@Stoyan的回答,我通过不寻找私有API来微调该类,使其与生产代码兼容。简单地看一下所有的UIScrollView
子类。
此外,我认为最好在集合重载期间更新委托,因为在设置委托时可能还没有完整的视图层次结构。
最后,该类现在递归地查找UIScrollView
,因此不会遗漏任何内容。
final class OrthogonalScrollingCollectionView: UICollectionView {
override func reloadData() {
super.reloadData()
scrollViews(in: self).forEach { scrollView in
scrollView.delegate = delegate
}
}
override func reloadSections(_ sections: IndexSet) {
super.reloadSections(sections)
scrollViews(in: self).forEach { scrollView in
scrollView.delegate = delegate
}
}
fileprivate func scrollViews(in subview: UIView) -> [UIScrollView] {
var scrollViews: [UIScrollView] = []
subview.subviews.forEach { view in
if let scrollView = view as? UIScrollView {
scrollViews.append(scrollView)
} else {
scrollViews.append(contentsOf: self.scrollViews(in: view))
}
}
return scrollViews
}
}