Compositional layout in iOS

Compositional layout是在2019年为UICollectioinView引入的一个新布局

Compositional layout是什么

  • Compositional layout是一套针对UICollectionView新的布局方法
  • 对应的核心类是UICollectionViewCompositionalLayout(macOS上是NSCollectionViewCompositionalLayout)
  • 其目的是让UICollectionView可以更容易地支持更灵活布局UI的开发

Compositional layout布局的三大设计哲学:

  1. Composable:可组合,强调用简单的组件组合出复杂的内容
  2. Flexible:灵活(官方说,You can write any layout with Compositional layout)
  3. Fast

几个例子感受一下Compositional layout能做什么

  1. 如下示意图展示了一个纵向滚动的UICollectionView,其中有上中下三部分,上部分看上去像两列UITableView,各部分的布局和样式各不相同
  1. 如下示意图展示了纵向滚动的UICollectionView,其中横向上有多个可以横向滚动的组(App Store应用大量使用该布局)
    • 看到这里我立马想到了:可能再也不用多个UICollectionView嵌套了

四个核心概念

Compositional layout由四个最核心的概念组成

Item > Group > Section > Layout

  • ItemLayout,表示的范围依次扩大
  • 任何一个Compositional layout都从左到右组合而成

如何使用Compositional layout

Compositional layout的核心类是UICollectionViewCompositionalLayout,其初始化方法有两类,如下代码所示:

  • 一类是直接提供section,另一类是通过provider动态的提供section
swift 复制代码
class UICollectionViewCompositionalLayout : UICollectionViewLayout {

    public init(section: NSCollectionLayoutSection)

    public init(section: NSCollectionLayoutSection, configuration: UICollectionViewCompositionalLayoutConfiguration)

    public init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider)

    public init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider, configuration: UICollectionViewCompositionalLayoutConfiguration)
}

创建UICollectionViewCompositionalLayout的过程就是上小节提到的

Item > Group > Section > Layout

less 复制代码
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                     heightDimension: .fractionalHeight(1.0))

let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalWidth(0.2))

let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
                                                 subitems: [item])

let section = NSCollectionLayoutSection(group: group)

let layout = UICollectionViewCompositionalLayout(section: section)

OrthogonalScrolling

再介绍一个官方Demo中提到的稍微复杂一点的Compositional layout案例

我们希望最终效果如下图所示:

首先进行设计:

  • 整体纵向滚动,横向上有多行,每一行也是可以滚动的,每一行我们可以看做是一个Section
  • 关注到每一行中的元素,每一行中基本的滚动单元是:左侧的一个大块+右侧两个小块,滚动单元可以认为是Group
  • 具体的大小块则可以认为是Item

关于"左侧的一个大块+右侧两个小块"的示意图如下所示:

以下是Compositional layout代码,我们对照注释看一下创建过程:

less 复制代码
// 1. 左侧大块Item的创建
// - 宽度:希望占容器(group)宽度的70%
// - 高度:希望和容器一样高
let leadingItem = NSCollectionLayoutItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
                                      heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

// 2. 右侧任意的一个小块
// - 宽度:和容器一样宽
// - 高度:占容器高度的一半
let trailingItem = NSCollectionLayoutItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalHeight(0.5)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

// 3. 创建一个容器group,这是一个纵向的容器,容纳右侧的两个小块Item。宽度占该group所在容器的30%;高度和容器一致
let trailingGroup = NSCollectionLayoutGroup.vertical(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
                                       heightDimension: .fractionalHeight(1.0)),
    repeatingSubitem: trailingItem,
    count: 2)
    
// 4. 创建一个横向容器group,容纳1个大块+2个小块。宽度占其容器的85%,高度占40¥
let containerGroup = NSCollectionLayoutGroup.horizontal(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85),
                                      heightDimension: .fractionalHeight(0.4)),
    subitems: [leadingItem, trailingGroup])
// 5. 创建section,包含最外层的横向容器group
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuous
// 6. 创建layout,使用默认configuration,默认是纵向滚动
return UICollectionViewCompositionalLayout(section: section)

再看一下数据源代码:

ini 复制代码
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var identifierOffset = 0
let itemsPerSection = 30
for section in 0..<5 {
    snapshot.appendSections([section])
    let maxIdentifier = identifierOffset + itemsPerSection
    snapshot.appendItems(Array(identifierOffset..<maxIdentifier))
    identifierOffset += itemsPerSection
}
  • 整体数据是一个二维数组,类似这样:[[0,1...29], [30...59], [...], [...], [...]]
  • 创建了5个Section,每个Section
  • 每个Section中有30个数字
  • 30个数字拆分成了10个Group,每个Group有三个Item。如果对应着数据源,则依次是0,1,2, 3,4,5.....
  • 每个数字表示一个Item

其他

UICollection​Layout​List​Configuration​.Appearance

orthogonalScrollingBehavior

orthogonal(发音:/ôrˈTHäɡən(ə)l/):正交。但并非数学上的概念,而是指,与指定方向是正交方向的另一个方向。说白了,如果制定的滚动方向是垂直,则orthogonalScrolling(正交滚动方向)就是水平方向

问题

1. 如何横向滚动

Demo中都是纵向滚动的,Compositional layout是否支持横向滚动?

当然,如下所示,不过要注意一下写法

  • 自定义UICollectionViewCompositionalLayoutConfiguration,设置scrollDirection即可

如果尝试修改因UICollectionViewCompositionalLayout(section: section)而自动创建的UICollectionViewCompositionalLayoutConfiguration.scrollDirection可能不起作用

ini 复制代码
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)

1. iOS 26.3中带count参数的Group初始化方法有bug

  • 通过horizontal(layout​Size:​repeating​Subitem:​count:)创建的group,无法做到按照count对item等分布局
  • 但通过horizontal(layout​Size:​subitems:)或者已经废弃的horizontal(layout​Size:​subitem:​count:)可以正确实现

按照如下代码中所示的:

less 复制代码
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 3)

2. 官方Demo-AdaptiveSections的bug

  • 官方Demo中,AdaptiveSections部分,会根据容器宽度决定一行显示的列数
  • 但在iOS 26.3中测试,旋转到横屏后,列数并没有按照预期增加
  • 未定位到原因

参考

相关推荐
CHB11 小时前
uni-app x 蒸汽模式 性能测试基准报告 Benchmark【Android版】
android·ios·uni-app
Magic-ZYJ13 小时前
不登录、不联网也能用:我是如何设计一款“本地优先”iOS App 的
ios·app·iphone·研发·独立开发者·心情日记
_阿南_14 小时前
flutter 展示的字体突然奔放了
android·flutter·ios
90后的晨仔19 小时前
你的 iOS 最低部署版本即将不再被 App Store 接受
ios
星期一研究室21 小时前
从“能看”到“爱看”:用色彩心理学打造人人爱看的的专业文档🧩
微服务·产品·设计
2601_965798471 天前
Launch Your Wellness App Fast: The Truth About Meditation Source Code
前端·macos·ios·objective-c·cocoa
初级代码游戏1 天前
iOS开发 Swift 速记7:结构体和类
开发语言·ios·swift
Anhty2 天前
手机端叮咚变声器2026实测:功能表现、优缺点完整解析
功能测试·ios·智能手机·安卓
SameX2 天前
后台定位耗电的问题,我花两年把它压到了3%-5%
ios
星期一研究室2 天前
飞书文档行文重点强调:五大招式,让你的信息“亮”起来!
微服务·产品·设计