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