【一步一步学IOS5 】 在表视图中添加搜索栏

下面,我们来演示一下如何在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

{

    [self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles]objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];

    return YES;

}

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 = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier: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) {

        [self performSegueWithIdentifier:@"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.searchDisplayController isActive]) {

            indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];

            destViewController.recipeName = [searchResults objectAtIndex:indexPath.row];

        }else{

            indexPath = [self.tableView indexPathForSelectedRow];

            destViewController.recipeName = [recipes objectAtIndex:indexPath.row];

        }

 

    }

}


我们首先判断用户是否使用搜索功能。当使用搜索功能时,我们从searchResultTableView 中检索indexPath,这个是搜索结果的表视图。否则,我们仍然从Recipe Book 视图控制器中的表视图获取indexPath 属性值。

好啦,再次运行App。搜索功能正常了。


 

猜你喜欢

转载自alan-hjkl.iteye.com/blog/1682985