iOS apprentice中文版 - Chapter 33:自定義 Table Cells

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ù)臉撕炛辛技邸DF(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的動向也很有趣。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丐重,一起剝皮案震驚了整個濱河市腔召,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扮惦,老刑警劉巖臀蛛,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異径缅,居然都是意外死亡掺栅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門纳猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桃笙,你說我怎么就攤上這事氏堤。” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵鼠锈,是天一觀的道長闪檬。 經(jīng)常有香客問我,道長购笆,這世上最難降的妖魔是什么粗悯? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮同欠,結果婚禮上样傍,老公的妹妹穿的比我還像新娘。我一直安慰自己铺遂,他們只是感情好衫哥,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著襟锐,像睡著了一般撤逢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粮坞,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天蚊荣,我揣著相機與錄音,去河邊找鬼莫杈。 笑死互例,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的姓迅。 我是一名探鬼主播敲霍,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丁存!你這毒婦竟也來了肩杈?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤解寝,失蹤者是張志新(化名)和其女友劉穎扩然,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聋伦,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡夫偶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了觉增。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兵拢。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逾礁,靈堂內(nèi)的尸體忽然破棺而出说铃,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布腻扇,位于F島的核電站债热,受9級特大地震影響,放射性物質發(fā)生泄漏幼苛。R本人自食惡果不足惜窒篱,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舶沿。 院中可真熱鬧墙杯,春花似錦、人聲如沸暑椰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽一汽。三九已至避消,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間召夹,已是汗流浹背岩喷。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留监憎,地道東北人纱意。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鲸阔,于是被迫代替她去往敵國和親偷霉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359