連接Table Cell用戶界面到代碼
在你能夠在table view cell中顯示動(dòng)態(tài)數(shù)據(jù)之前,你需要?jiǎng)?chuàng)建outlet來(lái)連接storyboard中的屬性和在MealTableViewCell.swift文件中代表table view cell的代碼。
連接這些視圖到MealTableViewCell.swift代碼
- 在storyboard中,選擇table view cell中的label。
-
打開助理編輯器。
image: ../Art/assistant_editor_toggle_2x.png -
如有必要,盡可能擴(kuò)展工作區(qū)空間。
image: ../Art/navigator_utilities_toggle_on_2x.png -
在編輯器選擇器欄中,它顯示在助理編輯器的頂部,把助理視圖從預(yù)覽視圖切換到切換到Automatic > MealTableViewCell.swift.
image: ../Art/CTV_assistant_switchtocode_2x.png
MealTableViewCell.swift顯示在右側(cè)的編輯器中。
- 在MealTableViewCell.swift中,找到class行:
class MealTableViewCell: UITableViewCell {
- 在class行的下面,添加下面注釋:
//MARK: Properties
-
按住Control鍵,從畫布中拖拽label到右側(cè)編輯器顯示的代碼中,在剛才添加的注釋的下面釋放。
image: ../Art/CTV_label_dragoutlet_2x.png -
在彈出的對(duì)話框中,Name字段鍵入nameLabel。
讓其他選項(xiàng)保持原樣。你的對(duì)話框看起來(lái)是這樣的。
image: ../Art/CTV_label_addoutlet_2x.png - 點(diǎn)擊Connect。
- 在storyboard中,選擇table view cell的image view。
-
按住Control鍵,從畫布中拖拽image view到右側(cè)編輯器顯示的代碼中,在剛才添加的nameLabel屬性下面釋放。
image: ../Art/CTV_imageview_dragoutlet_2x.png -
在彈出的對(duì)話框中,Name字段鍵入photoImageView。
讓其他選項(xiàng)保持原樣。并點(diǎn)擊Connect。
image: ../Art/CTV_imageview_addoutlet_2x.png - 在storyboard中,選擇table view cell的rating控件。
-
按住Control鍵,從畫布中拖拽rating控件到右側(cè)編輯器顯示的代碼中,在剛才添加的photoImageView屬性下面釋放。
image: ../Art/CTV_ratingcontrol_dragoutlet_2x.png -
在彈出的對(duì)話框中,Name字段鍵入ratingControl。
讓其他選項(xiàng)保持原樣。并點(diǎn)擊Connect。
image: ../Art/CTV_ratingcontrol_addoutlet_2x.png
在MealTableViewCell.swift中你的outlet看上去應(yīng)該是這樣的:
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var photoImageView: UIImageView!
@IBOutlet weak var ratingControl: RatingControl!
加載初始數(shù)據(jù)
為了在你的table cell中顯示真實(shí)數(shù)據(jù),你需要編寫代碼來(lái)加載這些數(shù)據(jù)。在這點(diǎn)上,你已經(jīng)有菜品的數(shù)據(jù)模型了:Meal類。你還需要保存這些菜品的一個(gè)列表。跟蹤這個(gè)數(shù)據(jù)的最自然的地方是與菜品列表場(chǎng)景連接的自定義視圖控制器子類。這個(gè)視圖控制器將管理顯示菜品列表的視圖,并有一個(gè)引用指向在用戶界面顯示的內(nèi)容背后的數(shù)據(jù)模型。
首先,創(chuàng)建一個(gè)自定義的table view controller子類來(lái)管理這個(gè)菜品列表場(chǎng)景。
創(chuàng)建一個(gè)UITableViewController子類
- 選擇File > New > File (或者按下 Command-N)。
- 在出現(xiàn)的對(duì)話框中,選擇iOS,并且選擇Cocoa Touch Class。
- 點(diǎn)擊Next。
- 在Class字段,鍵入Meal。
- 在Subclass of字段,選擇UITableViewController。
把類標(biāo)題改為MealTableViewController。 - 確保Also create XIB file選項(xiàng)沒有被選中。
XIB文件是一個(gè)舊的通過視圖控制器設(shè)計(jì)視圖管理的方式。它們?cè)缬趕toryboard出現(xiàn),基本相當(dāng)于代表storyboard上的單一視圖。這個(gè)視圖控制器不需要一個(gè)XIB文件,因?yàn)槟阋呀?jīng)定義它連接應(yīng)用的storyboard了。 - 確保語(yǔ)言是Swift。
- 點(diǎn)擊Next。
保存位置是項(xiàng)目目錄。
Group選項(xiàng)為默認(rèn)的FoodTracker。
在目標(biāo)區(qū)域,你的應(yīng)用被選擇,而應(yīng)用的tests沒有被選擇。 - 其他的選項(xiàng)保持不變,點(diǎn)擊Create。
Xcode創(chuàng)建了MealTableViewController.swift,一個(gè)定義自定義table view controller子類的源代碼文件。 - 必要時(shí),在Project navigator,拖拽MealTableViewController.swift到和其他的Swift文件一起。
在這個(gè)自定義類中,你能定義一個(gè)屬性來(lái)存儲(chǔ)Meal對(duì)象的列表。Swift標(biāo)準(zhǔn)(Swift standard library)包含一個(gè)被稱為Array(數(shù)組)的結(jié)構(gòu),它能夠很好的跟蹤列表的項(xiàng)。
加載初始數(shù)據(jù)
-
返回標(biāo)準(zhǔn)編輯器。并盡可能的擴(kuò)展工作區(qū)空間。
image: ../Art/standard_toggle_2x.png - 打開MealTableViewController.swift。
- 緊跟著class行添加下面的代碼:
//MARK: Properties
var meals = [Meal]()
這代碼在MealTableViewController聲明了一個(gè)屬性,并用一個(gè)默認(rèn)值初始化它(一個(gè)空的元素項(xiàng)為Meal對(duì)象數(shù)組)。聲明meals為變量而不是常量,意味著你能在初始化它之后還可以給它添加元素項(xiàng)。
- Table view controller模版包含很多存根方法以及對(duì)于這些方法的注釋。這些占位的實(shí)現(xiàn)方法你可以刪除注釋和擴(kuò)展(讓它們可用)來(lái)定義表的外觀和行為。在你設(shè)置完成模型數(shù)據(jù)后你將看到這些方法?,F(xiàn)在,滾動(dòng)到這些方法的下面,在結(jié)束花括號(hào)的上面添加如下方法:
//MARK: Private Methods
private func loadSampleMeals() {
}
這是一個(gè)輔助方法,用來(lái)加載樣本數(shù)據(jù)到應(yīng)用。
- 在loadSampleMeals()方法中,首先加載下面三個(gè)菜品圖片:
let photo1 = UIImage(named: "meal1")
let photo2 = UIImage(named: "meal2")
let photo3 = UIImage(named: "meal3")
確保圖片在項(xiàng)目中的名字和在代碼中的一致。
- 在加載完這些圖片后,創(chuàng)建三個(gè)菜品對(duì)象。
guard let meal1 = Meal(name: "Caprese Salad", photo: photo1, rating: 4) else {
fatalError("Unable to instantiate meal1")
}
guard let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5) else {
fatalError("Unable to instantiate meal2")
}
guard let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3) else {
fatalError("Unable to instantiate meal2")
}
因?yàn)椴似奉惖?init!(name:, photo:, rating:)初始化器是可失敗的,所以你需要檢查初始化器返回的結(jié)果。在本例中,你傳遞的是有效的參數(shù)嗎所以初始化器永遠(yuǎn)不會(huì)失敗。若果初始化器失敗,你的代碼就有了錯(cuò)誤。為了幫助你標(biāo)記和修復(fù)錯(cuò)誤,如果初始化器失敗,fatalError()函數(shù)就會(huì)在控制臺(tái)打印一個(gè)錯(cuò)誤消息,并且應(yīng)用程序終止。
- 在創(chuàng)建Meal對(duì)象之后,使用如下方法添加它們到meals數(shù)組。
meals += [meal1, meal2, meal3]
- 找到 viewDidLoad()方法。模版的實(shí)現(xiàn)看上去是這樣的:
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
模版實(shí)現(xiàn)的這個(gè)方法包含注釋,這些注釋是在Xcode創(chuàng)建MealTableViewController.swift的時(shí)候插入的。像這樣的代碼注釋在源代碼文件中提供了提示和上下文信息,但是在本課中你用不到它們。
- 在 viewDidLoad()方法中,刪除注釋,并在super.viewDidLoad()后面添加如下方法來(lái)加載樣本菜品數(shù)據(jù)。
// Load the sample data.
loadSampleMeals()
當(dāng)視圖加載時(shí),這個(gè)代碼調(diào)用你剛寫的加載樣本數(shù)據(jù)的輔助方法。你把它分離到自己的方法中,為的是代碼更加模塊化和易讀。
你的viewDidLoad()方法看上去應(yīng)該是這樣的:
override func viewDidLoad() {
super.viewDidLoad()
// Load the sample data.
loadSampleMeals()
}]
你的loadSampleMeals()方法看上去應(yīng)該是這樣的:
private func loadSampleMeals() {
let photo1 = UIImage(named: "meal1")
let photo2 = UIImage(named: "meal2")
let photo3 = UIImage(named: "meal3")
guard let meal1 = Meal(name: "Caprese Salad", photo: photo1, rating: 4) else {
fatalError("Unable to instantiate meal1")
}
guard let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5) else {
fatalError("Unable to instantiate meal2")
}
guard let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3) else {
fatalError("Unable to instantiate meal2")
}
meals += [meal1, meal2, meal3]
}
檢查點(diǎn):通過 Product > Build.選擇構(gòu)建項(xiàng)目。你的構(gòu)建應(yīng)該時(shí)沒有錯(cuò)誤的。注意,這時(shí)候,你或許看到一個(gè)關(guān)于在應(yīng)用中無(wú)法到達(dá)View Controller場(chǎng)景的Xcode警告。你將在下一課修復(fù)它。在本課剩下來(lái)的部分,先忽略它。
重要
如果你運(yùn)行出現(xiàn)問題,確保項(xiàng)目中的圖片名字是否真的和在代碼中使用的名字一致,
顯示數(shù)據(jù)
現(xiàn)在,你的自定義table view controller子類,MealTableViewController,有了一個(gè)可變數(shù)組,它用一些樣本數(shù)據(jù)預(yù)先做了填充。現(xiàn)在你需要在用戶界面上顯示這些數(shù)據(jù)。
為了顯示動(dòng)態(tài)數(shù)據(jù),一個(gè)table view需要兩個(gè)重要的幫手:數(shù)據(jù)源(data source)和委托(delegate)。一個(gè)table view 數(shù)據(jù)源,就像它的名字暗示的那樣,提供這個(gè)table view要顯示的數(shù)據(jù)。一個(gè)table view委托幫助table view管理cell的選擇、行高、以及與顯示數(shù)據(jù)相關(guān)的其他方面。默認(rèn)情況下,UITableViewController及其子類采用必要的協(xié)議來(lái)讓table view controller成為它關(guān)聯(lián)的表視圖的數(shù)據(jù)源(UITableViewDataSource協(xié)議)和委托(UITableViewDelegate協(xié)議)。你的工作就是在table view controller子類中實(shí)現(xiàn)合適的協(xié)議方法,這樣你的table view就有了正確的行為。
Table view需要實(shí)現(xiàn)三個(gè)表視圖數(shù)據(jù)源方法。
func numberOfSections(in tableView: UITableView) -> Int
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
第一個(gè) numberOfSections(In:)方法,它高速表視圖有多少section(部分)要顯示。section是表視圖內(nèi)部cell的可視化分組,它在表視圖里有很多數(shù)據(jù)的時(shí)候特別有用。對(duì)于簡(jiǎn)單的表視圖,比如FoodTracker 應(yīng)用,你只需要一個(gè)部分顯示就可以了,所以實(shí)現(xiàn)這個(gè)方法很簡(jiǎn)單。
在table view 中顯示一個(gè)section
- 在 MealTableViewController.swift中,找到numberOfSections(In:)數(shù)據(jù)源方法。模版實(shí)現(xiàn)看上去是這樣的:
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
- 把返回值改為1,刪掉警告注釋。
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
這個(gè)代碼讓table view顯示1個(gè)部分。你刪掉的注釋說(shuō)的是#warning Incomplete implementation(警告 沒有完成實(shí)現(xiàn)),但你已經(jīng)實(shí)現(xiàn)了,所以就刪掉了。
接下來(lái)的數(shù)據(jù)源方法,tableView(_:numberOfRowsInSection:),高速表視圖一個(gè)給定的部分里面有多少行。你的表視圖只有一個(gè)部分,并且每個(gè)Meal對(duì)象都應(yīng)該有自己的行。這就意味著行數(shù)應(yīng)該是meals數(shù)組里Meal對(duì)象的數(shù)目。
返回table view的行數(shù)
- 在MealTableViewController.swift中,找到tableView(_:numberOfRowsInSection:)數(shù)據(jù)源方法。它的模版實(shí)現(xiàn)看上去是這樣的:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
你要返回你有的菜品樹木。Array有一個(gè)屬性稱為count,它返回?cái)?shù)組中項(xiàng)目的總數(shù),所以行數(shù)就是meals.count。
- 改變tableView(_:numberOfRowsInSection:)數(shù)據(jù)源方法返回合適的行樹,移除警告注釋。
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return meals.count
}
最后一個(gè)數(shù)據(jù)源方法,tableView(_:cellForRowAt:),為給定的行配置并提供一個(gè)cell用來(lái)顯示。表視圖中的每個(gè)行都有一個(gè)cell,這個(gè)cell決定行中顯示的內(nèi)容以及這些內(nèi)容如何布局。
對(duì)于只有少量行的表視圖,所有行或許都在屏幕上,所以這個(gè)方法調(diào)用表中的每一行。但是有很多行的表視圖,在給定的時(shí)間里只有小部分能夠顯示在屏幕上。表視圖如果只請(qǐng)求需要被顯示的行的cell就會(huì)大大提高了效率,這就是tableView(_:cellForRowAt:)允許表視圖做的。
對(duì)于在table view中任何給定的row,你通過獲取在meals數(shù)組中的合適的Meal來(lái)配置cell,然后用Meal類的合適值來(lái)設(shè)置cell的屬性。
在table view中配置和顯示cell
- 在MealTableViewController.swift,找到tableView(_:cellForRowAt:) 數(shù)據(jù)源方法并取消注釋。(要取消注釋,只要?jiǎng)h除圍繞它的 /* 和 */字符。)
完成后這個(gè)這個(gè)方法看上去是這樣的:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
return cell
}
dequeueReusableCell(withIdentifier:for:)方法從這個(gè)table view中請(qǐng)求一個(gè)cell。作為替代在用戶滾動(dòng)cell的時(shí)候創(chuàng)建新cell并刪除舊cell的方式,表格會(huì)盡可能的重用cell。如果沒有可用的cell, dequeueReusableCell(withIdentifier:for:)會(huì)實(shí)例化一個(gè)新的;但是如果cell滾動(dòng)到屏幕的外面,它們會(huì)被重用。標(biāo)識(shí)符(identifier)會(huì)告訴dequeueReusableCell(withIdentifier:for:)哪個(gè)類型的cell要被創(chuàng)建或重用。
為了這段代碼能夠工作,你需要改變?cè)趕toryboard中的cell(MealTableViewCell)標(biāo)識(shí)符屬性,然后添加代碼來(lái)配置cell。
- 在這個(gè)方法一開始的地方添加下面的代碼:
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MealTableViewCell"
這使用storyboard中設(shè)置的標(biāo)識(shí)符創(chuàng)建了一個(gè)常量。
- 使用cellIdentifier變量更新方法中的標(biāo)識(shí)符,像下面這樣:
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
- 因?yàn)槟銊?chuàng)建了一一個(gè)你想用的自定義cell類,將cell的類型降級(jí)到你自定義的cell的子類,MealTableViewCell。
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? MealTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
這段代碼有很多事要做:
- as? MealTableViewCell表達(dá)式試圖把返回對(duì)象從UITableViewCell類降級(jí)到MealTableViewCell類。這個(gè)返回值是一個(gè)可選值(optional)。
- guard let表達(dá)式會(huì)安全解包這個(gè)可選值。
- 如果你的storyboard設(shè)置正確,并且cellIdentifier匹配storyboard的標(biāo)識(shí)符,那么降級(jí)處理將不會(huì)失敗。如果降級(jí)失敗,fatalError()函數(shù)就會(huì)在控制臺(tái)打印錯(cuò)誤信息,并終止應(yīng)用。
- 在guard語(yǔ)句后面,添加下面的代碼:
// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]
這個(gè)代碼從meals數(shù)組獲取合適的菜品對(duì)象。
- 現(xiàn)在,使用meal對(duì)象來(lái)配置你的cell。用下面的代碼來(lái)替換// Configure the cell注釋。
cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.ratingControl.rating = meal.rating
這個(gè)代碼為每個(gè)在table view cell中的視圖設(shè)置合適的數(shù)據(jù),這些數(shù)據(jù)來(lái)自meal對(duì)象。
你的tableView(_:cellForRowAt:)方法看起來(lái)是這樣的:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MealTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? MealTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]
cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.ratingControl.rating = meal.rating
return cell
}
在用戶界面顯示數(shù)據(jù)的最后一步是把MealTableViewController.swift中定義代碼連接到菜品列表場(chǎng)景。
將Table View Controller指向MealTableViewController.swift
- 打開storyboard。
-
通過點(diǎn)擊在場(chǎng)景dock直到整個(gè)場(chǎng)景有了一個(gè)藍(lán)色的輪廓來(lái)選擇table view controller。
image: ../Art/CTV_scenedock_table_2x.png - 打開Identity inspector。
-
在Identity inspector中,找到Class字段,并選擇MealTableViewController。
image: ../Art/CTV_inspector_identity_tablevc_2x.png
檢查點(diǎn):運(yùn)行應(yīng)用。你在 viewDidLoad()方法中添加的項(xiàng)目列表應(yīng)該顯示在table view的cell上了。你可能注意到在table view cell和狀態(tài)欄之間有一個(gè)小的重疊——你將在下一課修復(fù)它。

為導(dǎo)航準(zhǔn)備菜品詳情場(chǎng)景
當(dāng)你準(zhǔn)備在FoodTracker應(yīng)用中實(shí)現(xiàn)導(dǎo)航,你需要?jiǎng)h除一些占位的代碼和你不再需要的的用戶界面。
清除項(xiàng)目不使用的部分
-
打開storyboard并查看菜品詳情場(chǎng)景。
你的菜品詳情場(chǎng)景的用戶界面看上去是這樣的:
image: ../Art/CTV_mealsceneUI_old_2x.png -
在這個(gè)場(chǎng)景中,選擇Meal Name 標(biāo)簽(label),并按下刪除鍵刪除它。
在棧視圖中的其他元素會(huì)自動(dòng)重定位。
image: ../Art/CTV_mealsceneUI_new_2x.png - 打開ViewController.swift。
- 在ViewController.swift中,找到textFieldDidEndEditing(_:)方法。
func textFieldDidEndEditing(_ textField: UITextField) {
mealNameLabel.text = textField.text
}
- 刪除設(shè)置label的text屬性的行。
mealNameLabel.text = textField.text
你將很快使用新的實(shí)現(xiàn)來(lái)替換它。
- 在ViewController.swift,找到mealNameLabel的outlet,并刪除它。
@IBOutlet weak var mealNameLabel: UILabel!
因?yàn)楝F(xiàn)在你有兩個(gè)視圖控制器在項(xiàng)目中,需要給ViewController.swift一個(gè)更加有意義的名字。
重命名ViewController.swift文件
- 在project navigator,點(diǎn)擊 ViewController.swift文件一次,并按下回車鍵。
Xcode會(huì)允許你為這個(gè)文件輸入一個(gè)新名字。 - 重命名為MealViewController.swift,按下回車鍵。
- 在MealViewController.swift中,找到類聲明行
class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
- 改變類名為MealViewController
class MealViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
- 在文件頂部的注釋中,也把名字從ViewController.swift 改為MealViewController.swift。
- 打開storyboard。
-
通過點(diǎn)擊它的場(chǎng)景dock選擇視圖控制器。
image: ../Art/CTV_scenedock_mealscene_2x.png - 選中這個(gè)視圖控制器,打開Identity inspector。
-
在Identity inspector,在Class字段把ViewController改為MealViewController。
image: ../Art/CTV_mealscenefinal_2x.png
檢查點(diǎn):構(gòu)建或運(yùn)行應(yīng)用。一切應(yīng)該如常。
小結(jié)
在本課中,你構(gòu)建了一個(gè)自定義的table view cell。你把模型對(duì)象附加到了table view controller。你給模型添加了樣本數(shù)據(jù),并且你實(shí)現(xiàn)使用模型數(shù)據(jù)動(dòng)態(tài)的填充表格所需的表視圖控制器代碼。
下一課,你將添加在表視圖和菜品視圖之間導(dǎo)航的功能。
注意
想看本課的完整代碼,下載這個(gè)文件并在Xcode中打開。
下載文件












