Swift : UITableViewController & UICollectionViewController — Part 1

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

UITableViewController 和 UICollectionViewController 兩者都繼承自UIViewController,於製作 iOS app 中十分常見,在此我會介紹如何使用 Swift 將這兩者建構起來,希望對正在學習製作 iOS app 的人能有幫助。

PART 1 : UITableViewController
UITableViewController 常用來展示逐列呈現的資料,iPhone 中的設定就是一個最好的例子。每一列 (row) 都稱作一個 cell,每一個 cell 中除了基本的文字,還可以客製化設置圖片或是其他元件,此外也可以將好幾個 row 設置成一個節 (section),並且設置 header 和/或 footer。

本次示範目標

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

1 打開 Xcode,建立一個 Single View Application 專案,在此我命名為DemoForTableViewAndCollectionView。

2 進到專案後,先 run (Product → Run,或⌘R) 一次專案,確保專案是沒問題的。

3 忽略或刪除原生的 ViewController.swift 和 Main.storyboard 中的 View Controller Scene,並新增 (File → New → File…,或⌘N) 一個 Cocoa Touch Class,Subclass 請選取 UITableViewController,在此我將 Class 命名為DemoTableViewController。

4 進到 DemoTableViewController.swift,長按⌘、點擊 UITableViewController,可以看到 UITableViewController 繼承自 UIViewController,並遵從 (conform) 兩個協定 (protocol) : UITableViewDelegate 和 UITableViewDataSource。

5 長按⌘、點擊 UITableViewDataSource,可以看到協定中有 2 個 func 的開頭沒有 optional,這表示要建構起一個 UITableViewController,你務必實作這 2 個 func,不然是 run 不起來的!而在 UITableViewDelegate 中,則是所有的 func 都是 optional 的,因此沒有強制要實作,可針對自己開發上的需求去選擇實作不同的 func。建議新手可以親自點進去閱讀這些文件,可以讓自己功力倍增唷!

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

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

7 接著,我們實作 UITableViewDataSource 要求的 2 個 func :
(1) 實作每個 section 的列數

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return countries.count}

註:numberOfSections 這個 func 是 optional 的,若不實作預設值為 1,代表只有 1 個 section。
(2) 實作每列的 cell

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! DemoTableViewCellcell.demoImageView.image = UIImage(named: "circle")cell.demoLabel.text = countries[indexPath.row]return cell}

註:在這裡使用的是 dequeueReusableCell,cell 離開可見範圍就會自動被收進回收區,再用來製成即將顯示的 cell,好處是能節省記憶體,因為只會製作比畫面所呈現多一點的 cell,不用無上限的製作 cell,試想如果一個 tableView 中會呈現成千上萬個 cell,那就要製作出成千上萬個 cell,多麽耗費記憶體啊!

8 此時,run 專案還是不會成功的,因為我們還沒將 DemoTableViewController 和 storyboard 綁定。
(1) 進到 Main.storyboard,從右下方 Object library 中拖出一個 TableViewController,在右上方 Identity inspector 中,將 Custom class 的 class 選為 DemoTableViewController。
(2) 之後,點選 DemoTableViewController 中的 Prototype cells,在右上方 Attributes inspector 中,將 Table View Cell 的 Identifier 命名為 defaultCell。

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

以上是使用預設的 cell 格式,接下來我將示範客製化 cell 的做法:

1 新增一個 UITableViewCell 檔案,並選擇同步產生 XIB 檔,在此我命為其為 DemoTableViewCell。

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

3 回到 DemoTableViewController,在 viewDidLoad() 中必須註冊 cell:

override func viewDidLoad() {super.viewDidLoad()let nib = UINib(nibName: "DemoTableViewCell", bundle: nil)tableView.register(nib, forCellReuseIdentifier: "customCell")}

值得注意的是 nibName 指的是 XIB 檔的檔名,CellReuseIdentifier 指的則是在 XIB 檔中設置的 Identifier。

4 之後我們要改寫 cellForRowAt 這個 func:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! DemoTableViewCellcell.demoImageView.image = UIImage(named: "circle")cell.demoLabel.text = countries[indexPath.row]return cell}

5 此時,再 run 一次專案,就可以發現每列所呈現的是客製化後的 cell。

最後,我想簡單介紹 2 個常用的效果。

1 自動調整 cell 高度,讓cell 隨文字的量去調整高度。

自動調整 cell 高度

(1) 首先,我們先在 DemoTableViewCell 中,設定 demoLabel 的numberOfLines 等於 0:

class DemoTableViewCell: UITableViewCell {@IBOutlet weak var demoImageView: UIImageView!@IBOutlet weak var demoLabel: UILabel!override func awakeFromNib() {super.awakeFromNib()demoLabel.numberOfLines = 0}}

(2) 回到 DemoTableViewController,在 viewDidLoad() 中設定 tableView 的高度等於 UITableViewAutomaticDimension,且必須給予 tableView 估計的高度作為最小依據:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

(3) 因原先 countries 中的 String 都很短,無法達到換行的效果,所以在這我們將 countries 改寫為:

var countries = [
“Av de Isabel la Católica, 41004 Sevilla, Spain”,
“35 Rue du Chevalier de la Barre, 75018 Paris, France”,
“11 Naitōmachi, Shinjuku-ku, Tōkyō-to 160–0014, Japan”,
“Mahakali Caves Rd, Sunder Nagar, Andheri East, Sunder Nagar, Jogeshwari East, Mumbai, Maharashtra 400093, India”,
“40–1003 Hangangno 3(sam)-ga, Yongsan-gu, Seoul, Korea”,
“Av. Cárcano s/n, Chateau Carreras, Córdoba, Argentia”,
“Av. Atlântica, S/N — Copacabana, Rio de Janeiro — RJ, 22010–000, Brasil”]

(4) 此時 run 專案,就可以看到 cell 會隨著內容去變動高度囉!
註:如果沒有成功,可能得檢查一下客製化 cell 的 constraints 的設定,是否能讓 label 中的文字彈性增加!

2 增加 header 和 footer
(1) 首先增加 header 或 footer 的方式有兩種,可以回傳 String 成預設的樣子,也可回傳 UIView 客製化做成自己想要的樣貌,在這裡我僅以簡單的回傳 String 作為範例。
(2) 實作 titleForHeaderInSection 和 titleForFooterInSection。

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {return "Header"}override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {return "Footer"}

(3) 此外,也可以調整 header 和 footer 的高度。實作 heightForHeaderInSection 和 heightForFooterInSection。

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {return 40.0}override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {return 20.0}

(4) 此時 run 專案,就可以看到 header 和 footer 囉!

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

--

--