【一步一步學IOS5 】 在表檢視中新增搜尋欄
http://alan-hjkl.iteye.com/blog/1682985
下面,我們來演示一下如何在Tab Bar專案基礎上新增一個搜尋欄。通過搜尋欄,App可以讓使用者指定搜尋條件後,搜尋選單列表。
1.理解搜尋欄顯示控制器(Search Display Controller)
你可以使用搜索顯示控制器(如 UISearchDisplayController 類)管理App中的搜尋功能。搜尋顯示控制器管理搜尋欄(search bar)和表檢視(table view)的顯示,表檢視複雜顯示搜尋結果。
當用戶開始搜尋時,搜尋顯示控制器將在原始的檢視之上,疊加搜尋介面,並顯示搜尋結果。有趣的是,在表檢視中顯示的結果是有搜尋顯示控制器生成的。
和其它的檢視控制器一樣,你可以選擇程式設計建立搜尋控制器,或者使用Storyboard簡單新增搜尋顯示控制器到App中,我們採用後者。
2.在Storyboard 中新增搜尋顯示控制器
在Storyboard 程式設計介面,拖拉Search Bar and Search Display Controller 物件到 Recipe Book 檢視控制器的導航條下面。如果操作正確,你應該看到的如下所示的介面:
在繼續之前,我們嘗試執行下App,介面效果如下。在沒有編寫任何新的程式碼之前,你已經有一個搜尋欄。輕拍搜尋欄,將顯示搜尋介面。但是,搜尋並沒有顯示正確的搜尋結果。
3.搜尋結果顯示原理解析?
在前面提到過,搜尋結果顯示搜尋顯示控制器(Search Display Controller)生成的表檢視中,
我們在開發檢視App時,我們實現了UITableViewDataSource協議,告訴表檢視有多少條資料行顯示,以及每一行的資料。
和UITableView 物件一樣,搜尋顯示控制器生成的表檢視採用相同的方法,採用委託的方式,讓搜尋欄和搜尋結果互動。
一般而言,原始檢視控制器作為搜尋結果資料來源和委託的源物件,我們不必手動連線資料來源(DataSource)和委託(Delegate)到檢視控制器上,
當我們插入搜尋欄到Recipe Book 檢視控制器中時,將自動建立搜尋顯示控制器(Search Display Controller)的連線。滑鼠右鍵,點選搜尋顯示控制器(Search Display Controller)顯示連線資訊。
兩個表檢視(Recipe Book 檢視控制器中的表檢視 和 搜尋結果表檢視)共享相同的檢視控制器,負責資料填充。在顯示錶資料時,都會呼叫到
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{}
和
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {}
4.實現搜尋過濾器
顯然,為了實現搜尋功能,我們必須實現如下任務:
1.實現方法過濾選單名稱,返回正確的搜尋結果
2.更改資料來源方法,區分不同的表檢視。如果傳入的tableView 是 Recipe Book 檢視控制器的表檢視,則顯示所有的選單列表,如果傳入的是搜尋結果表檢視,則僅僅顯示搜尋結果。
首先,我們演示如何時間過濾器,這裡,我們已經有一個數組存放所有的選單列表了,我們需要建立另外一個數組存放搜尋結果 - 命名為searchResults 陣列。
@implementation RecipeBookViewController
{
NSArray *recipes;
NSArray *searchResults;
}
接著,新增一個新的方法負責處理搜尋過濾功能,過濾功能是iOS App 中常見的任務。過濾選單列表的直接方法是迴圈所有名稱,使用if語句過濾結果,這樣是實現並沒有任何錯誤。但是,iOS SDK 提供了一個更好的方法 - Predicate 負責搜尋查詢。 通過使用NSPredicate (是Predicate物件的表現形式),可以簡化程式碼。通過僅僅2行程式碼,就可以搜尋所有的選單列表,返回匹配的結果:- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@",searchText];
searchResults = [recipes filteredArrayUsingPredicate:resultPredicate];
}
基本上,一個Predicate,返回Boolearn值(true或false).你可以NSPredicate格式指定查詢條件,然後使用NSPredicate物件過濾陣列中的資料。NSArray提供了filteredArrayUsingPredicate:方法,該方法返回一個新的陣列,陣列包含了匹配製定的Predicate的物件。Predicate 中 SELF 關鍵字 - SELF contains[cd]%@ 指向比較物件(如選單名稱)。
操作符[cd]表示比較操作 - case 和 diacritic 不敏感。
5.實現搜尋顯示控制器(Search Display Controller)委託
現在,我們已經建立了處理資料過濾的方法,但是如何呼叫該方法呢?
顯然,在使用者輸入搜尋條件時,呼叫filterContentForSearchText: 方法。
UISearchDisplayController 類提供了 shouldReloadTableForSearchString: 方法,在搜尋文字更改時,該方法會自動呼叫,因此,在RecipeBookViewController.m 檔案新增如下方法:
- (BOOL)searchDisplayController:(UISearchDisplayController*)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[selffilterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBarscopeButtonTitles]objectAtIndex:[self.searchDisplayController.searchBarselectedScopeButtonIndex]]];
returnYES;
}
6.在searchResultsTableView 顯示搜尋結果
在前面解釋過,我們需要修改Data Source 方法,區分不同的表檢視(如Recipe Book 檢視控制器中的表檢視 和 搜尋結果表檢視)。
區分表檢視是相當簡單的。
我們簡單標記tableView 物件和 searchDisplayController的 searchResultsTableView.
如果相同,則顯示搜尋結果。
程式碼修改如下:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [searchResults count];
} else {
return [recipes count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = @"RecipeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:simpleTableIdentifier];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.textLabel.text = [searchResults objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = [recipes objectAtIndex:indexPath.row];
}
return cell;
}
7.再次執行App 當完成上述更新之後,再次執行App,搜尋欄效果如下8.處理搜尋結果中的行選擇 儘管搜尋功能正常了,但是它並沒有對行選擇進行處理。我們希望它和選單表檢視一樣功能, 當用戶輕拍任一搜索記錄時,將切換到詳細檢視,顯示所選擇的選單名稱。 之前,我們使用聯線(Segue)連線單元格和詳細檢視 現在,我們需要在Storyboard 中建立另外一個聯線,定義搜尋結果和詳細檢視之間的切換。 問題是我們不能這樣操作,搜尋結果表檢視是搜尋顯示控制器(Search Display Controller)的一個私有變數, 它不可能使用Storyboard 處理搜尋結果的行選擇。 然而,搜尋結果控制器可以讓你使用委託(Delegate),和搜尋結果表檢視的使用者選擇來互動。當用戶選擇一行時,將呼叫 didSelectRowAtIndexPath: 方法。 因此,我們需要實現如下方法:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
[selfperformSegueWithIdentifier:@"showRecipeDetail"sender:self];
}
}
我們簡單的呼叫preformSegueWithIdentifier: 方法,手動觸發showRecipeDetail 聯線。
在繼續編寫程式碼之前,我們再次執行App。在你選擇任一搜索結果記錄時,App顯示詳細檢視,並帶有選單名稱,
但是,選單名稱並不總是正確的。
參考prepareForSegue: 方法,我們使用indexPathForSelectedRow 方法檢索所選indexPath屬性值。
前面提到過,搜尋結果顯示在一個獨立的表檢視中,但是,在之前的prepareForSegue:方法中,我們總是從Recipe Book
檢視控制器的表檢視中檢索所選中的記錄行。
這就是為什麼我們在詳細檢視中獲得錯誤的選單名稱。為了取得搜尋結果中正確的選擇,我們需要修改prepareForSegue:方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"showRecipeDetail"]) {
NSIndexPath *indexPath = nil;
RecipeDetailViewController *destViewController = segue.destinationViewController;
if ([self.searchDisplayControllerisActive]) {
indexPath = [self.searchDisplayController.searchResultsTableViewindexPathForSelectedRow];
destViewController.recipeName = [searchResults objectAtIndex:indexPath.row];
}else{
indexPath = [self.tableView indexPathForSelectedRow];
destViewController.recipeName = [recipes objectAtIndex:indexPath.row];
}
}
}
我們首先判斷使用者是否使用搜索功能。當使用搜索功能時,我們從searchResultTableView 中檢索indexPath,這個是搜尋結果的表檢視。否則,我們仍然從Recipe Book 檢視控制器中的表檢視獲取indexPath 屬性值。 好啦,再次執行App。搜尋功能正常了。