設定 UICollectionViewFlowLayout 的itemSize,實現一排顯示固定數量的 cell

使用 collection view 我們可以快速做出照片牆,不過如果我們希望一排顯示固定數量的 cell,像 iOS 的照片 App 一樣,不管在直向或橫向都顯示 5 個 cell,要怎麼做到呢?

最簡單的方法是利用 UICollectionViewCompositionalLayout,因為它可以幫我們算出一排 5 個時,一個 cell 需要的大小。

不過本篇文章的主角是 UICollectionViewFlowLayout,它的 cell 大小是由 itemSize 決定,因此接下來我們將說明要在何時更新 itemSize。

假設我們想要呈現的 cell 是正方形,因此若要一排 5 個,而且 0 間距, cell 的寬度將是 collection view 的寬度 / 5。

在 viewDidLoad 計算 cell 的大小 ?

範例1: 使用 collection view controller 顯示整個頁面

collection view 的寬度在 viewDidLoad 時即是正確的寬度,因此可在此計算 cell 的寬度。

class CollectionViewController: UICollectionViewController {

func configureCellSize() {
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.estimatedItemSize = .zero
layout?.minimumInteritemSpacing = 0
let width = floor(collectionView.bounds.width / 5)
layout?.itemSize = CGSize(width: width, height: width)
}

override func viewDidLoad() {
super.viewDidLoad()
configureCellSize()
}

畫面在直向時很完美,一排剛好 5 個。

那如果換成橫向呢 ? 因為 viewDidLoad 只會執行一次,它不會在橫向時重新計算 cell 大小,所以下圖的 cell 大小將是原本直向時計算的大小。

範例2: 使用 view controller + collection view

class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!

func configureCellSize() {
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.estimatedItemSize = .zero
layout?.minimumInteritemSpacing = 0
let width = floor(collectionView.bounds.width / 5)
layout?.itemSize = CGSize(width: width, height: width)
}

override func viewDidLoad() {
super.viewDidLoad()
configureCellSize()
}

連直向都不對 ! 為什麼呢 ?

因為要等到 viewDidLayoutSubviews 或 viewDidAppear 時,元件的位置大小才會是 Auto Layout 條件算出的位置大小,因此我們在 viewDidLoad 得到的 collection view 寬度是錯的。

解法: 從螢幕寬度計算 collectionView寬度,在 viewWillTransition 時更新 itemSize

剛剛我們看到了兩個問題:

  • 取得錯誤的 collection view 寬度。
  • 轉向時沒有重新計算 itemSize。

有很多方法可以取得正確的 collection view 寬度,其中一個方法是利用螢幕寬度和畫面設計的間距條件計算。在剛剛的例子裡,collection view 對齊螢幕的左右兩側,因此 collection view 寬度就是螢幕寬度。

畫面轉向時會觸發 view controller 的 viewWillTransition(to:with:),因此我們可定義它,在裡面更新 itemSize。

class ViewController: UIViewController {

@IBOutlet weak var collectionView: UICollectionView!
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
configureCellSize(collectionViewWidth: size.width)
}

func configureCellSize(collectionViewWidth: CGFloat) {

let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.estimatedItemSize = .zero
layout?.minimumInteritemSpacing = 0
let width = floor(collectionViewWidth / 5)
layout?.itemSize = CGSize(width: width, height: width)

}
override func viewDidLoad() {
super.viewDidLoad()
configureCellSize(collectionViewWidth: UIScreen.main.bounds.width)
}

說明

在 viewDidLoad 呼叫 configureCellSize,此時的 collection view 寬度傳入 UIScreen.main.bounds.width。在畫面轉向,viewWillTransition 觸發時也會呼叫 configureCellSize,此時的 collection view 寬度傳入 size.width,因為 size.width 代表轉向後的螢幕寬度。

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com