iOS apprentice中文版 - Section 4: Store Search

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

Chapter 32:Search Bar

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

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

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

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


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

1.創(chuà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)在應該啟用它:

如果你沒有看到這個選項,點擊對話框左下角的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進入項目文件夾,手動重命名文件。

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

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

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

那么什么是commit呢?

當您使用Git這樣的版本控制系統(tǒng)時,應該經(jīng)常創(chuàng)建快照。通常情況下,你會在你的應用程序中添加了一個新功能之后,或者當你修復了一個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分支的知識?,F(xiàn)在,只要忽略你在截圖中看到的ch-32分支就可以了,你要知道,如果你沒有其他分支,也沒關(guān)系——你不應該這樣做:

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


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



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

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

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


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


2.創(chuàng)建UI

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



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

你不需要使用UITableViewController作為視圖控制器的基類因為你的UI中有一個表格視圖。對于這個應用程序,我將向您展示如何實現(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的一部分,但是對于這個應用程序,你會把它放在tableView的上方。

設(shè)置 storyboard

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



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

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

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

?點擊Add 4 Constraints按鈕完成。

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


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


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

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



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

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

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


連接到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)在運行這個應用程序,你會注意到一個小問題: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)建了。嬰兒般的第一步!

?運行應用程序。如果你點擊搜索欄,屏幕上的鍵盤會出現(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或者頂部的黃色圓圈。連接到委托。
? 運行應用程序,在搜索欄中輸入一些內(nèi)容并按下搜索按鈕。Xcode Debug窗格現(xiàn)在應該打印您輸入的文本。


顯示偽結(jié)果

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

extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
}

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

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

?現(xiàn)在Xcode應該抱怨你的代碼不符合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ù)源。重復此操作以連接到委托。

可能你想知道為什么搜索視圖控制器中要連接到委托兩次——第一次是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)建并運行應用程序,以確保一切正常運行。

注意:你注意到這些數(shù)據(jù)源方法和之前的應用程序有什么不同嗎?仔細看…
答:它們沒有override關(guān)鍵字。
在之前的應用程序中,重寫是必要的,因為我們正在處理UITableViewController的子類,它已經(jīng)提供了自己版本的tableView(:numberOfRowsInSection:)和tableView(:cellForRowAt:)方法。
在這些應用程序中,您要“覆蓋”或用自己的版本替換這些方法,因此需要使用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ù)組中添加一個帶文本的字符串。只是為了好玩,這個過程重復了3次,所以您的數(shù)據(jù)模型將包含3行。

當你寫for i in 0...2 的時候,它創(chuàng)建了一個循環(huán),該循環(huán)重復三次,因為閉區(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
}

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



UI的改進

在這一點上,你可以對應用的功能做一些改進。

搜索時隱藏鍵盤

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

searchBar.resignFirstResponder()

這告訴UISearchBar它不應該再監(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)在這款應用看起來好多了:


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

API文檔

Xcode附帶了一個用于開發(fā)iOS應用程序的大型文檔庫?;旧纤心阈枰赖亩荚谶@里。學習使用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文本標簽中。
?運行應用程序;應該是這樣的:



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

當你在應用程序中添加搜索功能時,你必須處理以下情況:

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

盡管這款應用還沒有進行任何實際的搜索,但你沒有理由不去偽造最后一個場景。

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

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

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

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

運行應用程序,搜索“justin bieber”——注意全是小寫字母。table應該是空的。
此時,您不知道搜索是否失敗,或者是否沒有結(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ū)分“尚未搜索”和“未找到”?,F(xiàn)在,您只能判斷searchResults數(shù)組是否為空,但不能確定是什么原因?qū)е铝诉@種情況。

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

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

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

選擇optional可能很誘人,但如果可以最好避免選擇這種方式。它們會使邏輯復雜化,如果不正確地展開,可能會導致應用程序崩潰,而且它們需要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)欄在版本之間切換。版本編輯器是查看源文件更改歷史的非常方便的工具。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容