UICollectionViewCompositionalLayout 常見排版範例
iOS 13 推出了 UICollectionViewCompositionalLayout,幫助我們更方便排版畫面,以下連結說明它的相關排版原理。
接下來我們將以推理女王克莉絲蒂的作品為例,以 UICollectionViewCompositionalLayout 實現各種 iOS App 的常見排版。
- 格子狀照片牆(grid)
- 水平捲動
- 水平捲動 & 分頁
- 水平捲動,分頁 & 顯示下一個的部分內容
- 水平捲動,不分頁,滑動停止時剛好停在 group 的起點
- 水平捲動,分頁 & 顯示上一個 / 下一個的部分內容
- 整個頁面垂直捲動,頁面裡有水平捲動的區塊
- 水平捲動的分頁,分頁的內容垂直捲動
- 表格(table)
- 畫面不能捲動的九宮格
格子狀照片牆(grid)
範例 1: item & group 的高度傳入 estimated,由 auto layout 自動計算高度
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
group.interItemSpacing = .fixed(space)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
return UICollectionViewCompositionalLayout(section: section)
}
image view 比例設為 0.7,priority 要設成 999,否則會有 auto layout breaking constraint 的警告訊息。
範例 2: itemSize 設為 fractionalWidth(0.5) & fractionalWidth(0.5 / 0.7),實現寬度是高度的 0.7 倍
func generateLayout() -> UICollectionViewLayout {
let space: Double = 5
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalWidth(0.5 / 0.7))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
return UICollectionViewCompositionalLayout(section: section)
}
水平捲動
方法 1: UICollectionViewCompositionalLayoutConfiguration 的 scrollDirection 設成 horizontal
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(200), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
return UICollectionViewCompositionalLayout(section: section, configuration: configuration)
}
方法 2: 設定 orthogonalScrollingBehavior
orthogonalScrollingBehavior 設為 continuous,group 的寬度固定 200,group 的高度等於 collection view 的高度。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。(因為 collection view 採用 UICollectionViewCompositionalLayout 時,預設的捲動方向是 vertical)
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(200), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
section.orthogonalScrollingBehavior = .continuous
return UICollectionViewCompositionalLayout(section: section)
}
水平捲動 & 分頁
範例 1:書本之間無間距
- 方法 1: UICollectionViewCompositionalLayoutConfiguration 的 scrollDirection 設成 horizontal, collectionView 的 isPagingEnabled 設成 true
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
return UICollectionViewCompositionalLayout(section: section, configuration: configuration)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
- 方法 2: orthogonalScrollingBehavior 設為 paging
orthogonalScrollingBehavior 設為 paging。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
return UICollectionViewCompositionalLayout(section: section)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
範例 2: 全螢幕照片,滑動時可看到間距,滑動停止時不會看到間距
orthogonalScrollingBehavior 設為 groupPaging,利用 section 的. interGroupSpacing 設定間距。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
section.orthogonalScrollingBehavior = .groupPaging
return UICollectionViewCompositionalLayout(section: section)
}
image view 的 content mode 設為 aspect fit。
範例 3: 圖文頁面
- 方法 1: UICollectionViewCompositionalLayoutConfiguration 的 scrollDirection 設成 horizontal, collectionView 的 isPagingEnabled 設成 true
func generateLayout() -> UICollectionViewLayout {
let inset: Double = 50
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
return UICollectionViewCompositionalLayout(section: section, configuration: configuration)
}
書名 label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
- 方法 2: orthogonalScrollingBehavior 設為 paging
orthogonalScrollingBehavior 設為 paging,利用 item 的 contentInsets 設定內容和邊界的間距。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let inset: Double = 50
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
return UICollectionViewCompositionalLayout(section: section)
}
書名 label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
水平捲動,分頁 & 顯示下一個的部分內容
orthogonalScrollingBehavior 設為 groupPaging,group 的寬度設為 fractionalWidth(0.9)。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: 0)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.orthogonalScrollingBehavior = .groupPaging
return UICollectionViewCompositionalLayout(section: section)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
水平捲動,不分頁,滑動停止時剛好停在 group 的起點
orthogonalScrollingBehavior 設為 continuousGroupLeadingBoundary,group 的寬度設為 fractionalWidth(0.9)。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: 0)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
return UICollectionViewCompositionalLayout(section: section)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
水平捲動,分頁 & 顯示上一個 / 下一個的部分內容
範例 1: group 裡有一個 item
使用 groupPagingCentered。從 item 的 contentInsets 設間距,item 之間的間距將是 space * 2。另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 5
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
return UICollectionViewCompositionalLayout(section: section)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
範例 2: group 裡有兩個 item
另外記得要將 collectionView 的 isScrollEnabled 設為 false,不然畫面還是可以垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 5
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 2)
group.interItemSpacing = .fixed(space * 2)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
return UICollectionViewCompositionalLayout(section: section)
}
label 的 vertical content hugging 設為 252,vertical content compression 設為 751。
整個頁面垂直捲動,頁面裡有水平捲動的區塊
範例 1: 多個 section,每個 section 搭配一樣的 NSCollectionLayoutSection 排版
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(200), heightDimension: .absolute(318))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
section.orthogonalScrollingBehavior = .continuous
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]
return UICollectionViewCompositionalLayout(section: section)
}
範例 2: 多個 section,每個 section 搭配不同的 NSCollectionLayoutSection 排版
分成多個 section,依據以下規則排版:
- section number 偶數的 section
group 延水平方向排列,可水平滑動。
- section number 奇數的 section
group 延垂直方向排列。
func generateLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { [unowned self] section, environment in
if section.isMultiple(of: 2) {
return self.horizontalScrollLayoutSection
} else {
return self.verticalScrollLayoutSection
}
}
}
var horizontalScrollLayoutSection: NSCollectionLayoutSection {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(200), heightDimension: .absolute(318))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
section.orthogonalScrollingBehavior = .continuous
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]
return section
}
var verticalScrollLayoutSection: NSCollectionLayoutSection {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(143))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]
return section
}
設定搭配不同 section 的 cell,section number 奇數的 section 顯示 VerticalCollectionViewCell,section number 偶數的 section 顯示 HorizontalCollectionViewCell。VerticalCollectionViewCell 的圖片比例 0.7,HorizontalCollectionViewCell 的圖片寬度 100。
水平捲動的分頁,分頁的內容垂直捲動
將 configuration.scrollDirection 設成 horizontal,collectionView 的 isPagingEnabled 設成 true,實現水平滑動的分頁,section.orthogonalScrollingBehavior 設成 continuous 讓分頁的內容垂直捲動。
func generateLayout() -> UICollectionViewLayout {
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
return UICollectionViewCompositionalLayout(sectionProvider: { section, environment in
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: section == 0 ? 2 : 3)
group.interItemSpacing = .fixed(space)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
section.orthogonalScrollingBehavior = .continuous
return section
}, configuration: configuration)
}
image view 比例設為 0.7,priority 要設成 999,否則會有 auto layout breaking constraint 的警告訊息。
表格(table)
範例 1: iOS 14 的 UICollectionViewCompositionalLayout.list
使用 UICollectionViewCompositionalLayout.list 產生以表格樣式排版的 UICollectionViewCompositionalLayout,cell 高度將以 auto layout 條件自動計算。
func generateLayout() -> UICollectionViewLayout {
let configuration = UICollectionLayoutListConfiguration(appearance: .grouped)
return UICollectionViewCompositionalLayout.list(using: configuration)
}
範例 2:
item 的大小等於 group 的大小,group 的寬度等於 collection view 的寬度。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(143))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = space
return UICollectionViewCompositionalLayout(section: section)
}
畫面不能捲動的九宮格
除了設定 layout,記得也要將 collection view 的 isScrollEnabled 設為 false。
func generateLayout() -> UICollectionViewLayout {
let space: Double = 3
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1/3))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
return UICollectionViewCompositionalLayout(section: section)
}