這篇教程是由iOS教程組的Nicolas Martin編寫的。Nicolas是nmappworks的一名自由iOS開發者。
在移動應用程序的世界裏,用戶對信息獲取的速度要求非常高!
iOS用戶希望他們需要的信息能夠迅速地,直觀地展現在他們面前。
因爲UITableView的上下滾動能讓用戶迅速,自然地瀏覽大量信息,許多基於UIKit的應用都使用了UITableView來組織信息。但如果信息量非常非常大,讓用戶上下滾動如此長的列表是非常沒有效率的。所以一個搜索的功能就是必須的了。
幸運的是,UIKit裏有一個叫做UISearchBar的組件,能讓用戶迅速的篩選有用的信息。
在這個教程裏,你會學習如何將這個搜索的組件融入你應用中的UITableView,包括加入自選的範圍欄,使得你的應用更好用!
別被搜索欄嚇到
今時今日,用戶在應用裏看到很長的列表時,都會期待一個搜索的功能。如果他們找不到搜索功能,他們會非常沮喪的!
問題是UISearchBar並不是那麼容易用。UISearchBar的文檔並不全面,這對第一次使用UISearchBar的開發者可能會比較有挑戰。
但是,一但你對搜索欄有一定了解,特別是在讀完這篇教程之後,你就不會覺得它有多難了。
其實UISearchBar挺懶的。它本身不做任何搜索。UISearchBar會提供一個基本的iOS搜索欄界面。它就像一箇中等幹部一樣,最在行的就是讓別人做事情。(像我以前的老闆一樣!)
UISearchBar類用delegate協議的方式來告訴app的其他部分用戶正在搜索欄中做什麼。你需要自己編寫對比字符串和過濾搜索的函數。這一聽起來有點嚇人,但是自定義的搜索功能讓你對搜索過濾有更多的控制。你能夠根據自己app的特點來修改搜索結果,讓你的用戶體驗更迅速和智能的搜索。
在這個教程中,你將編寫一個基於table view的有搜索功能的糖果app。(是的,好吃的糖果!)
首先,我們看看這個教程的概要:
- Candy 類: 爲了讓搜索函數明白如何過濾一些樣本數據,你將會創建一個自定義的對象
- Table View視圖: 我們會簡單介紹如何創建一個table view視圖。如果你已經熟悉這麼做,那你可以很快的瀏覽這一部分。
- 搜索欄: 你將會在視圖控制器中加入一個搜索欄的對象,這樣才能進行搜索。
- 過濾數據隊列: 你會學習如何使用一個能過濾的數據隊列來處理搜索請求。
- 傳送數據: 當你有搜索欄時,app的視圖變化會需要傳遞相應的搜索數據。這一段就是爲了鞏固這類的知識的
- 範圍欄: UISearchBar類還有一個強大的功能:讓用戶選擇搜索的範圍,以便於進一步縮小搜索結果。
- 隱藏UISearchBar: 最後一部分你將學到如何在用戶不需要搜索時隱藏搜索欄!
準備好做糖果搜索了嗎?那我們開始吧!
我想要吃糖果
在XCode裏,點擊”File New Project…”然後選擇”iOS Application Single View Application”。將project命名爲”CandySearch”。請確保在”Use Storyboards”和”Use Automatic Reference Counting”的選項旁打鉤。最後,確保device欄中你的選擇是iPhone,然後將這個project保存到你認爲合適的路徑。
我們先清理掉一些默認加入的文件,這樣我們才能從零開始。在XCode左邊的Project Navigator中多選ViewController.h和ViewController.m,點擊鼠標右鍵打開菜單,選擇Delete,然後點擊”Move to Trash”(移放到垃圾桶)。點擊打開MainStoryboard.storyboard,選中裏面唯一的view controller(視圖控制器)然後刪除它。
現在我們開始重新按自己的要求來構建這個project。首先從storyboard開始。從位於XCode右側欄下方的Object Browser(對象瀏覽器)中拖出一個Navigation Controller(導航控制器)到storyboard(故事版)中。這會生成兩個view,一個代表那個navigation controller(導航控制器)本身,另一個是UITableView。這個UITableView將成爲我們程序的第一個用戶看見的視圖。
最後還需要加入一個視圖控制器來控制當用戶搜索列表時顯示的詳細內容視圖。將一個View Controller對象拖入故事版(storyboard)中。你需要將這個視圖控制器與UITableView的視圖控制器聯繫起來。按住control鍵然後從Table View點擊拖動至那個新的視圖控制器,在彈出的manual segue窗口中選擇“Push”做爲視圖替換的模式。現在,你的project應該和下圖類似:
創建Candy類
下面你將創建一個數據模型類來保存每種糖果的類型和名字等信息。
創建一個新的文件並使用“iOS Cocoa Touch Objective-C class”模板。將這個類命名爲Candy,並將它設爲NSObject的子類。
打開Candy.h文件並將其內容改爲如下:
|
這個類的對象有兩個property(屬性),一個是糖果的種類,另一個是糖果的名字。當用戶搜索糖果時,你會用糖果的名稱和用戶輸入的字符串進行比較。而糖果的種類只有在教程後面使用範圍搜索時纔會有用。
將Candy.m文件的內容改爲如下:
|
上面加入的方法是用來初始化你的Candy類對象的。這個方法需要一個種類和名稱的字符串。你將會將這些Candy對象展示在列表中,讓用戶可以輕易使用你的搜索功能來過濾顯示的內容。
現在我們可以開始設置UITableView了。
設置UITableViewController(列表視圖控制器)/h2>
下面我們將設置一個UITableView。用“iOSCocoa TouchObjective-C class ”模板創建一個新的文件,命名爲CandyTableViewController,並將其設爲UITableViewController的子類。
我們先添加一個數組來儲存數據。打開CandyTableViewController.h文件並在@interface一行下面加入:
|
我們對CandyTableViewController.m文件進行一些簡單的修改,只爲了先完成一個示範性的列表。首先,清理掉一些我們不需要的模板自帶的代碼。將“(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath”以及其之後一直到文件最後一段(除了@end)的代碼全部刪除。
同時刪除numberOfSectionsInTableView:方法,因爲我們不需要用到它。
在文件頂端,導入Candy.h頭文件,讓我們的控制器知道這個數據模型:
|
然後在“@implementation”下面加入如下代碼:
|
現在你的控制器已經知道了Candy類數據模型,所以你可以通過candyOfCategory:name:方法來加入一些示範數據。在這個教程裏,你只需要創建爲數不多的示範數據來展示搜索欄的功能。但是在真實的app中,可能會有成百上千的數據。但無論數據是多或少,我們的搜索都能用!
在viewDidLoad方法中,刪除模板自帶的備註並加入下面的代碼來提供示範性數據:
|
將tableView:numberOfRowsInSection:的內容修改爲如下:
|
在最後一個“@end”前加入下面代碼:
|
首先,你告訴列表控制器每一行應該顯示什麼。然後從candyArray數組中通過indexPath來獲取相應的Candy對象,然後用這個Candy對象的內容添入相應的行中在列表視圖中顯示出來。
你還需要在storyboard中做一些設置,上述的代碼纔會有效。打開MainStoryboard.storyboard文件,選擇Root View Controller然後在Identity Inspector(右邊設置欄上方的第三欄)中將它的類改爲CandyTableViewController。
接着在左手工具欄點擊選擇table view,然後選擇Connections Inspector(右邊工具欄上方的第六欄)。將dataSource和delegate與Candy Table View Controller連接起來(點擊dataSource和delegate旁的圓點然後拖至Candy Table View Controller)。
雙擊”Root View Controller”標題並將其改爲”CandySearch”。
保存並編譯運行。你的table view應該顯示我們輸入的示範數據!糖果太多,時間太少。我們需要一個搜索欄!
設置UISearchBar
現在是時候設置UISearchBar了!打開storyboard(故事版)文件然後將一個“Search Bar and Search Display Controller”對象拖入到table view controller中。注意,對象庫中還有一個“Search Bar” 對象,那不是我們想要的。將搜索欄放在導航欄和Table View之間。
小貼士:不知道什麼是“Search Display Controller”(搜索顯示控制器)?根據蘋果自己的文檔記載,一個搜索顯示控制器用來控制一個搜索欄以及一個table view。這個table view會顯示搜索過濾後的信息。而這些信息來源於另一個視圖控制器。也就是說,在我們的情況下,你的“search display controller”(搜索顯示控制器)需要知道那個table view controller控制的那些示範數據。然後在搜索顯示控制器自己的table view中顯示搜索過濾後的結果。這個table view會覆蓋table view controller的視圖,這樣用戶只能看到過濾後的結果。
UISearchBar的屬性設置
在storyboard界面下的Attributes inspector欄,請花點時間瀏覽一下Search Bar對象的各種屬性。就算你不需要用到它們,但瞭解UIKit組件的各種屬性也是有價值的。
- Text: 這會改變出現在搜索欄中的默認字符串。因爲我們程序中的搜索欄不需要默認值,我們不需要添入任何字符。
- Placeholder: 在搜索欄沒有添入任何字符串時,一般會顯示一串灰色的字符來提示用戶輸入搜索信息。這個屬性就是用來設置這個提示的內容的。在我們的糖果程序中,就顯示”Search for Candy”(搜索糖果)好了。
- Prompt: 這個屬性的值會出現在搜索欄上方。對於有複雜搜索功能的程序,用戶可能需要一些指導信息。(但在我們的這個簡單程序中,灰色的提示信息應該就足夠了!)
- Style & Tint: 這些屬性能讓你改變搜索欄的格調和顏色。爲了讓你的設計看起來更和諧,我建議你用和UINavigationBar上這些屬性同樣的設置。
-
Show Search Results Button:
如果你在這個屬性旁打鉤,搜索欄右邊就會出現一個灰色按鈕。這個按鈕可以用來顯示最近幾次的搜索,或者上次搜索的結果。這個按鈕的的功能可以通過Search Bar Delegate內的方法來控制。
-
Show Bookmarks Button:
如果你在這個屬性旁打鉤,搜索欄右邊就會出現一個標準的藍色書籤按鈕。用戶可以通過這個按鈕調出他們儲存的書籤。這個按鈕的行爲同樣通過Search Bar Delegate內的方法來控制。 -
Show Cancel Button:
如果你在這個屬性旁打鉤,搜索欄右邊就會出現一個標準的取消按鈕,讓用戶取消搜索。不要在這個屬性旁打鉤,因爲當你在搜索欄中輸入字符串後,這個按鈕會自動出現。 -
Shows Scope Bar(顯示範圍欄) & Scope Titles(範圍標題):
範圍欄讓用戶可以進一步縮小搜索範圍。比如在一個音樂程序中,這個範圍欄可以讓用戶將搜索侷限於藝術家名字,專輯或者音樂類型。現在先不要在這個屬性旁打鉤。你會在這個教程的後半部分設置這個範圍欄。 -
Capitalize(大小寫), Correction(自動糾錯) & Keyboard(鍵盤):
這些屬性是從UITextField中直接搬過來的,讓你能改變搜索欄的一些行爲。比如,如果用戶需要搜索的是商店名稱或者人名,那你應該將自動糾錯關閉,否則用戶輸入的名字很有可能被自動糾錯成爲其他詞語。在這個教程中我們糖果的名字都是字典裏有的名詞,所以你可以不用關閉自動糾錯。
注意: 總是清楚地知道你有什麼選擇能提高開發效率。所以以後在iOS的開發中,你應該記得花時間看看所有有可能用到的選項設置。
設置UISearchBarDelegate
在完成storyboard的相關設置後,我們需要寫一些代碼來讓搜索欄運作起來。打開CandyTableViewController.h文件並將:
|
改爲:
|
這裏我們將theUISearchBarDelegate類和UISearchDisplayDelegate類加入到CandyTableViewController類中。
並且,加入一個搜索欄的IBOutlet以及一個叫做”filteredCandyArray”的數組來儲存搜索的結果。
|
別忘了在CandyTableViewController.m文件中“synthesize”這些新的屬性:
|
現在你的代碼中有一個outlet了,你需要將storyboard中的搜索欄和這個outlet連接起來。打開故事版文件,選擇“Candy Table View”控制器,打開“Connections Inspector”欄然後從candySearchBar outlet拖一根線出來和搜索欄連接起來:
編譯並運行,你應該能在模擬器中看到那個搜索欄,只是它似乎不工作!你需要使用那個filteredCandyArray數組。
設置filteredCandyArray數組
首先你需要初始化那個NSMutableArray。你可以將它的初始大小設爲candyArray數組的大小,因爲你不可能有比原來數組更大的過濾後的數組。將下面的代碼加到CandyTableViewController.m文件的viewDidLoad方法中我們設置candyArray的地方,但是在[self.tableView reloadData]之前:
|
將下面的代碼加入到文件的尾部:
|
上面的方法會根據搜索內容來過濾candyArray數組,並將結果放置到清空後的filteredCandyArray數組中。在清空filteredCandyArray後,我們用NSPredicate來過濾candyArray數組。
NSPredicate可以根據一個簡單的條件字符串來過濾一個數組。注意NSPredicate的格式:
|
看起來很複雜啊?別怕,這其實很簡單的。SELF.name指的是數組中每一個Candy對象的“name”屬性。”contains[c]“會讓predicate搜索“name”屬性中的字符串,看看有沒有和後面提供的“searchText”一樣的字符串。大小寫有別。
接着,將下面的代碼加到文件末端:
|
上面的代碼使得UISearchDisplayController Delegate的兩個方法在用戶改變搜索內容時會去調用實際過濾內容的函數(filterContentForSearchText)。
第一個方法在用戶改變搜索欄裏的字符串時就會被調用。第二個方法則在範圍欄改變時被調用。你還沒有在app中添加這個範圍欄,所以這個方法只有在教程後面加入範圍欄後纔會有用。
編譯並運行你的程序;你會發現搜索欄還是不能用,怎麼回事呢?這是因爲你還沒有編寫代碼來讓cellRowForIndexPath:方法知道什麼時候應該用一般的數據,什麼時候用過濾後的數據。將:
|
改爲:
|
上面的代碼檢查現在現實的列表視圖屬於普通列表還是搜索後的列表。如果是搜索列表的話,視圖中實際顯示的數據應該來自filteredCandyArray。否則,數據應該來自那個完整的數組。Display Controller會自動負責顯示和隱藏相應的列表,所以我們只需要提供正確的(過濾或者沒有過濾過的)數據,列表視圖就會正確地顯示出來。
numbersOfRowsInSection:方法也需要一些修改,因爲過濾後的數組的長度顯然和沒過濾過的數組不一樣。將numbersOfRowsInSection:的內容改爲如下:
|
編譯並運行程序。你的搜索欄已經可以使用了!不信你可以搜索一下你想要的糖果名字。
注意: 你或許也注意到numberOfRowsInSection:方法中用來檢查現在顯示的列表視圖的if/else邏輯在很多其他地方也需要。忘了做這個檢查或導致奇怪的bug。你只需要記住搜索過的內容會顯示在一個另外的列表視圖中。兩個列表視圖完全是分開的兩個視圖,蘋果的設計讓用戶難以察覺這個區別。所以導致許多開發者的誤解。
將信息發送到詳細視圖(detail view)
類似剛纔的情況,在詳細視圖中,詳細視圖控制器也需要知道現在使用的是哪一個列表視圖:有所有內容的視圖,或是有過濾後內容的視圖。將下面的代碼加入到CandyTableViewController.m文件中:
|
打開storyboard並確保連接糖果列表視圖控制器與詳細視圖控制器的segue的identifier是”candyDetail”。編譯並運行程序,當你點擊列表視圖中的任意一行時,那個糖果的詳細信息就會顯示在切入的詳細視圖中。
使用範圍搜索
我們還可以通過糖果的種類來進一步縮小搜索範圍。我們的糖果數據總共有三類(巧克力,硬糖和其他)。
首先,在storyboard中加入一個範圍欄。來到CandySearch視圖控制器並點擊選擇搜索欄。在 attributes inspector(屬性設置欄)中,在”Shows Scope Bar”選項旁打勾。然後將範圍欄的標題改爲:”All”, “Chocolate”, “Hard” ,和 “Other”。(你可以點擊+按鈕來加入更多的種類,雙擊來修改標題)。你的屏幕應該和下圖類似:
下面我們需要修改CandyTableViewController.m文件中的filterContentForSearchText:方法,讓它在過濾搜索時也會考慮選中的類別:
|
上面的代碼使用了一個新的NSPredicate,叫做scopePredicate。如果選中的類別爲“All”,我們則不做任何進一步的過濾。
編譯並運行程序。範圍欄應該已經出現在視圖裏了。但是我們在沒有搜索時是不應該看到這個範圍欄的。將下面的代碼加入到viewDidLoad方法的頂端(就則調用super之後):
|
編譯並運行。現在,你的範圍欄應該和下圖類似:
像音樂app中那樣隱藏UISearchBar
如果你注意蘋果自帶的音樂app,那裏面音樂列表的搜索欄一開始是隱藏起來的。這樣你的列表試圖就會有更多的空間。在viewDidLoad方法中,你剛纔加入代碼的下方加入下面的代碼:
|
因爲你的搜索欄一開始是隱藏起來的,我們應該加入一個按鈕來讓用戶知道列表視圖有搜索功能。
首先,我們應該加入一個IBAction。 將下面的代碼加入到CandyTableViewController.m文件的低端:
|
將下面的方法定義加入到CandyTableViewController.h文件:
|
現在打開storyboard,在導航欄加一個Bar Button Item(欄按鈕)。在Attributes Inspector(屬性設置欄),將identifier的值改爲“search”,這樣那個按鈕就會用蘋果默認的放大鏡做爲圖標。然後在connections inspector(鏈接設置欄),將那個按鈕和goToSearch:方法聯繫起來。
這樣就好了!你的“Search”按鈕應該能用了。編譯並運行:
如果看到和上圖類似的界面,你已經大功告成了!