UISearchController
是 iOS 8 之后推出的用于管理搜索事件的控件掀泳, 在使用該控件的過程中要注意很多坑雪隧,下面我將帶領(lǐng)大家來一步步的學(xué)習(xí)該如何使用該控件,并且最后帶領(lǐng)大家來模仿一下京東首頁(yè)的搜索框以及美團(tuán)的地址搜索框,具體如下圖:
廢話不多說 我們開始
基本使用
iOS 11.0 之前版本
1. 結(jié)果控制器 是nil
定義 tableView
和 searchController
兩個(gè)屬性,以及遵守相關(guān)的協(xié)議:
@interface GSNormalSearchVC ()<UITableViewDelegate员舵,UITableViewDataSource脑沿,UISearchResultsUpdating,UISearchControllerDelegate>{
NSArray *_dataArray;//數(shù)據(jù)源
NSArray *_filterArray;
}
@property (nonatomic马僻, strong) UITableView *tableView;
@property (nonatomic庄拇, strong) UISearchController *searchController;
對(duì)這兩個(gè)屬性懶加載:
#pragma mark --getter method
-(UISearchController *)searchController{
if (!_searchController) {
//創(chuàng)建UISearchController,當(dāng)ResultsController為nil的時(shí)候當(dāng)前控制器就是結(jié)果控制器
_searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
_searchController.hidesNavigationBarDuringPresentation = YES;//當(dāng)搜索框激活時(shí)韭邓, 是否隱藏導(dǎo)航條 default is YES;
_searchController.dimsBackgroundDuringPresentation = YES;//當(dāng)搜索框激活時(shí)措近, 是否添加一個(gè)透明視圖 default is YES
_searchController.searchBar.placeholder = @"查找您所在的區(qū)域";//搜索框占位符
_searchController.delegate = self;
_searchController.searchResultsUpdater = self;
}
return _searchController;
}
-(UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.tableFooterView = [[UIView alloc] init];
_tableView.tableHeaderView = self.searchController.searchBar;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellID];
}
return _tableView;
}
實(shí)現(xiàn)遵守的協(xié)議:
#pragma mark --UISearchResultsUpdating
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF CONTAINS %@)",searchController.searchBar.text];
_filterArray = [_dataArray filteredArrayUsingPredicate:predicate];
_filterArray = searchController.searchBar.text.length > 0 ? _filterArray : _dataArray;
[self.tableView reloadData];
}
#pragma mark --UISearchControllerDelegate
-(void)willPresentSearchController:(UISearchController *)searchController{
NSLog(@"將要彈出searchController");
}
-(void)willDismissSearchController:(UISearchController *)searchController{
NSLog(@"將要消失searchController");
}
#pragma mark --UITableViewDataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _filterArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID forIndexPath:indexPath];
cell.textLabel.text = _filterArray[indexPath.row];
return cell;
}
效果圖大致如下:
如果此時(shí)我們嘗試著將 automaticallyAdjustsScrollViewInsets
設(shè)置為 NO
的時(shí)候女淑,我們可以看到當(dāng)搜索控件處于激活狀態(tài)的時(shí)候 SearchBar
和底部?jī)?nèi)容之間的間距突然就拉大了瞭郑,具體如下圖所示:
所以這也告訴我們一個(gè)問題 在使用 UISearchController
的時(shí)候切記不要輕易的 改變 automaticallyAdjustsScrollViewInsets
內(nèi)部結(jié)構(gòu)
通過 Debug View Hierarchy
來看看此時(shí)它的內(nèi)部結(jié)構(gòu)是怎樣的.
當(dāng) UISearchController 未處于激活狀態(tài)
下的結(jié)構(gòu)如下圖所示:
當(dāng)處于激活狀態(tài)下
,通過下圖我們可以看到多了幾個(gè)View: UIDimmingView
UISearchBarContainerView
通過了解 UISearchController
的內(nèi)部結(jié)構(gòu)有助于下文我們自定義一個(gè)屬于我們自己的搜索控件 鸭你, 具體細(xì)節(jié) 后文會(huì)詳細(xì)介紹
自定義 結(jié)果控制器
如果我們想自定義一個(gè) 結(jié)果控制器 我們可以在創(chuàng)建 UISearchController
的時(shí)候直接指定即可:
GSSearchResultVC *resultVC = [[GSSearchResultVC alloc] init];
_searchController = [[UISearchController alloc] initWithSearchResultsController:resultVC];
然后在 updateSearchResultsForSearchController:
中將搜索到的結(jié)果傳遞給結(jié)果控制器即可:
#pragma mark --UISearchResultsUpdating
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF CONTAINS %@)"屈张,searchController.searchBar.text];
_filterArray = [_dataArray filteredArrayUsingPredicate:predicate];
_filterArray = searchController.searchBar.text.length > 0 ? _filterArray : _dataArray;
GSSearchResultVC *resultVC = (GSSearchResultVC *) searchController.searchResultsController;
resultVC.filterDataArray = _filterArray;
// [self.tableView reloadData];
}
運(yùn)行效果大致如圖所示:
此時(shí)我們便遇到了使用過程中的第一個(gè)坑 相信細(xì)心的朋友已經(jīng)發(fā)現(xiàn)了:當(dāng)搜索框處于激活狀態(tài)的時(shí)候 SearchBar和搜索到的結(jié)果之間的距離越來越大.
而且當(dāng)UISearchController
處于active
狀態(tài)下用戶push到下一個(gè)控制器的時(shí)候 SearchBar 卻仍然仍留在界面上
那么該如何解決上述問題呢?
此時(shí)我們只需要將 self.definesPresentationContext = YES;
即可,添加該行代碼之后運(yùn)行起來一切正常.
內(nèi)部結(jié)構(gòu)圖
跟上面的非常類似袱巨,只是當(dāng) UISearchController
處于激活狀態(tài)的時(shí)候阁谆,我們看到 UISearchControllerView
中多了一個(gè) UIView,該View其實(shí)就是 resultViewController.view愉老,如下圖所示:
注意
在使用UISearchController
的時(shí)候, 我們需要將其設(shè)置為全局變量或者控制器屬性, 使其生命周期與控制器相同; 如果設(shè)置為局部變量, 則會(huì)提前銷毀, 導(dǎo)致無(wú)法使用.
iOS 11.0 的新變化
我們將上述代碼在 iOS 11 的環(huán)境下運(yùn)行起來以后 如下圖所示:
上圖是在iOS 11 環(huán)境下的一點(diǎn)小變化场绿, 我相信細(xì)心的朋友已經(jīng)發(fā)現(xiàn)問題了, 當(dāng)搜索框處于
active
的狀態(tài)的時(shí)候 默認(rèn)情況下 第一行和 searchBar
之間的間距變大了嫉入,我們通過 Debug View Hierarchy
來看看結(jié)構(gòu)發(fā)生了什么變化裳凸,為什么會(huì)這樣呢?
通過上圖我們發(fā)現(xiàn)在 iOS 11
環(huán)境中 贱鄙, 激活狀態(tài)下 此時(shí) UISearchBar
已經(jīng)不是添加 到 UISearchControllerView
中 而是被添加到了 導(dǎo)航欄中了,所以為了解決這個(gè)問題 我們需要單獨(dú)的適配iOS 11
if (@available(iOS 11姨谷, *)) {
self.navigationItem.searchController = self.searchCtrl;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
} else {
self.tableView.tableHeaderView = self.searchCtrl.searchBar;
}
searchBar 個(gè)性化設(shè)置
///將Cancel 修改為 取消
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitle:@"取消"];
//修改取消文字顏色以及光標(biāo)的顏色
self.searchCtrl.searchBar.tintColor = [UIColor redColor];
//textField 設(shè)置圓角
UITextField *textField = (UITextField *)[self.searchCtrl.searchBar valueForKey:@"_searchField"];
textField.backgroundColor = [UIColor whiteColor];
textField.layer.borderColor = [UIColor redColor].CGColor;
textField.layer.borderWidth = 2.f;
textField.layer.cornerRadius = 14.f;
textField.placeholder = @"大家好";
textField.tintColor = [UIColor blueColor];
textField.clipsToBounds = YES;
//去除灰色背景
if (@available(iOS 11逗宁, *)) {
for (UIView *view in textField.subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UISearchBarSearchFieldBackgroundView")]) {
[view removeFromSuperview];
}
}
}
//取消上下兩條線
for (UIView *view in self.searchCtrl.searchBar.subviews.firstObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
[view removeFromSuperview];
}
}
//調(diào)節(jié)放大鏡的位置
[self.searchCtrl.searchBar setPositionAdjustment:UIOffsetMake(100.f, 0.f) forSearchBarIcon:UISearchBarIconSearch];
京東搜索框
下面我們通過模仿京東首頁(yè)的搜索框來強(qiáng)化一下 UISearchController
的結(jié)構(gòu) 具體效果如下圖:
通過上圖我們可以看到這個(gè)搜索框自帶一些動(dòng)畫效果,而系統(tǒng)并沒有為我們提供這些動(dòng)畫效果,為了更方便的實(shí)現(xiàn)上述效果 這里我采取了自定義 SearchController的方式 這樣我們實(shí)現(xiàn)起來也就更加靈活 也不用去適配 SearchController在不同版本下的差異了.
首先我們自定義一個(gè)繼承自 UIViewController
的控制器,在該控制器中 定義兩個(gè)代理方法(其實(shí)就是從UISearchController
中拿過來的,稍微改了下前綴) 如下:
@class GSJDSearchVC;
@protocol GSSearchControllerDelegate <NSObject>
@optional
- (void)didPresentSearchController:(GSJDSearchVC *)searchController;
- (void)didDismissSearchController:(GSJDSearchVC *)searchController;
@end
@protocol GSSearchResultsUpdating <NSObject>
@required
// Called when the search bar's text or scope has changed or when the search bar becomes first responder.
- (void)updateSearchResultsForSearchController:(GSJDSearchVC *)searchController;
@end
@interface GSJDSearchVC : UIViewController
@property (nullable, nonatomic, weak) id <GSSearchResultsUpdating> searchResultsUpdater;
@property (nullable, nonatomic, weak) id <GSSearchControllerDelegate> delegate;
@property (nonatomic, strong, readonly) GSJDSearchBar *searchBar;
@property (nullable, nonatomic, strong, readonly) UIViewController *searchResultsController;
- (instancetype)initWithSearchResultsController:(nullable UIViewController *)searchResultsController;
@end
然后在initWithSearchResultsController:
方法中將結(jié)果控制器添加進(jìn)來
-(instancetype)initWithSearchResultsController:(UIViewController *)searchResultsController{
self = [super init];
if (searchResultsController) {
_searchResultsController = searchResultsController;
CGFloat yCoordinate = 64.f;
if (GS_iPhoneX) {
yCoordinate = 88.f;
}
self.view.frame = CGRectMake(0.f, yCoordinate, SCREENSIZE.width, SCREENSIZE.height);
[self addChildViewController:_searchResultsController];
_searchResultsController.view.frame = self.view.bounds;
[self.view addSubview:_searchResultsController.view];
}
return self;
}
最后根據(jù) SearchBar
的內(nèi)容來決定是否將 GSJDSearchVC
添加到控制器中
- (void)textChange{
if (self.searchBar.text.length == 1 && self.delegate) {
[self.delegate didPresentSearchController:self];
}
if (self.searchBar.text.length == 0 && self.delegate) {
[self.delegate didDismissSearchController:self];
}
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResultsForSearchController:self];
}
}
這里我只摘了部分代碼 感興趣的同學(xué)可以去 GitHub 看看具體的代碼實(shí)現(xiàn)