一步一步实现 iOS App 的搜索功能

Posted by Calvin on 2017-05-04

先看下 App 中实现的搜索效果图:

效果图1

左侧为搜索初始页面,右侧为搜索的结果列表页面,现在说一下实现的思路和步骤:

  1. 点击搜索按钮跳转到搜索页面(包含头部热门搜索视图以及搜索历史视图和清空搜索历史功能)
  2. 搜索框输入内容开始搜索并展示搜索结果列表
  3. 点击搜索列表的某一行跳转到下级页面,并记录搜索历史数据
  4. 如果清空搜索框内的内容,再次展示搜索页面

搜索页面

创建 基于 UIViewController 的 DHSearchViewController 类为搜索页面,当点击搜索按钮时跳转到 DHSearchViewController

创建 TableView 到整个视图中,其中热门搜索标签视图为整个 TableView 的 HeadView,搜索历史数据为 TableView 的表格数据,清空搜索记录按钮为 TableView 的 FootView。

设置搜索页面的 TableView

设置 TableView 以及 HeadView 和 FootView

self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight) style:UITableViewStylePlain];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
// 创建搜索框
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(10, 0, kScreenWidth-20, 20)];
searchBar.placeholder = @"搜索平台";
searchBar.delegate = self;
searchBar.backgroundColor = [UIColor clearColor];
searchBar.showsCancelButton = YES;
searchBar.tintColor = [UIColor blueColor];
self.searchBar = searchBar;
self.navigationItem.titleView = self.searchBar;
// headView
self.headerView = [[UIView alloc] init];
self.headerView.sd_x = 0;
self.headerView.sd_y = 0;
self.headerView.sd_width = kScreenWidth;
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 20, kScreenWidth-20, 30)];
titleLabel.text = @"热门推荐";
titleLabel.font = [UIFont systemFontOfSize:13];
titleLabel.textColor = [UIColor grayColor];
[titleLabel sizeToFit];
[self.headerView addSubview:titleLabel];
self.tagsView = [[UIView alloc] init];
self.tagsView.sd_x = 10;
self.tagsView.sd_y = titleLabel.sd_y+30;
self.tagsView.sd_width = kScreenWidth-20;
[self.headerView addSubview:self.tagsView];
self.tableView.tableHeaderView = self.headerView;
UIView *footView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 40)];
UILabel *footLabel = [[UILabel alloc] initWithFrame:footView.frame];
footLabel.textColor = [UIColor grayColor];
footLabel.font = [UIFont systemFontOfSize:13];
footLabel.userInteractionEnabled = YES;
footLabel.text = @"清空搜索记录";
footLabel.textAlignment = NSTextAlignmentCenter;
[footLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(emptySearchHistoryDidClick)]];
[footView addSubview:footLabel];
self.tableView.tableFooterView = footView;

创建搜索页面的标签视图

根据热门搜索的内容长度设置标签

#pragma mark -- 设置标签
- (void)tagsViewWithTag
{
CGFloat allLabelWidth = 0;
CGFloat allLabelHeight = 0;
int rowHeight = 0;
for (int i = 0; i < self.tagsArray.count; i++) {
if (i != self.tagsArray.count-1) {
CGFloat width = [self getWidthWithTitle:self.tagsArray[i+1] font:[UIFont systemFontOfSize:14]];
if (allLabelWidth + width+10 > self.tagsView.frame.size.width) {
rowHeight++;
allLabelWidth = 0;
allLabelHeight = rowHeight*40;
}
}else{
CGFloat width = [self getWidthWithTitle:self.tagsArray[self.tagsArray.count-1] font:[UIFont systemFontOfSize:14]];
if (allLabelWidth + width+10 > self.tagsView.frame.size.width) {
rowHeight++;
allLabelWidth = 0;
allLabelHeight = rowHeight*40;
}
}
UILabel *rectangleTagLabel = [[UILabel alloc] init];
// 设置属性
rectangleTagLabel.userInteractionEnabled = YES;
rectangleTagLabel.font = [UIFont systemFontOfSize:14];
rectangleTagLabel.textColor = [UIColor whiteColor];
rectangleTagLabel.backgroundColor = RandomColor;
rectangleTagLabel.text = self.tagsArray[i];
rectangleTagLabel.textAlignment = NSTextAlignmentCenter;
[rectangleTagLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tagDidCLick:)]];
CGFloat labelWidth = [self getWidthWithTitle:self.tagsArray[i] font:[UIFont systemFontOfSize:14]];
rectangleTagLabel.layer.cornerRadius = 3;
[rectangleTagLabel.layer setMasksToBounds:YES];
if (labelWidth > kScreenWidth-20) {
labelWidth = kScreenWidth-20;
}
rectangleTagLabel.frame = CGRectMake(allLabelWidth, allLabelHeight, labelWidth, 25);
[self.tagsView addSubview:rectangleTagLabel];
allLabelWidth = allLabelWidth+10+labelWidth;
}
self.tagsView.sd_height = rowHeight*40+40;
self.headerView.sd_height = self.tagsView.sd_y+self.tagsView.sd_height+10;
}

标签的彩色背景从以下色彩池中随机获取

- (NSMutableArray *)colorPol
{
if (!_colorPol) {
NSArray *colorStrPol = @[@"009999", @"0099cc", @"0099ff", @"00cc99", @"00cccc", @"336699", @"3366cc", @"3366ff", @"339966", @"666666", @"666699", @"6666cc", @"6666ff", @"996666", @"996699", @"999900", @"999933", @"99cc00", @"99cc33", @"660066", @"669933", @"990066", @"cc9900", @"cc6600" , @"cc3300", @"cc3366", @"cc6666", @"cc6699", @"cc0066", @"cc0033", @"ffcc00", @"ffcc33", @"ff9900", @"ff9933", @"ff6600", @"ff6633", @"ff6666", @"ff6699", @"ff3366", @"ff3333"];
NSMutableArray *colorPolM = [NSMutableArray array];
for (NSString *colorStr in colorStrPol) {
UIColor *color = [UIColor dh_colorWithHexString:colorStr];
[colorPolM addObject:color];
}
_colorPol = colorPolM;
}
return _colorPol;
}

读取本地保存的搜索历史数据并刷新 TableView

懒加载数组并刷新视图

- (NSMutableArray *)searchHistories
{
if (!_searchHistories) {
self.searchHistoriesCachePath = SEARCH_SEARCH_HISTORY_CACHE_PATH;
_searchHistories = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithFile:self.searchHistoriesCachePath]];
}
return _searchHistories;
}
- (void)setSearchHistoriesCachePath:(NSString *)searchHistoriesCachePath
{
_searchHistoriesCachePath = [searchHistoriesCachePath copy];
// 刷新
self.searchHistories = nil;
[self.tableView reloadData];
}

展示搜索页面完毕后现在实现搜索功能

添加搜索结果页面并默认隐藏

添加搜索结果页面,当搜索框内容为空时默认隐藏搜索结果页。当搜索框内容变化时发送通知,并在搜索结果页面接收通知,添加搜索结果到视图中

- (DHSearchSuggestViewController *)searchSuggestionVC
{
if (!_searchSuggestionVC) {
DHSearchSuggestViewController *searchSuggestionVC = [[DHSearchSuggestViewController alloc] initWithStyle:UITableViewStylePlain];
__weak typeof(self) _weakSelf = self;
searchSuggestionVC.didSelectText = ^(NSString *didSelectText) {
if ([didSelectText isEqualToString:@""]) {
[self.searchBar resignFirstResponder];
}else{
// 设置搜索信息
_weakSelf.searchBar.text = didSelectText;
// 缓存数据并且刷新界面
[_weakSelf saveSearchCacheAndRefreshView];
}
};
searchSuggestionVC.view.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height-64);
searchSuggestionVC.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:searchSuggestionVC.view];
[self addChildViewController:searchSuggestionVC];
_searchSuggestionVC = searchSuggestionVC;
}
return _searchSuggestionVC;
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if ([searchText isEqualToString:@""]) {
self.searchSuggestionVC.view.hidden = YES;
self.tableView.hidden = NO;
}else{
self.searchSuggestionVC.view.hidden = NO;
self.tableView.hidden = YES;
[self.view bringSubviewToFront:self.searchSuggestionVC.view];
//创建一个消息对象
NSNotification * notice = [NSNotification notificationWithName:@"searchBarDidChange" object:nil userInfo:@{@"searchText":searchText}];
//发送消息
[[NSNotificationCenter defaultCenter]postNotification:notice];
}
}

在搜索结果页面接收通知并开始搜索

当搜索框开始搜索时,在搜索结果页面 DHSearchSuggestViewController 接收通知

// 获取通知中心单例对象
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
// 添加当前类对象为一个观察者,name和object设置为nil,表示接收一切通知
[center addObserver:self selector:@selector(handleColorChange:) name:@"searchBarDidChange" object:nil];

接收到通知后根据搜索内容查询数据并展示:

-(void)handleColorChange:(NSNotification* )sender
{
NSString *text = sender.userInfo[@"searchText"];
NSLog(@"%@", text);
// 搜索方法写在这里并刷新数据
}

搜索数据后,隐藏搜索页面的 TableView,展示搜索结果页的 searchSuggestionVC

在搜索结果页面处理对应交互

当用户点击搜索列表的某一行时再进行其他的操作

// 取消选中
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.didSelectText([tableView cellForRowAtIndexPath:indexPath].textLabel.text);
UIViewController *vc = [[UIViewController alloc]init];
[self.navigationController pushViewController:vc animated:YES];

注意点

1、用户点击搜索按钮后,保存搜索数据到本地

- (void)saveSearchCacheAndRefreshView
{
UISearchBar *searchBar = self.searchBar;
// 回收键盘
[searchBar resignFirstResponder];
// 先移除再刷新
[self.searchHistories removeObject:searchBar.text];
[self.searchHistories insertObject:searchBar.text atIndex:0];
// 移除多余的缓存
if (self.searchHistories.count > self.searchHistoriesCount) {
// 移除最后一条缓存
[self.searchHistories removeLastObject];
}
// 保存搜索信息
[NSKeyedArchiver archiveRootObject:self.searchHistories toFile:self.searchHistoriesCachePath];
[self.tableView reloadData];
}

2、用户点击搜索框的关闭按钮时调用 UISearchBar 的代理方法,并隐藏搜索结果页面,展示搜索页面。

3、点击搜索页面清空搜索历史按钮后,清空本地的搜索数据并刷新视图

- (void)emptySearchHistoryDidClick
{
self.tableView.tableFooterView.hidden = YES;
// 移除所有历史搜索
[self.searchHistories removeAllObjects];
// 移除数据缓存
[NSKeyedArchiver archiveRootObject:self.searchHistories toFile:self.searchHistoriesCachePath];
[self.tableView reloadData];
}

4、点击热门搜索标签后,使标签文本填充到搜索框并进行搜索。同时隐藏搜索页面,打开搜索结果页

- (void)tagDidCLick:(UITapGestureRecognizer *)gr
{
UILabel *label = (UILabel *)gr.view;
self.searchBar.text = label.text;
// 缓存数据并且刷新界面
[self saveSearchCacheAndRefreshView];
self.tableView.tableFooterView.hidden = NO;
self.searchSuggestionVC.view.hidden = NO;
self.tableView.hidden = YES;
[self.view bringSubviewToFront:self.searchSuggestionVC.view];
// 创建一个消息对象
NSNotification *notice = [NSNotification notificationWithName:@"searchBarDidChange" object:nil userInfo:@{@"searchText":label.text}];
// 发送消息
[[NSNotificationCenter defaultCenter]postNotification:notice];
}

5、设置历史搜索页面的单条记录清除按钮,当点击按钮后清除单条数据,并刷新视图

- (void)closeDidClick:(UIButton *)sender
{
// 获取当前cell
UITableViewCell *cell = (UITableViewCell *)sender.superview;
// 移除搜索信息
[self.searchHistories removeObject:cell.textLabel.text];
// 保存搜索信息
[NSKeyedArchiver archiveRootObject:self.searchHistories toFile:SEARCH_SEARCH_HISTORY_CACHE_PATH];
if (self.searchHistories.count == 0) {
self.tableView.tableFooterView.hidden = YES;
}
// 刷新
[self.tableView reloadData];
}

附Demo下载链接