Swift : UITableViewController & UICollectionViewController — Part 2

Hsin-Yu Tang
AppWorks School
Published in
10 min readMay 8, 2017

上一篇介紹了 UITableViewController 的實作方法,連結如下:

這次就要把剩下的 UICollectionViewController 介紹完畢啦!

PART 2: UICollectionViewController
UICollectionViewController 常用來展示格狀呈現的資料,iPhone 中的月曆應該就是用此做成的。和 UITableViewController 相似,每一格 (item) 稱作一個 cell,此外也可以將好幾個 item 設置成一個 section,並且設置 header 和/或 footer。

本次示範目標

然而,UICollectionViewController 相較於 UITableViewController,靈活度較佳,可做到垂直滑動或水平滑動,也因此除了協定中要求實作的 func 外,還須處理 layout 的問題。

UITableViewController 和 UICollectionViewController 之比較

跟著以下步驟,建立起一個 UICollectionViewController 吧!

1 打開 Part 1 建立的 DemoForTableViewAndCollectionView 專案。

2 新增一個 UICollectionViewController,在此我將 Class 命名為DemoCollectionViewController。

3 可以看到 class DemoCollectionViewController 上有一句

private let reuseIdentifier = “Cell”

viewDidLoad() 中也有註冊 cell 的 code:

// Register cell classes
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)

刪除這兩句 code,之後我會重新建立 cell,並重新命名 Identifier。
註:看到這裡,一定很好奇為什麼新增的 UICollectionViewController 比起 UITableViewController,好像已經幫我們做了很多事了,那是因為 UITableViewCell 中有一個屬性為 textLabel,所以我們無需做任何設定,只要把值塞進去 textLabel 中就可以呈現。但 UICollectionViewCell 中沒有這個屬性可以呼叫,必須和 Part 1 提到的實作客製化 cell 的方法一樣,要先註冊 cell,因此 UICollectionViewController 才會先幫我們做好。在此,我會直接刪除 UICollectionViewController 先寫好的 code,以利後續實作客製化 cell。

4 回到 DemoCollectionViewController,我們宣告一個名為 countries 的常數,作為 cell 要呈現的資料:

let countries = [“Spain”, “France”, “Japan”, “India”, “Korea”, “China”, “Philippines”,“Argentia”, “Brasil”]

5 接著,我們新增一個 UICollectionViewCell 檔案,並選擇同步產生 XIB 檔,在此我命為其為 DemoCollectionViewCell。

6 我在 XIB 檔中,簡單設置一個 ImageView 和一個 Label,設定好 constraints 後,拉好@IBOutlet。一樣必須設定其 Identifier,在此我命名為 customCell。

7 回到 DemoCollectionViewController,在 viewDidLoad() 中必須註冊 cell:

override func viewDidLoad() {super.viewDidLoad()let nib = UINib(nibName: "DemoCollectionViewCell", bundle: nil)collectionView?.register(nib, forCellWithReuseIdentifier: "customCell")}

一樣必須注意的是 nibName 指的是 XIB 檔的檔名,CellReuseIdentifier 指的則是在 XIB 檔中設置的 Identifier。

8 之後我們要實作協定中 2 個必須實作的 func:
(1) 實作每個 section 的格數

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

(2) 實作每格的 cell

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! DemoCollectionViewCellcell.demoImageView.image = UIImage(named: "circle")cell.demoLabel.text = countries[indexPath.row]cell.backgroundColor = UIColor.lightGrayreturn cell}

註:為了方便看到 cell 的邊界,在此我將 cell 的背景色設為淺灰色。

9 看似萬事皆備,但此時run 專案看到的畫面會是預設的大小和間距,因為我們還沒設定前面提到最大的不同 layout!在 viewDidLoad() 中設定UICollectionViewFlowLayout,以調整 cell 的大小和間距:

let layout = self.collectionViewLayout as! UICollectionViewFlowLayout

(1) cell 的大小

layout.itemSize = CGSize(width: 100, height: 100)

(2) 同一列 cell 和 cell 的間距

layout.minimumInteritemSpacing = 18

(3) 列和列的間距

layout.minimumLineSpacing = 18

(4) section 的邊界

layout.sectionInset = UIEdgeInsets(top: 18, left: 18, bottom: 18, right: 18)

大家可以試著調整不同的數值去看看呈現的效果,要注意的是 minimumInteritemSpacing 和 minimumLineSpacing 指的是最小值,所以實際呈現的狀況還是要以計算出來的樣子為主。

10 此時,run 專案應該已經成功,可以看到每個 cell 呈現出 countries 中的 String。

最後,我想補充說明如何增加 header 和 footer,有興趣的人可以自己實作看看!

1 要做 header 和 footer 必需各新增一個 UICollectionReusableView 檔案,並選擇同步產生 XIB 檔,在此我分別命名為 DemoHeaderCollectionReusableView 和 DemoFooterCollectionReusableView。

2 為求方便我在 XIB 檔中,不設置任何元件,直接設定其 Identifier,在此我分別命名為 sectionHeader 和 sectionFooter。

3 回到 DemoCollectionViewController,在 viewDidLoad() 中必須註冊 ReusableView 和設定大小:

let nibOfHeader = UINib(nibName: "DemoHeaderCollectionReusableView", bundle: nil)collectionView?.register(nibOfHeader, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "sectionHeader")let nibOfFooter = UINib(nibName: "DemoFooterCollectionReusableView", bundle: nil)collectionView?.register(nibOfFooter, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "sectionFooter")layout.headerReferenceSize = CGSize(width: 375, height: 40)layout.footerReferenceSize = CGSize(width: 375, height: 40)

4 和 UITableViewController 不一樣的是,UICollectionViewController 的 header 和 footer 實作的 func 共用一個,藉由判斷不同的 kind 去決定是 header 或 footer:

// MARK: - Add Header and Footeroverride func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {switch kind {case UICollectionElementKindSectionHeader:let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeader", for: indexPath)header.backgroundColor = .brownreturn headercase UICollectionElementKindSectionFooter:let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFooter", for: indexPath)footer.backgroundColor = .lightGrayreturn footerdefault:assert(false, "Unexpected element kind")}}

5 此時 run 專案,就可以看到呈現褐色的 header 和呈現淺灰色的 footer 囉!

以上的 code,我有放在 GitHub,有興趣的人可以進去看看!
https://github.com/CelesteTang/DemoForTableViewAndCollectionView.git

--

--