用 flow layout 的 collection view 實現水平滑動的分頁畫面

開發 iOS App 時,我們時常看到水平滑動的分頁設計,就像下圖 Developer App 的紅色框框區塊。

開發 UIKit App 時,我們可用 4 種方法實現這樣的功能。

  • flow layout 的 collection view
  • compositional layout 的 collection view
  • scroll view
  • page view controller

接下來我們將以 flow layout 的 collection view 為例,說明如何實現這樣的功能。

開發重點

使用 flow layout 的 collection view 實現水平滑動的分頁畫面時,關鍵在於以下 2 點:

  • 設定 collection view 捲動方向為水平,並以分頁模式捲動。
  • 設定 cell 的大小等於 collection view 的大小。

為了讓 cell 的大小等於 collection view 的大小,我們可在以下三種地方設定。

  • 方法1: 定義 UICollectionViewDelegateFlowLayout 的 function collectionView(_:layout:sizeForItemAt:)。
  • 方法2: 在 controller 的 viewDidLayoutSubviews 設定 UICollectionViewFlowLayout 的 itemSize。
  • 方法3: 在 controller 的 viewDidAppear 設定 UICollectionViewFlowLayout 的 itemSize。

加入 collection view,設定 auto layout 條件

collection view 跟 Safe Area 的 top,leading & trailing 間距 10,collection view 的 aspect ratio 1:1。

設定 collection view 的 Scroll Direction 為 Horizontal,勾選 Paging Enabled

設定 collection view 的 dataSource & delegate 是 View Controller

設定 collection view cell

在 cell 裡加入 image view,image view 跟 content view 的邊界對齊,image view 的 Content Mode 設為 Aspect Fill。

新增繼承 UICollectionViewCell 的 NumberCollectionViewCell,將 cell 的類別和 Reuse ID 設為 NumberCollectionViewCell ,連結 outlet imageView & 宣告 cell 的 reuseIdentifier。

class NumberCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "\(NumberCollectionViewCell.self)"
@IBOutlet weak var imageView: UIImageView!

}

collection view 的 Estimate Size 設為 None,Min Spacing 設為 0

我們待會將從程式設定 collection view cell 的尺寸等於 collection view,因此 Estimate Size 要設為 None,否則它將用 auto layout 的條件計算 cell 的大小。

宣告儲存資料的 array

宣告 array photos 儲存 SF Symbol 的圖片名稱,從 1.circle.fill ~ 10.circle.fill。

class ViewController: UIViewController {

let photos = (1...10).map { "\($0).circle.fill" }

定義 UICollectionViewDataSource 的 function,設定 cell 的數量和內容

extension ViewController: UICollectionViewDataSource {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
photos.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCollectionViewCell.reuseIdentifier, for: indexPath) as! NumberCollectionViewCell
let photo = photos[indexPath.item]
cell.imageView.image = UIImage(systemName: photo)
return cell
}

}

一頁顯示一個 cell,設定 cell 的尺寸等於 collection view

當 collection view 設為 Paging Enabled 時,分頁的尺寸將是 collection view 的尺寸。因此我們必須讓 cell 的尺寸等於 collection view 的尺寸,如此才能一頁顯示一個 cell,實現水平滑動的分頁效果。

由於 ViewController 是 collection view 的 delegate,因此 collection view 將透過 protocol UICollectionViewDelegateFlowLayout 的 function collectionView(_:layout:sizeForItemAt:) 決定 cell 的尺寸。我們回傳 collectionView.bounds.size,因此 cell 的尺寸將等於 collection view。

extension ViewController: UICollectionViewDelegateFlowLayout {

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.bounds.size
}
}

結果

其它例子: 換成比較漂亮的圖片。

範例連結

設定 cell 尺寸的其它方法

剛剛的例子主要利用 UICollectionViewDelegateFlowLayout 的 collectionView(_:layout:sizeForItemAt:) 設定 cell 尺寸,不過我們也可以不透過 delegate,而是在 controller 的 viewDidLayoutSubviews 或 viewDidAppear 設定 UICollectionViewFlowLayout 的 itemSize。

class ViewController: UIViewController {

@IBOutlet weak var collectionView: UICollectionView!
let photos = (1...10).map { "\($0).circle.fill" }

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.itemSize = collectionView?.bounds.size ?? .zero
layout?.estimatedItemSize = .zero
}

說明

layout?.itemSize = collectionView?.bounds.size 將讓 cell 的尺寸等於 collection view,layout?.estimatedItemSize = .zero 則表示不使用 auto layout 計算 cell 的尺寸。值得注意的,以上程式若改在 viewDidLoad 或 viewWillAppear 設定 cell 尺寸,將產生問題,因為 viewDidLoad 或 viewWillAppear 時 collection view 的大小是不正確的,要等到 viewDidLayoutSubviews 或 viewDidAppear 才能取得 collection view 經過 auto layout 計算後真正的大小。

--

--

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

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