1. 程式人生 > >【一步一步學IOS5 】 在表檢視中新增搜尋欄

【一步一步學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。搜尋功能正常了。