NSCollectionLayoutSection 注意事项之 orthogonalScrollingBehavior
本篇文章记录使用 NSCollectionLayoutSection
类的 orthogonalScrollingBehavior
属性的过程中遇到的问题。
通过设置该属性可以控制对应 Section 的滑动效果。
在阅读以下内容时,我将默认您已经掌握了
UICollectionViewCompositionalLayout
的基础用法,不再对一些细节进行补充说明。
Orthogonal Scroll View
这一节我们会涉及到两个系统的私有类型:_UICollectionViewOrthogonalScrollView
和 _UICollectionViewOrthogonalScrollerEmbeddedScrollView
因为这两个类型的名称太长,同时会多次重复提及,故下文使用
_UIOrthogonalScrollView
代替。
这两个类型可以看作是一个,早期 Apple 使用的是 _UICollectionViewOrthogonalScrollerEmbeddedScrollView
这个名称,后期在某个版本中改为 _UICollectionViewOrthogonalScrollView
。
orthogonalScrollingBehavior
的默认值是 .none
,当我们将其设置为其他值后,系统就会在 UICollectionView
上添加一层类型为 _UIOrthogonalScrollView
的 UIScrollView
子类作为 Cell 的父视图,如下图所示:
此时 Cell 的父视图不再是 UICollectionVIew
。
我们测试过以下情况:
UICollectionViewCompositionalLayout
只有一种布局。NSCollectionLayoutGroup
的滑动方向和UICollectionView
的滑动方向一致或不一致。NSCollectionLayoutSection
的尺寸和UICollectionView
相等或不相等。
在这三种情况下,只要修改了 orthogonalScrollingBehavior
属性,就会添加 _UIOrthogonalScrollView
视图。
在正式展开说明之前插一句:本文后面所提及的内容,都是在下面这个布局的基础上进行修改的:
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .absolute(50))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: .init(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5)),
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
获取时机
那么该如何获取该视图呢,经过不完全测试,列表不包含任何 Cell 时,不会添加该视图。
该视图可在 viewDidLayoutSubviews
方法中通过遍历子视图获得。
/// `orthogonalScrollView` 可能的类型
///
/// 目前尚无法确定哪个版本的系统使用了哪个类型,所以为了稳妥起见,使用数组进行判断
private var orthogonalScrollViewTypes: [String] {
[
"_UICollectionViewOrthogonalScrollView",
"_UICollectionViewOrthogonalScrollerEmbeddedScrollView"
]
}
var orthogonalScrollView: UIScrollView? {
subviews.first { orthogonalScrollViewTypes.contains("\(type(of: $0))") } as? UIScrollView
}
clipsToBounds
_UIOrthogonalScrollView
的 clipsToBounds
属性默认是 false
,这会导致一个问题:
如果您的 Section 设置了 contentInsets
之类的属性,导致比 UICollectionView
小,那么在滑动 _UIOrthogonalScrollView
的时候,其内容会超出 Section 的范围。
iOS 15 及以上
_UIOrthogonalScrollView
是严格跟着 Section 范围走的,例如下图:
蓝色选中的范围是 _UIOrthogonalScrollView
,可以看到滑动的时候 Cell 明显超出了它的范围。
上文有提到获取 _UIOrthogonalScrollView
的时机,您可以在获取后通过手动将 clipsToBounds
设置为 ture
来解决该问题。
iOS 15 以下
经不完全测试,iOS 14.7.1 及以下版本符合该小节内容。iOS 14.7.1 - iOS 15 之间的版本没有经过测试,不确定符合上一小节还是该小节。
_UIOrthogonalScrollView
的范围是跟着 UICollectionView
走的,由下图所示:
和上一张图对比之后,可以很明显的发现区别(代码相同)。值得一提的是此时 _UIOrthogonalScrollView
的 contentInset
是 .zero
此时设置 clipsToBounds
就没有用了。
跟随滑动
如果您准备用 UICollectionViewCompositionalLayout
实现类似如上图的效果,那么您需要确保滑块是添加到 _UIOrthogonalScrollView
上。
考虑到该视图的获取时机,我建议您还是改用 UICollectionViewFlowLayout
实现。
.continuous
原本以为配合 UICollectionLayoutSectionOrthogonalScrollingBehavior.continuous
,最终可以达到和 UICollectionViewCompositionalLayout
一样的效果,但是事实是并不会。
本文使用的例子里包含一个横滑的 group,item 的宽度是 group 的 1/3,group 的宽度和 UICollectionView 宽度一致。但是如果把 item 的宽度改为某个固定的值,例如 50
,那么就会发现:屏幕上只会显示完整的 Cell。如下图所示:
右侧剩余的宽度不够第四个 Cell 显示出来,那么它就会隐藏掉,在后面再显示:
如果不修改 orthogonalScrollingBehavior
(即值为 .none
),则不会有该问题,但是 Cell 在超出屏幕后会换行展示,因为没有 _UIOrthogonalScrollView
...
所以建议这种需求还是使用 UICollectionViewFlowLayout
组合实现。
或者您知道什么更好的方式,请在评论区留言告诉我,谢谢。