iOS apprentice中文版 - Section 4: Store Search

譯者語:目前正在學習iOS apprentice 第四部分的內(nèi)容:Store Search咙鞍,因之前跟隨學習的博主曾翻譯到第三部分的內(nèi)容時便斷更了,且目前版本使用的方法較之前已有多處改動,決定自行翻譯原書iOS12 swift 4.2版本的第四個部分,并在此做下記錄方便日后查閱復(fù)習。 _(:」∠) _

Chapter 32:Search Bar

移動應(yīng)用程序最常見的任務(wù)之一是與互聯(lián)網(wǎng)上的服務(wù)器對話——如果你在編寫移動應(yīng)用程序,你需要知道如何上傳和下載數(shù)據(jù)焙蹭。
通過這個名為StoreSearch的新應(yīng)用程序,您將了解如何對web服務(wù)執(zhí)行HTTP GET請求嫂伞,如何解析JSON數(shù)據(jù)壳嚎,以及如何下載圖像等文件。

你將創(chuàng)建一個可以讓你搜索iTunes商店的應(yīng)用程序末早。當然烟馅,你的iPhone已經(jīng)有了相應(yīng)的應(yīng)用程序——“App Store”和“Apple Music”等,但再寫一個又有什么害處呢?
蘋果公司推出了一項網(wǎng)絡(luò)服務(wù)然磷,可以搜索整個iTunes商店郑趁,你可以用它來學習網(wǎng)絡(luò)。

完成后的應(yīng)用程序?qū)⑹沁@樣的:

您將向老朋友table view添加搜索功能姿搜。當您點擊表格中的一個項目時寡润,會彈出一個帶有額外信息的動畫窗口。當你將iPhone翻轉(zhuǎn)到橫屏模式時舅柜,應(yīng)用程序的布局會完全改變梭纹,以一種不同的方式顯示搜索結(jié)果。
這款應(yīng)用還將推出iPad版致份,并為iPad定制用戶界面:


StoreSearch填補并補充了您從以前開發(fā)的應(yīng)用程序中獲得的知識变抽。您還將學習如何將應(yīng)用程序分發(fā)給beta測試人員,以及如何將其提交到應(yīng)用程序商店。
在本章中绍载,您將做以下工作:

1.創(chuàng)建項目:

為您的新應(yīng)用程序創(chuàng)建一個新項目诡宗。使用Git設(shè)置版本控制。

2.創(chuàng)建UI:

為StoreSearch創(chuàng)建用戶界面击儡。

3.執(zhí)行偽搜索:

通過獲取搜索項并使用偽搜索結(jié)果填充表視圖塔沃,了解搜索欄的工作原理。

4.創(chuàng)建數(shù)據(jù)模型:

創(chuàng)建一個數(shù)據(jù)模型來保存搜索結(jié)果的數(shù)據(jù)阳谍,并允許將來進行擴展蛀柴。

5.沒有找到數(shù)據(jù):

在進行搜索時處理“沒有數(shù)據(jù)”的情況。


1.創(chuàng)建項目

啟動Xcode并創(chuàng)建一個新項目矫夯。選擇Single View App模板鸽疾,填寫選項如下:

Product Name: StoreSearch
Team: Default value
Organization Name: your name
Organization Identifier: com.yourname
Language: Swift
Use Core Data, Include Unit Tests, Include UI Tests: leave these unchecked

當您保存項目Xcode時,您可以選擇創(chuàng)建一個Git存儲庫茧痒。到目前為止,您忽略了這個選項融蹂,但現(xiàn)在應(yīng)該啟用它:

如果你沒有看到這個選項旺订,點擊對話框左下角的Options按鈕。

Git和版本控制

Git是一個版本控制系統(tǒng)——它允許您對您的工作進行快照超燃,這樣您就可以隨時回頭查看對項目所做更改的歷史記錄区拳。更好的是,Git這樣的工具允許您在同一代碼庫上與多人協(xié)作意乓。
想象一下樱调,如果兩個程序員同時更改同一個源文件,將會是多么混亂届良。您的更改可能會被同事意外覆蓋笆凌。我曾經(jīng)在做一份工作時不得不在大廳里對另一個程序員大喊:“你在使用文件X嗎?”這樣來避免我們破壞彼此的工作。

有了Git這樣的版本控制系統(tǒng)士葫,每個程序員都可以獨立地處理相同的文件乞而,而不用擔心撤銷其他程序員的工作。Git非常聰明慢显,可以自動合并所有更改爪模,如果有沖突的編輯,可以手動解決荚藻。
Git不是唯一的版本控制系統(tǒng)屋灌,但它是iOS中最流行的。許多iOS開發(fā)人員在GitHub (github.com)上共享他們的源代碼应狱,GitHub是一個使用Git作為引擎的免費協(xié)作網(wǎng)站共郭。另一個流行的系統(tǒng)是Subversion,通常縮寫為SVN落塑。Xcode內(nèi)置了對Git的支持纽疟,雖然它在過去的版本中支持Subversion,但自從Xcode 10之后就不再是這樣了憾赁。

對于StoreSearch污朽,您將使用一些基本的Git功能。即使你是一個人工作龙考,也不用擔心其他程序員把你的代碼弄亂蟆肆,使用它仍然是有意義的。畢竟晦款,可能是你把自己的代碼搞砸了炎功。而使用Git,您總是有辦法回到原來的代碼中——并且是能夠正常工作的代碼版本缓溅。

第一個屏幕

StoreSearch中的第一個屏幕是有一個帶有搜索欄的表視圖——讓我們?yōu)樵撈聊粍?chuàng)建視圖控制器蛇损。
在項目導航器中,選擇ViewController.swift坛怪,將光標移到ViewController的類名上淤齐,右鍵單擊以顯示上下文菜單。從菜單中選擇Refactor→Rename…并將類(以及相關(guān)文件和sb引用)重命名為SearchViewController袜匿。

注意:有時候更啄,Refactor會做所有正確的事情,除了正確地重命名文件居灯。如果發(fā)生這種情況祭务,您將在項目導航器中看到紅色的新文件名,因為Xcode期望新文件但該文件實際上仍然是舊文件名怪嫌。如果發(fā)生這種情況义锥,只需通過Finder進入項目文件夾,手動重命名文件岩灭。

運行應(yīng)用程序以確保一切正常缨该。您應(yīng)該會看到一個白色屏幕,頂部有狀態(tài)欄川背。

注意贰拿,項目導航器現(xiàn)在在列表中的一些文件名旁邊顯示了M和R圖標:

如果您沒有看到這些圖標,那么從Xcode菜單欄中選擇Source Control→Fetch and Refresh Status選項熄云。如果這給出了一個錯誤消息或仍然不能工作膨更,只需重新啟動Xcode。一般來說缴允,這是一個很好的提示:如果Xcode行為怪異荚守,請重新啟動它珍德。
M表示自上次提交以來文件已被修改,R表示該文件已重命名矗漾。

那么什么是commit呢?

當您使用Git這樣的版本控制系統(tǒng)時锈候,應(yīng)該經(jīng)常創(chuàng)建快照。通常情況下敞贡,你會在你的應(yīng)用程序中添加了一個新功能之后泵琳,或者當你修復(fù)了一個bug之后,或者當你覺得你已經(jīng)做出了想要保留的更改時才這樣做誊役。
這就是所謂的commit获列。

Git 版本控制

創(chuàng)建項目時,Xcode進行了初始提交蛔垢。您可以在項目歷史記錄窗口中看到击孩。
?從navigator窗格中選擇Source Control navigator,然后點擊項目根(頂部的藍色文件夾圖標)查看項目歷史:


你可能會得到一個彈出窗口鹏漆,請求訪問你的聯(lián)系人巩梢。這允許Xcode將聯(lián)系人信息添加到提交歷史記錄中的名稱中。如果您正在與其他開發(fā)人員協(xié)作艺玲,這將非常有用括蝠。你之后可以在System Preferences的 Security & Privacy 中修改它。

注意:您的Git歷史記錄可能與我的不一樣板驳,因為我的歷史記錄也顯示了一個名為ch-32的分支又跛。分支是一種Git機制碍拆,用于沿著不同的路徑處理相同的代碼基若治。在后面的章節(jié)中,您將了解更多關(guān)于Git分支的知識「谢欤現(xiàn)在端幼,只要忽略你在截圖中看到的ch-32分支就可以了,你要知道弧满,如果你沒有其他分支婆跑,也沒關(guān)系——你不應(yīng)該這樣做:

讓我們來提交你剛剛做出的改變。從“Source Control”菜單中庭呜,選擇“Commit……


這將打開一個新窗口滑进,詳細顯示您所做的更改。這是快速檢查代碼更改的好時機募谎,只是為了確保您沒有提交任何您不打算提交的內(nèi)容:



在底部的文本框中寫一個簡短但明確的提交理由總是一個好主意扶关。在這里有一個好的描述將幫助您以后在您的項目歷史中找到特定的提交。

將 “ViewController重命名為SearchViewController” 作為提交消息数冬。

按下提交3個文件的按鈕节槐。您將看到,在項目導航器中,M和R圖標不見了——至少在您進行下一個更改之前是這樣铜异。
源代碼控制導航器現(xiàn)在應(yīng)該顯示兩次提交哥倔。如果沒有,單擊列表中的另一個分支揍庄,然后再次單擊根文件夾咆蒿。


如果雙擊某個特定的提交,Xcode將顯示該提交的更改币绩。你將會定期提交并且在本書的最后蜡秽,你會成為這方面的專家:]


2.創(chuàng)建UI

StoreSearch還沒有太大的進展。在本節(jié)中缆镣,您將構(gòu)建這樣的UI—在表視圖上的搜索欄:



即使這個屏幕使用熟悉的表視圖芽突,它也不是一個表視圖控制器,而是一個常規(guī)的UIViewController——如果你不確定的話董瞻,檢查SearchViewController.swift中的類定義寞蚌。

你不需要使用UITableViewController作為視圖控制器的基類因為你的UI中有一個表格視圖。對于這個應(yīng)用程序钠糊,我將向您展示如何實現(xiàn)挟秤。

UITableViewController vs. UIViewController

那么,TableViewController和ViewController之間到底有什么區(qū)別呢?

首先抄伍,UITableViewController是UIViewController的子類艘刚,它能做ViewController能做的一切。不過截珍,它經(jīng)過了優(yōu)化攀甚,適合與表視圖table View一起使用,并且具有一些很酷的額外功能岗喉。

例如:當表格cell包含text field時秋度,單擊該文本字段將彈出屏幕鍵盤。UITableViewController會自動滾動單元格钱床,讓你能看到你在輸入什么荚斯。

你不能用一個普通的UIViewController免費獲得那個行為——如果你想要那個特性,你必須自己編程查牌。

UITableViewController確實有一個很大的限制:它的主視圖必須是一個UITableView事期,它占據(jù)了整個屏幕空間,除了頂部的導航欄和底部的工具欄或標簽欄纸颜。
如果你的屏幕只包含一個UITableView兽泣,讓它成為UITableViewController是有意義的。但如果你想要有其他視圖懂衩,更基本的UIViewController是你要做的選項撞叨。

這就是你不在這個app中使用UITableViewController的原因金踪。除了表格視圖,app還有另一個視圖牵敷,UISearchBar胡岔。你可以把搜索欄searchBar放在tableView中作為一個特殊的頭視圖,或者把searchBar作為導航欄navigation bar的一部分枷餐,但是對于這個應(yīng)用程序靶瘸,你會把它放在tableView的上方。

設(shè)置 storyboard

?打開storyboard并使用View as: panel切換到iPhone SE毛肋。你在這里選擇什么型號的iPhone并不重要怨咪,但iPhone SE讓你更容易跟隨這本書∪蟪祝”
?將一個新的tableView——而不是tableViewController——拖拽到現(xiàn)有的視圖控制器中诗眨。
?讓tableView和主視圖一樣大(320 * 568),然后使用底部的Add New Constraints菜單將tableView緊貼到屏幕邊緣:



還記得這是怎么回事嗎?這個應(yīng)用程序使用Auto Layout孕讳,您已經(jīng)在以前的應(yīng)用程序中使用過匠楚。通過Auto Layout,你可以創(chuàng)建一些約束條件來決定視圖的大小以及它們在屏幕上的位置厂财。

?首先芋簿,如果勾選了Constrain to margins(約束到頁邊距),取消它璃饱。每個屏幕的左右兩側(cè)都有16個點的空白与斤,但是您可以更改它們的大小。當“約束到頁邊距”被啟用時荚恶,您將對這些頁邊距進行固定撩穿。這里不行;你想把tableView固定在屏幕邊緣裆甩。
?在Spacing to nearest neighbor中冗锁,選擇紅色的“工”構(gòu)成四個約束齐唆,分別在tableView的每一邊嗤栓。將間距值保持為0。

這將tableView固定到它的父視圖的邊緣」坑剩現(xiàn)在茉帅,無論設(shè)備屏幕的大小,表格總是會填滿整個屏幕锭弊。

?點擊Add 4 Constraints按鈕完成堪澎。

如果您成功了,那么現(xiàn)在表格視圖周圍應(yīng)該有四個藍色的條框味滞,每個約束一個樱蛤。在文件大綱中還應(yīng)該有一個新的約束部分钮呀。


從對象庫中拖拽一個Search Bar到視圖中——注意選擇搜索欄"Search Bar",而不是搜索欄和搜索顯示控制器 "Search Bar and Search Display Controller"昨凡。將它放在Y = 20處爽醋,這樣它就位于狀態(tài)欄下。
確保searchBar沒有放在表視圖中便脊。它應(yīng)該與文檔大綱中的tableView位于同一層:


如果你確實把搜索欄放到了表格視圖中蚂四,你可以在文檔大綱中找到它,并把它拖到tableView下面哪痰。

?將searchBar固定在頂部遂赠、左側(cè)和右側(cè)邊緣——總共有3個約束。



你不需要固定searchBar的底部或者給它一個高度限制晌杰。searchBar的固有高度為44points跷睦。

?在searchBar的屬性檢查器Attributes inspector中,將占位符文本Placeholder更改為“App name, artist, song, album, e-book”肋演。

視圖控制器的設(shè)計應(yīng)該是這樣的:


連接到outlets

你知道接下來會發(fā)生什么:將searchBar和tableView連接到視圖控制器上的outlet送讲。
?將以下outlets添加到SearchViewController.swift:

@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!

回憶一下,一旦一個對象不再有任何強引用惋啃,它就會消失——它被釋放——而且任何對它的弱引用都會變成nil哼鬓。
根據(jù)蘋果的建議,你已經(jīng)使你的outlets為weak边灭。你可能會想异希,如果對這些視圖對象的引用是weak,那么對象不會很快被釋放嗎?

練習:是什么阻止這些視圖被釋放?

回答:視圖總是視圖層次結(jié)構(gòu)的一部分绒瘦,它們總是有一個具有Strong引用的所有者——它們的父視圖称簿。

SearchViewController的主視圖對象包含對searchBar和tableView的引用。這是在UIKit中完成的惰帽,你不用擔心憨降。只要視圖控制器存在,這兩個outlet也會存在该酗。

?切換回storyboard授药,將searchBar和tableView連接到它們各自的outlet——從視圖控制器control-拖動到你想連接的對象。

Table View 內(nèi)容集

如果你現(xiàn)在運行這個應(yīng)用程序呜魄,你會注意到一個小問題:Table視圖的第一行隱藏在搜索欄下面悔叽。


這并不奇怪,因為您將searchBar放在表的頂部爵嗅,遮住了tableView的一部分娇澎。
你可以用幾種不同的方法來解決這個問題:
1.更改表視圖的頂部布局約束,以匹配搜索欄的底部邊緣睹晒。
2.使搜索欄部分透明趟庄,以便讓表格單元格的內(nèi)容發(fā)光括细。
3.使用表視圖的content inset屬性允許搜索欄覆蓋的區(qū)域。

你會選擇選項3戚啥。不幸的是勒极,content inset屬性不能通過Interface Builder使用。所以虑鼎,這必須通過代碼來實現(xiàn)辱匿。

?在SearchViewController.swift的viewDidLoad()的末尾添加以下一行:

tableView.contentInset = UIEdgeInsets(top: 64, left: 0,  bottom: 0, right: 0)

這告訴table view在頂部添加64個point的空白—狀態(tài)欄為20個point,searchBar為44個point炫彩。

現(xiàn)在第一行總是可見的匾七,當您滾動表視圖時,單元格仍然在searchBar下江兢。


3.執(zhí)行偽搜索

在實現(xiàn)iTunes商店搜索之前昨忆,最好了解UISearchBar組件是如何工作的。
在本節(jié)中杉允,您將從搜索欄中獲得搜索項邑贴,并使用它將一些虛假的搜索結(jié)果放入表視圖中。一旦您掌握了這些工作叔磷,您就可以在web服務(wù)中構(gòu)建了拢驾。嬰兒般的第一步!

?運行應(yīng)用程序改基。如果你點擊搜索欄,屏幕上的鍵盤會出現(xiàn)——如果你在模擬器,您可能需要按?K彈出鍵盤,和Shift +?K允許從你的Mac鍵盤打字繁疤。
然而,當你輸入一個搜索詞并點擊搜索按鈕時秕狰,它不會做任何事情稠腊。

監(jiān)聽搜索欄就完事了——還有別的辦法嗎?-委托。讓我們把這個委托代碼放到一個extension擴展中鸣哀。

添加搜索欄委托

將以下內(nèi)容添加到SearchViewController.swift的底部,在最后一個右括號之后:

extension SearchViewController: UISearchBarDelegate {
  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    print("The search text is: '\(searchBar.text!)'")
  }
}

請記住架忌,您可以使用extension來組織源代碼。通過將所有UISearchBarDelegate內(nèi)容放到它自己的extension中我衬,您可以將它們放在一起叹放,而不影響其他代碼。

UISearchBarDelegate協(xié)議有一個方法searchBarSearchButtonClicked(_:)低飒,當用戶輕擊鍵盤上的搜索按鈕時調(diào)用該方法许昨。您將實現(xiàn)此方法將一些假數(shù)據(jù)放入表中懂盐。稍后褥赊,您將讓這個方法向iTunes商店發(fā)送一個網(wǎng)絡(luò)請求,以查找與用戶輸入的搜索文本匹配的歌曲莉恼、電影和電子書拌喉,但是我們不要同時做太多的新事情!

目前速那,所有的新代碼所做的就是將搜索項從搜索欄輸出到Xcode控制臺。

提示:當我使用print()時尿背,我總是在單引號之間加上字符串端仰。這樣,您就可以很容易地看到字符串中是否有尾隨或前導空格田藐。還要注意搜索欄荔烧。文本是optional型,因此我們需要展開它汽久。它永遠不會返回nil鹤竭,所以使用一個nil就可以。

? 在storyboard中景醇,從搜索欄control-拖動到SearchViewController或者頂部的黃色圓圈臀稚。連接到委托。
? 運行應(yīng)用程序三痰,在搜索欄中輸入一些內(nèi)容并按下搜索按鈕吧寺。Xcode Debug窗格現(xiàn)在應(yīng)該打印您輸入的文本。


顯示偽結(jié)果

?給SearchViewController.swift添加以下新的(空的)extension:

extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
}

上面的extension將處理所有與tableView相關(guān)的委托方法散劫。當然稚机,如果您愿意,也可以將它們作為兩個單獨的extension添加進來获搏,但是我更愿意將所有與tableView委托相關(guān)的代碼保存在一個地方抒钱。

添加UITableViewDataSource和UITableViewDelegate協(xié)議對于之前的應(yīng)用程序是不必要的,因為你在每種情況下都使用了UITableViewController颜凯。UITableViewController已經(jīng)根據(jù)需要遵守這些協(xié)議谋币。
SearchViewController是一個常規(guī)視圖控制器,因此你必須自己連接數(shù)據(jù)源和委托協(xié)議症概。

?現(xiàn)在Xcode應(yīng)該抱怨你的代碼不符合UITableViewDataSource協(xié)議蕾额。使用Xcode“Fix”選項添加協(xié)議存根,然后修改代碼彼城,如下所示诅蝶,添加您目前需要的最小代碼:

extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 0
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return UITableViewCell()
  }
}

這只是告訴table view它還沒有行。很快您就會給它提供一些要顯示的假數(shù)據(jù)募壕,但是現(xiàn)在您只想能夠編譯沒有錯誤的代碼调炬。

通常,您可以聲明符合協(xié)議舱馅,而不需要實現(xiàn)協(xié)議的任何方法——這種工作方式對于UISearchBarDelegate來說很好缰泡。

協(xié)議可以有可選的和必需的方法。如果你忘記了一個必需的方法代嗤,你通常會看到Xcode發(fā)出抱怨棘钞,就像你在上面所做的那樣缠借。

?在storyboard中,control-拖動從Table View到Search View Controller宜猜。連接到數(shù)據(jù)源泼返。重復(fù)此操作以連接到委托。

可能你想知道為什么搜索視圖控制器中要連接到委托兩次——第一次是searchBar,第二次是Table View姨拥。Interface Builder展示的方式有點誤導:委托的出口outlet不是來自SearchViewController,而是你control-拖動關(guān)聯(lián)的對象绅喉。因此,您將SearchViewController關(guān)聯(lián)searchBar的delegate outlet叫乌,以及tableView的delegate(和數(shù)據(jù)源dataSource)outlet:


?構(gòu)建并運行應(yīng)用程序霹疫,以確保一切正常運行。

注意:你注意到這些數(shù)據(jù)源方法和之前的應(yīng)用程序有什么不同嗎?仔細看…
答:它們沒有override關(guān)鍵字综芥。
在之前的應(yīng)用程序中丽蝎,重寫是必要的,因為我們正在處理UITableViewController的子類膀藐,它已經(jīng)提供了自己版本的tableView(:numberOfRowsInSection:)和tableView(:cellForRowAt:)方法屠阻。
在這些應(yīng)用程序中,您要“覆蓋”或用自己的版本替換這些方法额各,因此需要使用override關(guān)鍵字国觉。
但是在這里,你的基類不是一個表格視圖控制器而是一個常規(guī)的UIViewController虾啦。這樣的視圖控制器還沒有任何表格視圖方法麻诀,所以這里你沒有重寫任何東西

正如您現(xiàn)在所知道的,表視圖需要某種數(shù)據(jù)模型傲醉。讓我們從一個簡單的數(shù)組開始蝇闭。
?為數(shù)組添加一個實例變量——它在class括號中洲守,而不是在任何extension中:

var searchResults = [String]()

搜索欄委托方法會將一些假數(shù)據(jù)放入這個數(shù)組中雁歌,然后使用表格顯示它。
?將searchBarSearchButtonClicked(_:)方法替換為:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  searchResults = []
  for i in 0...2 {
    searchResults.append(String(format: 
        "Fake Result %d for '%@'", i, searchBar.text!))
  }
  tableView.reloadData()
}

這里的符號[]表示實例化一個新的字符串數(shù)組并用它替換searchResults屬性的內(nèi)容祸憋。這是在用戶每次執(zhí)行搜索時完成的吐咳。如果前面已經(jīng)有一個結(jié)果數(shù)組逻悠,那么該數(shù)組將被丟棄并重新分配。您也可以編寫searchResults = [String]()來執(zhí)行相同的操作韭脊。

向數(shù)組中添加一個帶文本的字符串童谒。只是為了好玩,這個過程重復(fù)了3次沪羔,所以您的數(shù)據(jù)模型將包含3行饥伊。

當你寫for i in 0...2 的時候,它創(chuàng)建了一個循環(huán),該循環(huán)重復(fù)三次撵渡,因為閉區(qū)間0…2包含數(shù)字0融柬、1和2死嗦。注意趋距,這不同于半開放區(qū)間0..<2,其中只包含0和1越除。你也可以寫1…3但是节腐,正如您現(xiàn)在所發(fā)現(xiàn)的,程序員喜歡從0開始計數(shù):]

您以前見過格式化字符串摘盆。格式說明符%d是整數(shù)的占位符翼雀。同樣,%f是浮點數(shù)孩擂。占位符%@適用于所有其他類型的對象狼渊,比如字符串。

方法中的最后一條語句重載table視圖以使新行可見类垦,這意味著您還必須調(diào)整數(shù)據(jù)源方法來從這個數(shù)組中讀取數(shù)據(jù)。
?將表視圖委托擴展中的方法替換為:

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cellIdentifier = "SearchResultCell"
  
  var cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) 
  if cell == nil {
    cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
  }

  cell.textLabel!.text = searchResults[indexPath.row]
  return cell
}

到目前為止蚤认,您應(yīng)該對上面的所有代碼都非常熟悉。只需根據(jù)searchResults數(shù)組的內(nèi)容返回要顯示的行數(shù)蘸嘶,然后手工創(chuàng)建一個UITableViewCell來顯示行陪汽。
運行應(yīng)用程序。如果你搜索任何東西雪情,一些虛假的結(jié)果會被添加到數(shù)據(jù)模型中你辣,并顯示在表格中。
搜索其他內(nèi)容舍哄,表格視圖就會用新的假結(jié)果更新宴凉。



UI的改進

在這一點上,你可以對應(yīng)用的功能做一些改進表悬。

搜索時隱藏鍵盤

按下搜索按鈕后弥锄,鍵盤還留在屏幕上,這不是很好。它模糊了表格視圖的一半籽暇,而且無法忽略鍵盤温治。
?將下面的一行添加到searchBarSearchButtonClicked(_:)的頂部:

searchBar.resignFirstResponder()

這告訴UISearchBar它不應(yīng)該再監(jiān)聽鍵盤輸入。結(jié)果戒悠,鍵盤會隱藏起來熬荆,直到你再次點擊搜索欄。

您還可以配置table視圖以用手勢關(guān)閉鍵盤绸狐。
?在storyboard中卤恳,選擇Table View。到Attributes inspector中寒矿,設(shè)置鍵盤互斥(Keyboard to Dismiss interactively)突琳。

將搜索欄擴展到狀態(tài)區(qū)域

搜索欄上方的狀態(tài)欄仍然有一個難看的白色空白。如果狀態(tài)欄區(qū)域與搜索欄統(tǒng)一起來符相,效果會好得多拆融。UINavigationBar和UISearchBar項目有一個委托方法,允許項目顯示它的頂部位置啊终。
?將以下方法添加到SearchBarDelegate的extension中:

func position(for bar: UIBarPositioning) -> UIBarPosition {
  return .topAttached
}

現(xiàn)在這款應(yīng)用看起來好多了:


如果你在UISearchBarDelegate的API文檔中查找镜豹,你不會找到這個position(for:)方法。相反孕索,它是UIBarPositioningDelegate協(xié)議的一部分逛艰,UISearchBarDelegate協(xié)議擴展了這個協(xié)議——就像類一樣,協(xié)議可以從其他協(xié)議繼承散怖。

API文檔

Xcode附帶了一個用于開發(fā)iOS應(yīng)用程序的大型文檔庫∏范基本上所有你需要知道的都在這里具伍。學習使用Xcode文檔瀏覽器——它將成為你最好的朋友!
有幾種方法可以在Xcode中找到項目的文檔。有一個快速的幫助萤厅,它顯示了關(guān)于文本光標下的項目的信息:


只要打開Quick Help inspector快速幫助檢查器——檢查窗格中的第二個選項卡——它就會顯示上下文相關(guān)的幫助楼誓。將文本光標放在想要了解更多信息的項上疟羹,檢查器將提供摘要阁猜。您可以單擊摘要中的任何藍色文本鏈接跳轉(zhuǎn)到完整的文檔黄刚。
您還可以獲得彈出式幫助憔维。按住Option (Alt)鍵检吆,將鼠標懸停在想要了解更多信息的項目上蹭沛。然后點擊鼠標:



當然,還有一個完善的文檔窗口帚呼。您可以從“Help”菜單的“開發(fā)人員文檔(Developer Documentation)”下訪問它。使用頂部的工具欄搜索你想知道更多的信息:

4.創(chuàng)建數(shù)據(jù)模型

到目前為止沈自,您已經(jīng)向searchResults數(shù)組添加了String對象今豆,但這有點限制呆躲。您將從iTunes商店返回的搜索結(jié)果包括產(chǎn)品名稱插掂、藝術(shù)家的名稱、到圖像的鏈接璃弄、購買價格等等夏块。
你不能把所有這些都放在一個字符串中,所以讓我們創(chuàng)建一個新的類來保存這些數(shù)據(jù)政己。

SearchResult類

?使用Swift文件模板為項目添加一個新文件歇由。新類命名為SearchResult。
?將以下內(nèi)容添加到SearchResult.swift中赦肃。

class SearchResult {
 var name = ""
 var artistName = ""
}

這將向新的SearchResult類添加兩個屬性他宛。您還將添加一些其他內(nèi)容。

在SearchViewController中队塘,你需要修改searchResults數(shù)組來保存SearchResult的實例憔古。

var searchResults = [SearchResult]()

下一步锯梁,將搜索欄委托方法中的for in循環(huán)更改為:

for i in 0...2 {
  let searchResult = SearchResult()
  searchResult.name = String(format: "Fake Result %d for", i)
  searchResult.artistName = searchBar.text!
  searchResults.append(searchResult)
}

這將創(chuàng)建SearchResult對象的一個實例陌凳,并在它的name和artistName屬性中放入一些假文本。同樣蛤肌,你在循環(huán)中執(zhí)行此操作,因為僅擁有一個搜索結(jié)果對它來說有點難受赔硫。

此時爪膊,tableView(_:cellForRowAt:)仍然期望數(shù)組包含字符串。更新那個方法

func tableView(_ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  . . .  
  if cell == nil {
    cell = UITableViewCell(style: .subtitle,       // change
           reuseIdentifier: cellIdentifier)
  }
  // Replace all the code below this point
  let searchResult = searchResults[indexPath.row]
  cell.textLabel!.text = searchResult.name  
  cell.detailTextLabel!.text = searchResult.artistName
  return cell
}

代碼現(xiàn)在使用的是“subtitle”單元格樣式耘成,而不是常規(guī)的表視圖單元格。將artistName屬性的內(nèi)容放入subtitle文本標簽中师妙。
?運行應(yīng)用程序;應(yīng)該是這樣的:



5. 沒有找到數(shù)據(jù)

當你在應(yīng)用程序中添加搜索功能時默穴,你必須處理以下情況:

  1. 用戶還沒有執(zhí)行搜索珠洗。
  2. 用戶執(zhí)行搜索并收到一個或多個結(jié)果许蓖。這就是當前版本的應(yīng)用程序所發(fā)生的:對于每個搜索膊爪,您都會得到返回的一些SearchResult對象米酬。
  3. 用戶執(zhí)行搜索赃额,沒有結(jié)果。明確地告訴用戶沒有結(jié)果通常是一個好主意飞盆。如果根本不顯示任何內(nèi)容吓歇,用戶將不知道是否實際執(zhí)行了搜索。

盡管這款應(yīng)用還沒有進行任何實際的搜索杏慰,但你沒有理由不去偽造最后一個場景鹃愤。

處理“沒有得到任何結(jié)果”

為了保護用戶的品味,當用戶搜索“justin bieber”時吟税,該應(yīng)用程序?qū)⒎祷?個結(jié)果,這樣你就知道該應(yīng)用程序可以處理這種情況备典。
?在searchBarSearchButtonClicked(_:)中提佣,在for In循環(huán)中放置以下if語句:

. . .
if searchBar.text! != "justin bieber" {
  for i in 0...2 {
    . . .
  }
}
. . .

這里的更改非常簡單——您添加了一個if語句,如果文本等于“justin bieber”倚喂,則可以阻止創(chuàng)建任何SearchResult對象。

運行應(yīng)用程序子库,搜索“justin bieber”——注意全是小寫字母刑巧。table應(yīng)該是空的。
此時浑彰,您不知道搜索是否失敗,或者是否沒有結(jié)果涯保。您可以通過顯示文本“Nothing found”來改進用戶體驗未荒,這樣用戶就可以毫無疑問地知道沒有搜索結(jié)果片排。
?將tableView(_:cellForRowAt:)的最后一部分更改為:

if cell == nil {
  . . .
}
// New code
if searchResults.count == 0 {
  cell.textLabel!.text = "(Nothing found)"  
  cell.detailTextLabel!.text = ""
} else {
  let searchResult = searchResults[indexPath.row]
  cell.textLabel!.text = searchResult.name
  cell.detailTextLabel!.text = searchResult.artistName
}
// End of new code
return cell

僅僅這樣是不夠的迫卢。當數(shù)組中沒有內(nèi)容時,searchResults.count是0幻捏,對吧?但這也意味著numberOfRowsInSection將返回0篡九,而table視圖將保持空—“Nothing found”這一行將永遠不會出現(xiàn)。

?將tableView(_: numberOfRowsInSection:)改為:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if searchResults.count == 0 {
    return 1
  } else {
    return searchResults.count
  }
}

現(xiàn)在沛善,如果沒有結(jié)果,為了文本為“(Nothing Found)”的行尤蛮,該方法將返回1。這是因為numberOfRowsInSection和cellForRowAt都會檢查這種特殊情況坯临。

試一試:


解決app啟動時沒有結(jié)果

不幸的是,當用戶還沒有搜索任何內(nèi)容時挟炬,文本“Nothing found”也會首先出現(xiàn)辟宗。那是愚蠢的爵赵。
問題是您無法區(qū)分“尚未搜索”和“未找到”。現(xiàn)在泊脐,您只能判斷searchResults數(shù)組是否為空空幻,但不能確定是什么原因?qū)е铝诉@種情況。

練習:你怎么解決這個小問題?

我想到了兩個顯而易見的解決方案:

  1. 將searchResults更改為optional類型容客。如果它是nil秕铛,也就是說它沒有值,那么用戶還沒有搜索缩挑。這與用戶搜索但沒有找到匹配項的情況不同谨湘。
  2. 使用一個單獨的布爾變量來跟蹤搜索是否已經(jīng)完成。

選擇optional可能很誘人,但如果可以最好避免選擇這種方式蕉朵。它們會使邏輯復(fù)雜化,如果不正確地展開,可能會導致應(yīng)用程序崩潰,而且它們需要if let語句無處不在。
所以巩检,我們將選擇布爾值。但是,您可以自由地回來嘗試您自己的optional選項夹姥,并比較它們之間的差異旦部。這將是一個很好的練習!

?仍然在SearchViewController.swift中蘸秘,添加一個新的實例變量:

var hasSearched = false

?在搜索欄的委托方法中,將這個變量設(shè)置為true霞揉。你在哪里做這些并不重要抚官,只要它發(fā)生在表格視圖重載之前朴上。

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  . . .
  hasSearched = true      // Add this line
  tableView.reloadData()
}

?最后,改變tableView(_: numberOfRowsInSection:)來查看這個新變量的值

func tableView(_ tableView: UITableView,
     numberOfRowsInSection section: Int) -> Int {
  if !hasSearched {
    return 0
  } else if searchResults.count == 0 {
    return 1
  } else {
    return searchResults.count
  }
}

現(xiàn)在毫蚓,table視圖仍然是空的,直到您第一次搜索某個東西胁后。試一下!
稍后敏储,您將看到使用枚舉處理此問題的更好方法瘦真,它會讓您大吃一驚!

選擇處理

還有一件事,如果你現(xiàn)在點擊一行,它就會被選中,并一直被選中。
要解決這個問題,可以在表視圖委托擴展中添加以下方法:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  tableView.deselectRow(at: indexPath, animated: true)
}
  
func tableView(_ tableView: UITableView, 
     willSelectRowAt indexPath: IndexPath) -> IndexPath? {
  if searchResults.count == 0 {
    return nil
  } else {
    return indexPath
  }
}

tableView(_:didSelectRowAt:)方法會簡單地用動畫取消選擇行,而willSelectRowAt會確保只有在有實際搜索結(jié)果時才能選擇行盯桦。
如果您現(xiàn)在點擊(Nothing Found)行,您將注意到它根本不會變成灰色羽利。實際上蛋辈,如果短時間內(nèi)按下一行,它可能仍然會變成灰色托酸。這是因為您沒有更改單元格的selectionStyle屬性揩慕。你很快就會修好的溶耘。

?現(xiàn)在是提交所有更改的好時機二拐。點擊Source Control → Commit,或者按鍵盤快捷鍵?+Option+ C。
確保所有修改的文件都在左邊的列表中被選中/選中凳兵,檢查您的修改百新,并輸入提交備注——類似于“添加搜索欄和表視圖,現(xiàn)在搜索會暫時將偽結(jié)果放入table中”庐扫。
按下Commit按鈕完成提交饭望。

版本編輯器

如果你想回顧你的提交歷史,你可以通過源代碼控制導航器——正如你在本章開頭學到的那樣——或者版本編輯器(Version editor)形庭,如下圖所示:


您可以使用Xcode窗口右上角的相關(guān)工具欄按鈕切換到版本編輯器铅辞。

在上面的截圖中,左邊顯示的是以前的版本萨醒,右邊顯示的是當前版本斟珊。您可以使用每個窗格底部的跳轉(zhuǎn)欄在版本之間切換。版本編輯器是查看源文件更改歷史的非常方便的工具富纸。

這個應(yīng)用程序還不是很令人印象深刻囤踩,但是您已經(jīng)為接下來的工作打下了基礎(chǔ)旨椒。
您有一個搜索欄,并且知道當用戶按下search按鈕時如何操作堵漱。該應(yīng)用程序還有一個簡單的數(shù)據(jù)模型综慎,由一個帶有SearchResult對象的數(shù)組組成,它可以在一個表視圖中顯示這些搜索結(jié)果勤庐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末示惊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子埃元,更是在濱河造成了極大的恐慌涝涤,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岛杀,死亡現(xiàn)場離奇詭異,居然都是意外死亡崭孤,警方通過查閱死者的電腦和手機类嗤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辨宠,“玉大人遗锣,你說我怎么就攤上這事∴托危” “怎么了精偿?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赋兵。 經(jīng)常有香客問我笔咽,道長,這世上最難降的妖魔是什么霹期? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任叶组,我火速辦了婚禮,結(jié)果婚禮上历造,老公的妹妹穿的比我還像新娘甩十。我一直安慰自己,他們只是感情好吭产,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布侣监。 她就那樣靜靜地躺著,像睡著了一般臣淤。 火紅的嫁衣襯著肌膚如雪橄霉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天荒典,我揣著相機與錄音酪劫,去河邊找鬼吞鸭。 笑死,一個胖子當著我的面吹牛覆糟,可吹牛的內(nèi)容都是我干的刻剥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼滩字,長吁一口氣:“原來是場噩夢啊……” “哼造虏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起麦箍,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤漓藕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挟裂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體享钞,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年诀蓉,在試婚紗的時候發(fā)現(xiàn)自己被綠了栗竖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡渠啤,死狀恐怖狐肢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沥曹,我是刑警寧澤份名,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站妓美,受9級特大地震影響僵腺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜部脚,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一想邦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧委刘,春花似錦丧没、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淆珊,卻和暖如春夺饲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工往声, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留擂找,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓浩销,卻偏偏與公主長得像贯涎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子慢洋,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359