寫在前面
這兩天想給app加一個搜索功能,打算采用系統自帶的UISearch等一系列控件。
后來發現在iOS8前后,使用也是不一樣的。
UISearchBar + UISearchDisplayController 是iOS8之前的常用寫法。
而在iOS8之后,就開始采用UISearchController。
昨天和今天主要是在嘗試iOS8之前那種方式時,始終出不來效果。直到今天才是解決了,還是寫篇文章記錄下,防止以后忘記了。
Before iOS8
在iOS8之前,搜索功能可以只用SearchBar,具體的Search功能實現就可以卸載SearchBar的代理中,具體實現就不多說了。
但是我覺得通過UISearchBar + UISearchDisplayController 的一起使用,可以獲得iOS系統上更原生的體驗,如本文開頭動圖所示。
UISearchBar
UISearchBar通常就是我們看到的那個輸入框,具體的其屬性配置不多說了,可以查看官方文檔
UISearchDisplayController
UISearchDisplayController我覺得有點像一個UITableViewController,它也有Tableview(searchResultTableView),有dataSource(searchResultsDataSource),也有delegate(searchResultsDelegate)。
而且它的dataSource是和我們展示所用的普通Tableview的dataSource是一樣的。
所以說UISearchDisplayController其實就是一個單純展示搜索結果的UITableViewController,只是蘋果幫我們很好的封裝了起來。
官方的指南寫法也是如此:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//直接在dataSource中判斷當前是哪個TableView
if (tableView == self.tableView) {
return ...;
}
// If necessary (if self is the data source for other table views),
// check whether tableView is searchController.searchResultsTableView.
return ...;
}
demoBefore iOS8
直接上完整代碼:
#import "SearchVBefore8.h"
@interface SearchVBefore8 ()<UISearchBarDelegate,UISearchDisplayDelegate>
/**數據源*/
@property (nonatomic, strong) NSArray *dataArray;
/**經過搜索之后的數據源*/
@property (nonatomic, strong) NSArray *searchResultArray;
/**我們的UISearchDisplayController*/
@property (nonatomic, strong) UISearchDisplayController *displayer;
@end
@implementation SearchVBefore8
- (NSArray *)getDataArray
{
/**模擬一組數據*/
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
for (int i = 0; i< 20; i++) {
NSString *dataString = [NSString stringWithFormat:@"%d",i];
[resultArray addObject:dataString];
}
return resultArray;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupSearchBar];
self.dataArray = [self getDataArray];
}
- (void)setupSearchBar{
/**配置Search相關控件*/
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
self.tableView.tableHeaderView = searchBar;
self.displayer = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
/**searchBar的delegate看需求進行配置*/
searchBar.delegate = self;
/**以下都比較重要,建議都設置好代理*/
self.displayer.searchResultsDataSource = self;
self.displayer.searchResultsDelegate = self;
self.displayer.delegate = self;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
/**對TableView進行判斷,如果是搜索結果展示視圖,返回不同結果*/
if (tableView == self.displayer.searchResultsTableView) {
return self.searchResultArray.count;
}
else{
return self.dataArray.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"mainCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"mainCell"];
}
/**對TableView進行判斷,如果是搜索結果展示視圖,返回不同數據源*/
if (tableView == self.displayer.searchResultsTableView) {
cell.textLabel.text = [NSString stringWithFormat:@"%@",self.searchResultArray[indexPath.row]];
}
else{
cell.textLabel.text = [NSString stringWithFormat:@"%@",self.dataArray[indexPath.row]];
}
return cell;
}
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
NSLog(@"begin");
return YES;
}
-(BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
NSLog(@"end");
return YES;
}
/**UISearchDisplayController的代理實現*/
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
/**通過謂詞修飾的方式來查找包含我們搜索關鍵字的數據*/
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"self contains[cd] %@",searchString];
self.searchResultArray = [self.dataArray filteredArrayUsingPredicate:resultPredicate];
return YES;
}
之前主要一直看不到結果是因為有self.searchDisplayController這個默認屬性的存在,所以配置都是用在了self.searchDisplayController上。至今仍然很奇怪為什么,這是之前的代碼:
UISearchDisplayController *displayerControllr = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
displayerControllr.searchResultsDataSource = self;
displayerControllr.searchResultsDelegate = self;
displayerControllr.delegate = self;
NSLog(@"%@ 和 %@",displayerControllr,self.searchDisplayController);
我們看后臺打印:
2016-05-06 14:49:07.546 SearchBarDemo[26008:3948409] <UISearchDisplayController: 0x7fe3e3445870> 和 <UISearchDisplayController: 0x7fe3e3445870>
兩者地址也是一樣的,所以不明白為什么不起作用。當換回自己寫的新屬性self.displayer的時候就可以了,而且self.displayer和self.searchDisplayController的地址也是一樣的。
以上只是粗略demo,但是功能實現了。如果要美化或者添加功能的再自己豐富下吧。
UISearchController
它的init方法:
- (instancetype)initWithSearchResultsController:(nullable UIViewController *)searchResultsController;
它不用像iOS8之前那樣我們還需要單獨寫一個UISearchBar,UISearchController在我們創建的時候會自己生成一個UISearchBar,而UISearchController本身更像是一個容器與膠水,它把我們當前的ViewController與結果展示的ViewController相連一起,相較之于之前的UISearchDisplayController相比來說,這樣的方式可以讓我們更能去高度定制我們所需要的結果展示ViewController。
searchResultsUpdater
更新代理,負責通知searchResultsController進行更新。
相比較來說,個人覺得iOS8之后的配置會顯得更加容易方便一點。
demoAfteriOS8
在主界面實現文件中:
#import "MainTableViewController.h"
#import "Product.h"
#import "MySearchTableViewController.h"
@interface MainTableViewController ()<UISearchResultsUpdating,UISearchBarDelegate>
@property(nonatomic,strong)NSArray *allProducts;
/**搜索結果ViewController*/
@property(nonatomic,strong)MySearchTableViewController *mySRTVC;
@property(nonatomic,strong)UISearchController *svc;
@end
@implementation MainTableViewController
-(NSArray *)allProducts
{
if (!_allProducts) {
_allProducts=[Product demoData];
}
return _allProducts;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
//創建兩個屬性實例
self.mySRTVC=[[MySearchTableViewController alloc]init];
self.svc=[[UISearchController alloc]initWithSearchResultsController:self.mySRTVC];
//設置與界面有關的樣式
[self.svc.searchBar sizeToFit]; //大小調整
self.tableView.tableHeaderView=self.svc.searchBar;
//設置搜索控制器的結果更新代理對象
self.svc.searchResultsUpdater=self;
//Scope:就是效果圖中那個分類選擇器
self.svc.searchBar.scopeButtonTitles=@[@"設備",@"軟件",@"其他"];
//為了響應scope改變時候,對選中的scope進行處理 需要設置search代理
self.svc.searchBar.delegate=self;
self.definesPresentationContext=YES; //迷之屬性,打開后搜索結果頁界面顯示會比較好。
//看文檔貌似是頁面轉換模式為UIModalPresentationCurrentContext,如果該選項打開,那么就會使用當前ViewController的一個presentContenxt
//否則就向父類中進行尋找并使用。
}
/**普通的tableview展示實現。*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allProducts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
Product *p=self.allProducts[indexPath.row];
cell.textLabel.text=p.name;
return cell;
}
#pragma mark - UISearchResultsUpdating
/**實現更新代理*/
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
//獲取scope被選中的下標
NSInteger selectedType=searchController.searchBar.selectedScopeButtonIndex;
//獲取到用戶輸入的數據
NSString *searchText=searchController.searchBar.text;
NSMutableArray *searchResult=[[NSMutableArray alloc]init];
for (Product *p in self.allProducts) {
NSRange range=[p.name rangeOfString:searchText];
if (range.length>0&&p.type==selectedType) {
[searchResult addObject:p];
}
}
self.mySRTVC.searchProducts=searchResult;
/**通知結果ViewController進行更新*/
[self.mySRTVC.tableView reloadData];
}
#pragma mark - UISearchBarDelegate
/**點擊按鈕后,進行搜索頁更新*/
-(void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
[self updateSearchResultsForSearchController:self.svc];
}
@end
在搜索結果頁面中(就是一個普通Tableview的展示):
#import "MySearchTableViewController.h"
#import "Product.h"
@interface MySearchTableViewController ()
@end
@implementation MySearchTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"mycell"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.searchProducts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"mycell" forIndexPath:indexPath];
Product *p=self.searchProducts[indexPath.row];
cell.textLabel.text=p.name;
return cell;
}
@end
———————————分割線—————————————
關于評論中有人提出搜索結果頁(MySearchTableViewController)不能Push到下一個界面的問題,補上一點漏掉的內容。
個人覺得MySearchTableViewController只是作為一個childViewController被添加到MainTableViewController上面,所以它本身是沒有被壓入navigationController的棧中,因此簡單的在MySearchTableViewController中調用
[self.navigationController pushViewController:someVC animated:YES];
是不行的,因此self.navigationController是空的。
本例中可以這樣解決:
@interface MySearchTableViewController : UIViewController
@property (nonatomic, strong) NSArray *resultDataArray;
//在MySearchTableViewController添加一個指向展示頁的【弱】引用屬性。
@property (nonatomic, weak) UIViewController *mainSearchController;
@end
//然后在mainSearchController的創建MySearchTableViewController實例的代碼中添加下面一句:
self.mySRTVC=[[MySearchTableViewController alloc]init];
self.mySRTVC.mainSearchController = self;
//最后在想要實現push的地方這樣實現
[self.mainSearchController.navigationController pushViewController:detailViewController animated:YES];
———————————2017.1.5更新—————————————
貼一個demo地址:
iOSSearchBarDemo