Chapter 33:自定義 Table Cells
在你的應用程序搜索iTunes商店之前毫痕,首先讓我們讓表格視圖看起來更好一些秃流。對于應用程序來說,外觀確實很重要!
你的應用程序仍然會使用相同的偽數(shù)據(jù),但你會讓它看起來更好咬展。這是你在本章結尾會看到的:
在這個過程中耸峭,你會學到以下幾點:
1. 自定義表單元格和nib:
如何通過nib文件創(chuàng)建、配置和使用自定義表單元格蝉稳。
2. 改變App的外觀:
改變App的外觀抒蚜,使它更令人興奮和充滿活力。
3. commit標簽:
使用Xcode的內(nèi)置Git支持標記特定的commit耘戚,以便稍后識別代碼庫中的重要里程碑嗡髓。
4. 調試器:
使用調試器識別常見的崩潰并找出崩潰的根本原因。
1. 自定義表單元格和nib:
對于之前的應用程序收津,您使用原型單元格創(chuàng)建自己的表視圖單元格布局饿这。這很好,但還有另一種方法撞秋。在本章中长捧,您將創(chuàng)建一個帶有單元格設計的“nib”文件,并從中加載表視圖單元格吻贿。原理與原型單元非常相似串结。
nib,也稱為xib舅列,非常像一個storyboard奉芦,只是它只包含一個item的設計。該item可以是視圖控制器剧蹂,但也可以是單個view 或 table view cell声功。nib實際上是一個“凍干”對象的容器,你可以在Interface Builder中編輯它宠叼。
實際上先巴,許多應用程序由nib和storyboard文件組成其爵,所以最好知道如何同時使用這兩種文件。
添加 assets
? 首先伸蚯,將應用程序資源中的Images文件夾的內(nèi)容添加到項目的資產(chǎn)目錄Assets.xcassets中摩渺。(原版的電子書有附源碼和相關資源)
每張圖片都有兩種版本:2x和3x。沒有低分辨率的1x設備可以運行最新版本的iOS剂邮。所以沒有必要包含1x張圖片摇幻。
添加一個 nib 文件
?給工程添加一個新文件。在User Interface中選擇Empty模板挥萌。這會產(chǎn)生一個新的空nib绰姻。
?點擊Next并將新文件保存為SearchResultCell。
打開SearchResultCell.xib你會看到一塊空畫布引瀑。
Xib或nib
我一直稱它為nib狂芋,但是文件擴展名是.xib。有什么不同呢?實際上憨栽,這些術語可以互換使用帜矾。從技術上講,xib文件被編譯成nib文件屑柔,并放入應用程序包中屡萤。nib這個術語主要是由于歷史原因而保留下來的——它代表的是NeXT Interface Builder,來自上世紀90年代的舊NeXT平臺掸宛。
您可以認為術語“xib文件”和“nib文件”是等價的死陆。首選項似乎是nib,所以從現(xiàn)在開始我將使用nib旁涤。這不會是計算機術語最后一次混淆翔曲、模糊或不一致迫像。編程的世界充滿了術語劈愚。
?將View as:面板切換到iPhone SE設備。和往常一樣闻妓,我們將為這個設備進行設計菌羽,但是使用自動布局使用戶界面適應更大的設備/屏幕。
從對象庫中由缆,拖拽一個新的表格視圖單元格到畫布上:
?選擇新的表格視圖單元格注祖,并進入尺寸檢查器。在Height字段中鍵入80(而不是行高度Row Height)均唉。確保寬度Width為320是晨,即iPhone SE屏幕的寬度。
cell現(xiàn)在看起來是這樣的:
注意: 有時舔箭,單元格可能有一個藍色邊框罩缴,它與實際單元格的位置略有偏移蚊逢。這是一個接口構建器Interface Builder的bug。如果發(fā)生這種情況箫章,只需切換到其他文件烙荷,然后切換回SearchResultCell.xib——一切應該都會恢復正常。
?將一個 Image View和兩個Label拖拽到cell中檬寂,就像這樣:
注意:如果你像上面那樣在每一項周圍都有藍色矩形——或者想用矩形看到每一項的完整邊界——那么使用 Editor → Canvas → Show Bounds Rectangles 來打開/關閉邊界矩形终抽。
?將圖像視圖定位在X:16, Y:10,Width:60桶至,Height:60昼伴。
?將第一個Label的text設置為Name,字體設置為System 18, X:84, Y:16塞茅,Width設置為220亩码,Height設置為22。
?將第二個Label的text設置為Artist Name野瘦,字體設置為System 15描沟,顏色設置為黑色,透明度opacity設置為50%鞭光,X:84, Y:44吏廉,Width:220,Height:18惰许。
正如您所看到的席覆,編輯nib就像編輯storyboard一樣。區(qū)別在于畫布要小得多汹买,因為你只編輯一個表格視圖單元格佩伤,而不是整個視圖控制器。
Table View Cell本身需要有一個重用標識符identifier晦毙。您可以在屬性檢查器中將其設置為SearchResultCell.
imageView將放置搜索到的藝術作品生巡,比如相冊封面、書籍封面或應用程序圖標见妒。加載這些圖像可能需要幾秒鐘孤荣,因此在此之前,最好顯示占位符圖像须揣。這個占位符是您剛剛添加到項目中的圖像文件的一部分盐股。
?選擇 Image View。在屬性檢查器中耻卡,將Image設置為Placeholder疯汁。
單元格cell的設計現(xiàn)在應該是這樣的:
你還沒做完呢。這款手機的設計只有320點寬卵酪,但也有一些iOS設備的屏幕比這還要寬幌蚊。單元格本身會調整大小以適應那些更大的屏幕秸谢,但Label不會,這可能會導致它們的text被切斷霹肝。你必須添加一些自動布局約束估蹄,讓Label和單元格一起調整大小。
設置自動布局約束
當設置自動布局約束時沫换,最好從一條邊開始——就像從左到右屏幕的左上角一樣臭蚁,但要記住也有可以從右到左的屏幕——然后向左向下移動。當您設置自動布局約束時讯赏,視圖將移動以匹配這些約束垮兑,通過這種方式,您可以確保您設置的每個視圖相對于前一個視圖都是穩(wěn)定的漱挎。
如果你隨機為視圖設置布局約束系枪,你會看到你的視圖到處移動,過一段時間你可能就不記得你最初放置視圖的位置了磕谅。
?選擇ImageView并打開Add New Constraints菜單私爷。取消對邊距的約束,并將ImageView固定在單元格的頂部和左側膊夹。也要限制它的寬度和高度衬浑,使它的大小總是固定在60×60的點上:
?點擊Add 4 Constraints來添加約束。
?選擇Name Label放刨,再次使用Add New Constraints菜單工秩。取消對頁邊距的約束,選擇頂部进统、左側和右側(但不包括底部):
?點擊Add 3 Constraints助币。
?最后,通過添加4個新的約束螟碎,將Artist Name Label分別釘在left眉菱、top、right和bottom——同樣不受邊距的限制抚芦。
這就是這個單元格的設計”睹眨現(xiàn)在你必須告訴應用程序使用這個nib迈螟。
注冊nib文件用于代碼
?打開SearchViewController.swift叉抡,將這些行添加到viewDidLoad()的末尾:
let cellNib = UINib(nibName: "SearchResultCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "SearchResultCell")
UINib類用于加載nib。在這里答毫,您告訴它加載您剛剛創(chuàng)建的nib—注意褥民,您沒有指定.xib文件擴展名。然后洗搂,您要求tableView為重用標識符“SearchResultCell”注冊這個nib消返。
從現(xiàn)在開始载弄,當你為標識符“SearchResultCell”調用dequeueReusableCell(withIdentifier:)時,UITableView會自動從nib中創(chuàng)建一個新的單元格——或者重用一個現(xiàn)有的單元格(如果有的話)撵颊。這就是你需要做的宇攻。
?在tableView(_:cellForRowAt:)中修改這段代碼:
let cellIdentifier = "SearchResultCell"
var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if cell == nil {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
}
最后的方法是這樣的:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath)
if searchResults.count == 0 {
. . .
} else {
. . .
}
return cell
}
你可以用一條語句替換一段代碼。現(xiàn)在倡勇,它幾乎與使用原型單元格完全一樣逞刷,只是您必須創(chuàng)建自己的nib對象,并且需要預先將其注冊到表視圖中妻熊。
注意:dequeueReusableCell(withIdentifier:)的調用現(xiàn)在接受第二個參數(shù)for:夸浅,它接受一個IndexPath值。dequeue方法的這種變體使表視圖更智能一些扔役,但它只在向表視圖注冊了nib或使用原型單元格時才有效帆喇。
運行應用程序并進行(偽)搜索。哎呀亿胸,應用程序崩潰了坯钦。
練習:想想是為什么?
答:因為你做了自己的自定義單元格設計侈玄,你不能使用UITableViewCell的textLabel和detailTextLabel屬性葫笼。
每個表格視圖單元格——甚至是你從nib加載的自定義單元格——都有一些標簽和自己的圖像視圖,但是你應該只在使用標準單元格樣式之一時使用這些:.default拗馒、.subtitle等等路星。如果您在自定義單元格上使用它們,那么這些內(nèi)置標簽就會妨礙您自己的標簽诱桂。
在這種情況下洋丐,您不應該使用textLabel和detailTextLabel將文本放入單元格—您需要為標簽創(chuàng)建自己的屬性。
你把這些屬性放在哪里?當然是在一個新的class里挥等。你將創(chuàng)建一個新的類友绝,名為SearchResultCell,它擴展了UITableViewCell肝劲,并具有屬性和邏輯迁客,用于在這個應用程序中顯示搜索結果。
添加一個自定義UITableVIewCell子類
?使用Cocoa Touch Class模板向項目添加一個新文件辞槐。將它命名為SearchResultCell并使它成為UITableViewCell的子類——請注意類名的更改掷漱,如果在設置名稱之后選擇子類,“Also create XIB file”應取消選中榄檬,因為您已經(jīng)有一個卜范。
這將創(chuàng)建Swift文件,與您之前創(chuàng)建的nib文件一起使用鹿榜。
?打開SearchResultCell.xib海雪,并選擇表視圖單元格Table View Cell——確保選擇的是實際的表視圖單元格對象锦爵,而不是它的內(nèi)容視圖Content View。
?在Identity inspector中奥裸,將它的類從“UITableViewCell”更改為SearchResultCell险掀。
你這樣做是為了告訴nib它包含的頂層視圖對象不再是UITableViewCell而是你自己的SearchResultCell子類。從現(xiàn)在開始湾宙,無論何時調用dequeueReusableCell()迷郑,表視圖都會返回一個SearchResultCell類型的對象。
?將以下outlet屬性添加到SearchResultCell.swift:
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var artistNameLabel: UILabel!
@IBOutlet weak var artworkImageView: UIImageView!
?將這些outlet連接到nib中各自的標簽和圖像視圖上创倔。從SearchResultCell的連接檢查器中最容易做到這一點:
您還可以打開助理編輯器并從標簽和圖像視圖Control-drag到它們各自的outlet定義嗡害。如果您以前使用過nib文件,那么您可能會想要將outlet連接到文件的所有者畦攘,但在本例中這是行不通的;它們必須連接到tableView單元格霸妹。
現(xiàn)在一切都設置好了,您可以告訴SearchViewController使用這些新的SearchResultCell對象知押。
在app中使用自定義表格視圖單元格
?打開SearchViewController.swift叹螟,把cellForRowAt改成:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell",
for: indexPath) as! SearchResultCell
if searchResults.count == 0 {
cell.nameLabel.text = "(Nothing found)"
cell.artistNameLabel.text = ""
} else {
let searchResult = searchResults[indexPath.row]
cell.nameLabel.text = searchResult.name
cell.artistNameLabel.text = searchResult.artistName
}
return cell
}
注意第一行中的變化。之前它返回了一個UITableViewCell對象台盯,但是現(xiàn)在你已經(jīng)在nib中更改了類名罢绽,你保證總是會收到一個SearchResultCell——你只需要用as!轉換它。
給定這個單元格静盅,您可以將搜索結果中的名稱和藝術家名稱放入適當?shù)臉撕炛辛技邸DF(xiàn)在使用的是單元格的nameLabel和artistNameLabel輸出口,而不是textLabel和detailTextLabel蒿叠。你也不需要再寫!來展開明垢,因為outlet是隱式展開的optional。
?運行這個應用程序市咽,應該是這樣的:
還有一些需要改進的地方痊银。注意到您在幾個不同的地方使用了字符串“SearchResultCell”嗎?一般來說,為這種場合創(chuàng)建一個常量會更好施绎。
為表單元格標識符使用常量
假設由于某種原因溯革,您或您的某個同事在某個位置重命名reuse identifier。然后谷醉,您還必須記住其他所有使用標識符“SearchResultCell”的地方并更改它致稀。最好使用符號名將這些更改限制在一個位置。
?將以下內(nèi)容添加到SearchViewController.swift中孤紧,在class定義的某個地方:
struct TableView {
struct CellIdentifiers {
static let searchResultCell = "SearchResultCell"
}
}
這定義了一個新的struct豺裆,TableView拒秘,包含一個二級struct名為cellidentifier号显,它包含一個常量名為searchResultCell臭猜,值為“SearchResultCell”。
如果你想改變這個值押蚤,你只需要在這里更改蔑歌,任何使用TableView.CellIdentifiers.searchResultCell的代碼將自動更新。
使用符號名而不是實際值還有另一個原因:它賦予了額外的含義揽碘。僅僅看到文本“SearchResultCell”顯然不及TableView.CellIdentifiers.searchResultCell更能說明它的預期用途次屠。
注意: 在Swift中,將符號常量作為static let成員放在一個struct(或一系列struct)中是一個常見的技巧雳刺。靜態(tài)static值可以在沒有實例的情況下使用劫灶,所以在使用它之前不需要實例化TableView.cellidentifier——就像您需要使用一個類一樣。
Swift允許在類中放置結構體掖桦,這允許不同類都有自己的TableView.CellIdentifier結構本昏。如果你把結構體放在類的外面,這就行不通了——那么你就會在全局命名空間中有多個同名結構體枪汪,這是不允許的涌穆。
?SearchViewController.swift,用TableView.CellIdentifiers.searchResultCell替換字符串“SearchResultCell”雀久。
例如宿稀,viewDidLoad()現(xiàn)在看起來像這樣:
override func viewDidLoad() {
. . .
let cellNib = UINib(nibName: TableView.CellIdentifiers.searchResultCell, bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: TableView.CellIdentifiers.searchResultCell)
}
另一個變化是tableView(_:cellForRowAt:)。
?運行應用程序赖捌,確保一切正常運行祝沸。
一個新的“No results”單元格
還記得我們的朋友Justin Bieber嗎?尋找他的過程是這樣的:
那不太好看——更不用說有點不好看了。如果你能給它一個自己的樣子會更好越庇。這并不難:你可以為它再做一個nib奋隶。
?給項目添加另一個nib文件。這又是一個空的nib悦荒。命名為NothingFoundCell.xib唯欣。
?將一個新的Table View Cell拖放到畫布上。將它的寬度設置為320搬味,高度設置為80境氢,并給它一個重用標識符“NothingFoundCell”。
?拖拽一個 Label到單元格中,然后給它一個“Nothing Found”的文本寿弱。將文本顏色設置為50%不透明黑色(opaque black)聊替,字體系統(tǒng)設置為15。
?使用Editor → Size to Fit Content 來匹配內(nèi)容寿桨,使標簽Lablel與文本Text完全匹配——你可能需要取消選擇并再次選擇Label來啟用菜單選項。
?將Label居中,使用藍色的導航條將Label準確地對齊到中心亭螟。
它應該是這樣的:
為了讓文本在所有設備上居中挡鞍,你可以使用自動布局對齊菜單(Align menu):
? 選擇 Horizontally in Container 和 Vertically in Container 然后點擊 Add 2 Constraints.
約束條件應該是這樣的:
還有一件事要解決。還記得在willSelectRowAt中预烙,如果沒有搜索結果來阻止行被選中墨微,那么返回nil嗎?如果您持續(xù)的點擊,您仍然可以使行顯示為灰色扁掸,就像它被選中一樣翘县。
出于某種原因,UIKit會繪制選定的背景如果你按下單元格足夠長時間谴分,即使這不算真正的選擇锈麸。為了防止這種情況,您必須告訴單元格不要使用選定的顏色牺蹄。
?選擇單元格本身掐隐。在Attributes inspector中,將Selection設置為None〕伲現(xiàn)在虑省,輕擊或按住“Nothing Found”行將不再顯示任何類型的選擇。
你不需要為這個單元格創(chuàng)建UITableViewCell子類因為沒有文本要修改僧凰,也沒有屬性要設置探颈,你只需要將這個nib注冊到表格視圖中。
?在SearchViewController.swift中為結構體添加了一個新的重用標識符训措。
struct TableView {
struct CellIdentifiers {
static let searchResultCell = "SearchResultCell"
static let nothingFoundCell = "NothingFoundCell" // New
}
}
?將這些行添加到viewDidLoad()中伪节,在其他注冊nib的代碼下面:
cellNib = UINib(nibName: TableView.CellIdentifiers.nothingFoundCell, bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier:TableView.CellIdentifiers.nothingFoundCell)
這還要求您更改let cellNib為var cellNib,因為您正在重用cellNib局部變量绩鸣。
?最后怀大,將tableView(_:cellForRowAt:)改為
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if searchResults.count == 0 {
return tableView.dequeueReusableCell(withIdentifier:
TableView.CellIdentifiers.nothingFoundCell,
for: indexPath)
} else {
let cell = tableView.dequeueReusableCell(withIdentifier:
TableView.CellIdentifiers.searchResultCell,
for: indexPath) as! SearchResultCell
let searchResult = searchResults[indexPath.row]
cell.nameLabel.text = searchResult.name
cell.artistNameLabel.text = searchResult.artistName
return cell
}
}
這里的邏輯已經(jīng)進行了一些調整。只有在實際有任何結果時才生成SearchResultCell呀闻。如果數(shù)組是空的化借,您只需取出標識符為nothingFoundCell的單元格,并返回它捡多,因為沒有為該單元格配置任何東西蓖康。
?運行這個應用程序。現(xiàn)在Justin Bieber的搜索結果是這樣的:
你也可以在更大的屏幕設備上嘗試一下垒手。Label應該始終以單元格為中心蒜焊。
很好,你上次提交工作已經(jīng)有一段時間了科贬,所以現(xiàn)在似乎是保障工作的好時機泳梆。
Source Control的變化
但在提交更改之前,請在編輯器視圖中查看SearchViewController.swift。你可能會注意到沿著排水溝的一些藍線优妙,像這樣:
那些藍線是什么意思?
這實際上是Xcode 10中的一個新特性——那些藍色的行出現(xiàn)在啟用了源代碼控制Source Control的項目中乘综,它們表示開發(fā)人員自上次提交以來所做的更改。
但除此之外,如果你與其他開發(fā)人員一起工作,而其他人正好更改了你正在工作的文件并提交了他們的修改到Git, Xcode甚至會顯示這些pending changes,這樣你會意識到有人進行了可能會影響你工作的修改鳞溉。很方便!
?將更改提交到存儲庫瘾带。我使用備注“為搜索結果使用自定義單元格”鼠哥。
2. 改變應用程序的外觀
我寫這篇文章的時候熟菲,外面灰蒙蒙的,下著雨朴恳。這個App看起來也相當灰色和沉悶抄罕。讓我們用更鮮艷的顏色讓它看起來更活潑一點。
?將以下方法添加到AppDelegate.swift:
// MARK:- Helper Methods
func customizeAppearance() {
let barTintColor = UIColor(red: 20/255, green: 160/255, blue: 160/255, alpha: 1)
UISearchBar.appearance().barTintColor = barTintColor
window!.tintColor = UIColor(red: 10/255, green: 80/255, blue: 80/255, alpha: 1)
}
這改變了UISearchBar的外觀——事實上于颖,它改變了app中的所有搜索欄呆贿。
UIColor(red:green:blue:alpha:)方法根據(jù)您指定的RGB和alpha color組件創(chuàng)建一個新的UIColor對象。
許多繪圖程序允許您選擇RGB值森渐,范圍從0到255做入,所以這是許多程序員習慣于考慮的顏色值范圍。然而同衣,UIColor初始化器接受0.0到1.0之間的值竟块,因此必須將這些數(shù)字除以255才能將它們縮小到這個范圍。
?在application(_:didFinishLaunchingWithOptions:)調用這個新方法:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
customizeAppearance() // Add this line
return true
}
運行應用程序并注意區(qū)別:
搜索欄是藍綠色的耐齐,但仍然是半透明的±嗣兀現(xiàn)在的整體色調是深綠色,而不是默認的藍色——你目前只能在文本框的光標上看到色調埠况,但稍后會變得更加明顯耸携。
App Delegate的角色
可憐的AppDelegate經(jīng)常被濫用。人們賦予它太多的責任辕翰。實際上夺衍,App Delegate沒有太多要做的事情。
它獲取關于應用程序狀態(tài)的許多回調——例如喜命,應用程序是否即將關閉——處理這些事件應該是它的主要職責刷后。App Delegate還擁有主窗口(main window)和頂層視圖控制器(top-level view controller )。除此之外渊抄,它不應該做太多尝胆。
一些開發(fā)人員使用App Delegate作為他們的數(shù)據(jù)模型,那是個糟糕的設計护桦。你真的應該有一個或好幾個單獨的類來作為數(shù)據(jù)模型含衔。
另一些人讓App Delegate作為他們的主控制中心。又錯了!應該把這些放到頂層視圖控制器中。
如果你曾經(jīng)在某人的源代碼中看到以下類型的東西贪染,這是一個很好的跡象缓呛,表明應用程序委托正在被錯誤地使用:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.someProperty = . . .
當一個對象想從app委托中獲取一些東西時,就會發(fā)生這種情況杭隙。它能工作哟绊,但不是好的架構。
在我看來痰憎,用另一種方式來設計代碼更好:app委托可能會進行一定數(shù)量的初始化票髓,但隨后它會將任何數(shù)據(jù)模型對象交給根視圖控制器(root view controller),并移交控制權铣耘。根視圖控制器將這些數(shù)據(jù)模型對象傳遞給任何需要它們的控制器洽沟,以此類推。
這也被稱為依賴注入(dependency injection)蜗细。我在MyLocations應用程序(Section 3 的App)的“傳遞上下文(Pass the context)”小節(jié)的第27章中描述了這一原則裆操。
更改行選擇顏色
目前,點擊一行用灰色突出顯示炉媒。這和茶色的主題不太協(xié)調踪区。所以,你會給行選擇同樣的藍綠色吊骤。
這很簡單缎岗,因為所有表格視圖單元格都有一個selectedBackgroundView屬性。當單元格被選中時水援,來自該屬性的視圖被放置在單元格背景的頂部密强,但低于其他內(nèi)容。
?將以下代碼添加到SearchResultCell.swift中的awakeFromNib()中:
override func awakeFromNib() {
super.awakeFromNib()
// New code below
let selectedView = UIView(frame: CGRect.zero)
selectedView.backgroundColor = UIColor(red: 20/255, green: 160/255, blue: 160/255, alpha: 0.5)
selectedBackgroundView = selectedView
}
awakeFromNib()方法在單元格對象從nib加載之后調用蜗元,但是在單元格添加到表視圖之前調用或渤。您可以使用此方法做額外的工作來準備要使用的對象。這對于創(chuàng)建帶有選擇顏色的視圖來說是完美的奕扣。
為什么不在init方法中這樣做薪鹦,比如init?(編碼器)?公平地說,在這種情況下你可以惯豆。但值得注意的是池磁,awakeFromNib()是在init?(編碼器)之后的某個時候調用的,而且也是在nib中的對象連接到它們的輸出口outlet之后調用的楷兽。
例如地熄,在init?(coder)中,nameLabel和artistNameLabel輸出口仍然是nil芯杀,但在awakeFromNib()中端考,它們將正確地連接到它們的UILabel對象雅潭。所以,如果你想在代碼中對這些輸出口做些什么却特,你需要在awakeFromNib()中做扶供,而不是在init?(coder)中。
這就是為什么awakeFromNib()是這類東西的理想位置——它類似于在視圖控制器中使用viewDidLoad()裂明。
不要忘記首先調用super.awakeFromNib()——這是必需的椿浓。如果您忘記了,那么超類UITableViewCell——或任何其他超類——可能沒有機會初始化它們自己闽晦。
提示:在重寫的方法中調用super.methodName(…)總是一個好主意扳碍,比如viewDidLoad()、viewWillAppear()尼荆、awakeFromNib()等等左腔,除非文檔中另有說明唧垦。
當你運行這個應用程序捅儒,搜索并點擊一行,它應該是這樣的:
添加App圖標
當你進行到這里振亮,你可能想給這個App一個圖標巧还。
?打開資產(chǎn)目錄(Assets.xcassets)并選擇AppIcon組。
?將圖片從資源文件夾的Icon文件夾拖拽到匹配的位置坊秸。
記住麸祷,對于2x插槽,您需要使用兩倍大小(以像素為單位)的圖像褒搔。例如阶牍,將Icon-152.png文件拖放到iPad應用程序76pt, 2x中。對于3x星瘾,你需要將圖像大小乘以3走孽。”
運行應用程序琳状,注意它現(xiàn)在有了一個漂亮的新圖標:
app啟動顯示鍵盤
我想做的最后一個用戶界面調整是磕瓷,當你啟動應用程序時,鍵盤應該立即可見念逞,這樣用戶就可以立即開始打字困食。
?在SearchViewController.swift中給viewDidLoad()添加以下代碼:
searchBar.becomeFirstResponder ()
正如您從Checklists應用程序(Section 2中的App)中了解到的,becomeFirstResponder()將為searchBar提供“第一響應”并顯示鍵盤翎承。你輸入的任何東西都會出現(xiàn)在搜索欄里硕盹。
?試一試,commit你的改變叨咖。你重新設計了搜索欄瘩例,還添加了應用圖標待讳。
3. commit標簽:
如果您查看到目前為止所做的各種提交,您將注意到一串奇怪的數(shù)字仰剿,比如“bb55701”:
這些都是Git用來唯一標識提交的內(nèi)部數(shù)字(稱為散列)创淡。對于我們?nèi)祟悂碚f,這樣的數(shù)字不是很好記南吮,也不是很有用琳彩,所以Git還允許您用更友好的標簽“標記”某個提交。
用Xcode給提交打上標簽就像在Source Control navigator視圖中選擇commit一樣簡單部凑,右鍵點擊獲得上下文菜單并選擇Tag選項露乏。
輸入“v0.1”作為標簽Tag,并提供一條描述該標簽所包含內(nèi)容的可選信息涂邀。然后單擊Create創(chuàng)建標簽瘟仿。
您可以在源代碼控制導航器視圖中看到新標記:
Xcode在Git上運行得很好,但是您可能需要更強大的功能來執(zhí)行復雜的Git操作比勉。如果你這樣做了劳较,你可能需要學習如何使用終端,或者使用SourceTree這樣的工具浩聋,這在Mac應用商店是免費的观蜗。
4. 調試器
Xcode有一個內(nèi)置的調試器。不幸的是衣洁,調試器并不能把錯誤從程序中清除出去;它只是讓它們以慢鏡頭碰撞墓捻,這樣你就能更好地了解出了什么問題。
就像偵探一樣坊夫,調試器允許您在造成損害之后挖掘證據(jù)砖第,以便找到造成損害的歹徒。多虧了調試器环凿,您不會在黑暗中跌倒卻不知道發(fā)生了什么梧兼。相反,你可以用它來快速查明哪里出了問題拷邢。一旦你知道了這兩件事袱院,弄清楚為什么會出錯就容易多了。
索引超出范圍bug
讓我們在應用程序中引入一個bug瞭稼,這樣它就會崩潰——知道當應用程序崩潰時該做什么是非常重要的忽洛。
?更改SearchViewController.swift的numberOfRowsInSection方法:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if !hasSearched {
. . .
} else if searchResults.count == 0 {
. . .
} else {
return searchResults.count + 1 // This line changes
}
}
在運行應用程序并搜索一些內(nèi)容。應用程序崩潰环肘,Xcode窗口變成這樣:
崩潰是:Thread 1: Fatal error: Index out of range. (線程1:致命錯誤:索引超出范圍)欲虚。聽起來非常急!
根據(jù)錯誤消息,用于訪問某個數(shù)組的索引大于數(shù)組中項的數(shù)量悔雹。換句話說复哆,該指數(shù)“超出了范圍”欣喧。這是數(shù)組中常見的錯誤,在您的編程生涯中梯找,您可能不止一次地犯這種錯誤唆阿。
現(xiàn)在你知道哪里出了問題,最大的問題是:哪里出了問題?您的應用程序中可能有許多對array[index]的調用锈锤,您不希望必須遍歷整個代碼才能找到罪魁禍首驯鳖。
幸運的是,您有調試器來幫助您久免。在源代碼編輯器中浅辙,它已經(jīng)指出了有問題的一行:
重要的是:這條線不一定是崩潰的原因——畢竟,您沒有在這個方法中更改任何東西——但它是崩潰發(fā)生的地方阎姥。從這里你可以追溯原因记舆。
數(shù)組是searchResults,索引由indexPath.row給出呼巴。如果能深入了解行號就好了泽腮,有幾種方法可以做到這一點。
我們將在這里看到的是使用調試器的命令行界面伊磺,就像電影中的黑客神童一樣:]
?在Xcode控制臺盛正,在(lldb)提示符之后删咱,輸入p indexPath.row并按回車鍵:
輸出應該是這樣的:
(Int) $R1 = 3
這意味著indexPath.row的值是3屑埋,類型是Int (你可以忽略$R1)。
我們也來看看數(shù)組中有多少項痰滋。
鍵入p searchResults并按enter鍵摘能。如果您使用自動完成功能,請注意searchResult和searchResults都是選項敲街,末尾沒有“s”团搞。一定要選對。
輸出顯示了一個包含三個項的數(shù)組多艇。
現(xiàn)在您可以對這個問題進行推理了:table視圖正在請求第4行(即索引3處的單元格)的單元格逻恐,但是數(shù)據(jù)模型中只有3行(第0行到第2行)。
table視圖知道從numberOfRowsInSection返回的值中有多少行峻黍,所以可能這個方法返回的行數(shù)是錯誤的?
當然复隆,這確實是原因所在,因為您故意在那個方法中引入了錯誤姆涩。
我希望這說明了你應該如何處理崩潰:首先找出崩潰發(fā)生的地方挽拂,找出真正的錯誤是什么,然后向后推理骨饿,直到找到原因亏栈。
Storyboard outlet bug
?將numberOfRowsInSection恢復到之前的狀態(tài)台腥,然后向SearchViewController.swift添加一個新的outlet屬性:
@IBOutlet weak var searchBar2: UISearchBar!
?打開storyboard并從SearchViewController control -拖動到搜索欄Search Bar。從彈出框中選擇searchBar2绒北。
現(xiàn)在黎侈,搜索欄也連接到這個新的searchBar2 outlet——對于一個對象一次連接到多個outlet是完全沒問題的。
?在源代碼中刪除SearchViewController.swift中的searchBar2 outlet屬性闷游,而不是在storyboard中蜓竹。
這對我來說是一個制造另一次崩潰的卑鄙伎倆。storyboard包含到不再存在的屬性的連接储藐。如果您認為這是一個復雜的示例俱济,那么等到您在自己的某個應用程序中犯了這個錯誤時再做決定。這比你想象的要頻繁得多!
運行應用程序钙勃,它會立即崩潰蛛碌。崩潰是“Thread 1: signal SIGABRT”(線程1:信號SIGABRT)。
在Debug窗格中向上滾動Xcode控制臺輸出辖源,應該會看到:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<StoreSearch.SearchViewController 0x7fb83ec09bf0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar2.'
*** First throw call stack:
(
0 CoreFoundation 0x0000000111da1c7b __exceptionPreprocess + 171
. . .
這條信息的第一部分非常重要:它告訴你這個應用程序因為一個“NSUnknownKeyException”而終止蔚携。在一些平臺上,異常是一種常用的錯誤處理機制克饶,但在iOS上酝蜒,這總是一個致命的錯誤,應用程序被迫停止矾湃。
應該引起你興趣的是:
this class is not key value coding-compliant for the key searchBar2
嗯亡脑,這有點神秘。它確實提到了searchBar2邀跃,但是“key value-coding compliant”(鍵值編碼兼容)是什么意思呢?這種錯誤我已經(jīng)見過很多次了霉咨,所以知道哪里出了問題,但是如果您是新手拍屑,那么這樣的信息就不會很有啟發(fā)性途戒。
讓我們看看Xcode認為崩潰發(fā)生在哪里:
這也不是很有用。Xcode說應用程序在AppDelegate中崩潰了僵驰,但這不是真的喷斋。
Xcode遍歷調用堆棧,直到找到一個它擁有源代碼的方法蒜茴,也就是它所顯示的方法星爪。調用堆棧是最近調用的方法列表。你可以在調試器窗口的左邊看到它矮男。
?點擊Debug導航器底部最左邊的圖標來查看更多信息移必。
頂部的方法是最后一個被調用的方法——它實際上是一個函數(shù),而不是方法毡鉴。它從pthread_kill調用崔泵,它從abort調用秒赤,它從abort_message調用,等等憎瘸,一直到main函數(shù)入篮,這是應用程序的入口點也是應用程序啟動時調用的第一個函數(shù)。
這個調用堆棧中列出的所有方法和函數(shù)都來自系統(tǒng)庫幌甘,這就是為什么它們是灰色的潮售。如果你點擊其中一個,你會得到一堆難以理解的匯編代碼:
很明顯锅风,這種方法不會給你帶來任何好處酥诽。不過,您還可以嘗試另一件事——設置異常斷點(Exception Breakpoint)皱埠。
斷點是代碼中的一個特殊標記肮帐,它將暫停應用程序的執(zhí)行并啟動調試器。
當您的應用程序遇到斷點時边器,應用程序將在該斷點處暫停训枢。然后,您可以使用調試器逐行逐行地遍歷代碼忘巧,以便以慢動作運行它恒界。如果你真的弄不明白為什么有些東西會崩潰,這是一個很方便的工具砚嘴。
您不會在本書中逐步了解代碼十酣,但是您可以在蘋果開發(fā)人員支持站點的調試部分閱讀更多相關內(nèi)容≡婀或者婆誓,您可以在Xcode的Help→Xcode Help菜單選項下檢查您的應用程序調試主題。
您將設置一個特殊的斷點也颤,它將在發(fā)生致命異常時觸發(fā)。這將使程序在即將崩潰時停止運行郁轻,這會讓你對正在發(fā)生的事情有更多的了解翅娶。
?切換到斷點導航器(Breakpoint navigator),點擊底部的+按鈕添加一個異常斷點(Exception Breakpoint):
這將添加一個新的斷點:
?現(xiàn)在再次運行應用程序好唯。它仍然會崩潰竭沫,但Xcode顯示了更多的信息:
現(xiàn)在調用堆棧中有更多的方法。讓我們看看能不能找到一些線索來解釋到底發(fā)生了什么骑篙。
引起我注意的是對[UIViewController _loadViewFromNibNamed:bundle:]的調用蜕提。這是在加載nib文件或本例中的故事板時發(fā)生此錯誤的一個很好的提示。
使用這些提示和線索靶端,以及您在沒有異常斷點的情況下得到的有點神秘的錯誤消息谎势,您通沉莞啵可以找出是什么原因導致應用程序崩潰。
在本例中脏榆,我們已經(jīng)確定應用程序在加載storyboard時崩潰猖毫,錯誤消息提到“searchBar2”。把兩者結合起來须喂,你就得到了答案吁断。
在源代碼中快速瀏覽一下就可以確認searchBar2 outlet不再存在于視圖控制器中,但是storyboard仍然引用它坞生。
?打開storyboard仔役,在連接檢查器中斷開搜索視圖控制器與searchBar2的連接,以修復崩潰是己。那是另一個被解決的bug!
注意:啟用異常斷點意味著如果應用程序崩潰骂因,您將不再在控制臺中獲得有用的錯誤消息—斷點將在異常發(fā)生之前停止應用程序。
如果在稍后的開發(fā)過程中赃泡,您的應用程序在另一個bug上崩潰寒波,您可能希望禁用此斷點以實際查看錯誤消息腻暮。您可以通過簡單地選擇斷點并單擊深藍色箭頭槽地,在斷點導航器中實現(xiàn)這一點捕虽。如果箭從深藍色變成淡藍色戳晌,它就會失效瓣铣。
總結:
如果你的應用程序在運行Xcode的時候崩潰了班眯,Xcode調試器經(jīng)常會顯示一條錯誤消息纵穿,以及崩潰發(fā)生在代碼的什么地方特幔。
如果Xcode認為崩潰發(fā)生在AppDelegate上——這不是很有用!——添加一個異常斷點(Exception Breakpoint)以獲取更多信息蓖柔。
如果應用程序是SIGABRT崩潰辰企,但控制臺中沒有錯誤消息,則禁用可能存在的任何異常斷點况鸣,使應用程序再次崩潰牢贸。或者镐捧,從“調試器”工具欄中多次單擊“繼續(xù)執(zhí)行程序(Continue program execution)”按鈕潜索。最終這也將顯示錯誤信息。
EXC_BAD_ACCESS錯誤通常意味著內(nèi)存管理出了問題懂酱。一個對象可能被“釋放”過多次竹习,或者“保留”得不夠。對于Swift列牺,這些問題基本上都是過去的事了整陌,因為編譯器通常會確保做正確的事情。但是,如果您在與Objective-C代碼或低級api對話泌辫,仍然有可能出錯随夸。
EXC_BREAKPOINT不是錯誤。應用程序在斷點處停止甥郑,藍色箭頭指向應用程序暫停的行逃魄。您可以設置斷點來在代碼中的特定位置暫停應用程序,以便在調試器中檢查應用程序的狀態(tài)澜搅∥榉“繼續(xù)程序執(zhí)行”按鈕恢復應用程序。
這應該能幫助你弄清大多數(shù)App崩潰的真相!
build日志
如果您想知道Xcode在構建應用程序時實際做了什么勉躺,那么請查看報表導航器(Report navigator)癌瘾。它是navigator窗格中的最后一個選項卡。
報表導航器跟蹤您的構建和調試會話饵溅,以便您可以回顧發(fā)生了什么妨退。它甚至能記住應用程序前幾次運行的調試輸出。
確保選中 All Messages蜕企。要獲取關于特定日志項的更多信息咬荷,請選擇該項并單擊右側出現(xiàn)的小細節(jié)圖標。這一行將展開轻掩,您將確切地看到執(zhí)行了哪些命令Xcode以及結果是什么幸乒。
如果您遇到一些奇怪的編譯問題,那么這里就是進行故障排除的地方唇牧。此外罕扎,不時看看Xcode的動向也很有趣。