一、搜索功能我相信很多app都用得上,iOS系統(tǒng)提供做搜索的類有那幾種呢?
1.UISearchBar,這個(gè)相信大多數(shù)開發(fā)者都用過,這個(gè)只是提供搜索框,其實(shí)就是跟一個(gè)UITextField沒啥區(qū)別,只是在textfield封裝了多點(diǎn)內(nèi)容。
2.UISearchDisplayController,這個(gè)玩意是iOS8之前,專門用來(lái)做搜索功能,對(duì)的,它就是一個(gè)控制器,他為你提供蘋果那種搜索風(fēng)格的轉(zhuǎn)場(chǎng)動(dòng)畫以及搜索視圖顯示邏輯等,但是使用起來(lái)相對(duì)復(fù)雜。所以在iOS8這個(gè)類已經(jīng)被拋棄了,而引申一個(gè)新的搜索類,就是以下要介紹的UISearchController。
3.UISearchController,這個(gè)類其實(shí)跟UISearchDisplayController功能差不多,只是蘋果對(duì)其再進(jìn)行了高級(jí)封裝,從而使用起來(lái)更加簡(jiǎn)單了。
二、其實(shí)自己在搞UISearchController之前,我已經(jīng)到網(wǎng)上找過一下相關(guān)的資料,發(fā)現(xiàn)網(wǎng)上寫的都是千遍一律,教你怎么用,就我個(gè)人覺得,難的是你要在它基礎(chǔ)上去對(duì)改它的UI,這就要你對(duì)它的視圖層次結(jié)構(gòu)要很清楚了,那我們下面來(lái)開始干活吧。
我們首先看一下系統(tǒng)自帶的搜索框是怎樣的。

這個(gè)是正常狀態(tài)下的搜索框,但是那灰色的背景真是丑出翔了,試問有那個(gè)產(chǎn)品設(shè)計(jì)能接受這個(gè)樣式的搜索框?我們?cè)倏聪戮庉嫚顟B(tài)下的吧。

從normal到edit狀態(tài)會(huì)有一個(gè)轉(zhuǎn)場(chǎng)動(dòng)畫,雖然界面還是很丑,而我們需要的正式這個(gè)動(dòng)畫,如果我們完全自定義的話還需要做這個(gè)動(dòng)畫就未免有點(diǎn)復(fù)雜了。

當(dāng)你輸入文字是,你發(fā)現(xiàn)它那個(gè)蒙版消失了,而展現(xiàn)新的視圖,沒錯(cuò),那個(gè)就是你自己的搜索結(jié)果控制器視圖了,下面再詳細(xì)介紹。
三、看了上面幾張圖,我們大概知道UISearchController的交互大概怎樣。
那我們?cè)龠M(jìn)去頭文件看一下他的一些屬性和方法吧


它的指定初始化方法就是
```
- (instancetype)initWithSearchResultsController:(nullableUIViewController*)searchResultsController;
```
所以你能定制屬于你自己的搜索結(jié)果view。當(dāng)你傳nil的時(shí)候,默認(rèn)是當(dāng)前view就是搜索結(jié)果view。
UISearchResultsUpdating -> 搜索結(jié)果更新回調(diào)的協(xié)議,實(shí)現(xiàn)里面的方法就能更新搜索邏輯了,這里不詳細(xì)說(shuō),網(wǎng)上一大堆教這個(gè)的。
再說(shuō)下比較常用的屬性吧
```
dimsBackgroundDuringPresentation -> 是否顯示灰色透明的蒙版,默認(rèn)YES
hidesNavigationBarDuringPresentation -> 是否隱藏導(dǎo)航條,這個(gè)一般不需要管,都是隱藏的
searchResultsController -> 就是你初始化傳進(jìn)去的搜索結(jié)果VC
searchBar -> 它內(nèi)部會(huì)創(chuàng)建一個(gè)搜索框給你。
UISearchController就這幾個(gè)屬性了。我們看下先怎么去用?
```
UISearchController *searchController= [[UISearchController alloc] init]; ?
self.tableView.tableHeaderView=searchController.searchBar;
searchController.searchResultsUpdater=self;
searchController.searchBar.delegate=self;
searchController.searchBar.placeholder=@"搜索"; // placeholder
[searchController.searchBar setSearchFieldBackgroundImage:[UIImagehcq_imageNamed:@"business_search_bg"] forState:UIControlStateNormal]; // 設(shè)置搜索框內(nèi)部textField的背景圖
[self.searchBar setBackgroundImage:@""] // 設(shè)置搜索框背景圖,要跟上面的區(qū)分哦,兩者不一樣
[searchController.searchBarsetImage:[UIImagehcq_imageNamed:@"business_search_icon"] forSearchBarIcon:UISearchBarIconSearchstate:UIControlStateNormal];// 設(shè)置搜索框內(nèi)放大鏡圖片
searchController.searchBar.tintColor=KC_RGB_COLOR(225,225,225);// 設(shè)置搜索框內(nèi)按鈕文字顏色,以及搜索光標(biāo)顏色。
searchController.searchBar.barTintColor=HCQ_VIEW_BACKGROUND_COLOR;// 設(shè)置搜索框背景顏色
// Get the instance of the UITextField of the search bar
// 用KVC修改placeholder文字顏色
UITextField*searchField = [self.searchBarvalueForKey:@"_searchField"]; // 先取出textfield
// Change the search bar placeholder text color
[searchFieldsetValue:self.searchBar.tintColorforKeyPath:@"_placeholderLabel.textColor"]; // 然后setValueForKey,搞定
[searchController.searchBarsetValue:@"完成"forKey:@"_cancelButtonText"]; // 設(shè)置搜索框那個(gè)取消按鈕文字
// 如果你不想要搜索框的背景或者希望背景透明,你加上這句代碼吧
[[[searchController.searchBar.subviews.firstObject subviews] firstObject] removeFromSuperview];// 直接把背景imageView干掉。在iOS8,9是沒問題的,7沒測(cè)試過。
```
到這里,UISearchBar的UI就自定義完畢,想了解UISearchBar層次結(jié)構(gòu)的話,可以用Xcode運(yùn)行后打開那個(gè)視圖層次結(jié)構(gòu)看一下就一目了然。
四、下面介紹怎樣定義編輯無(wú)文字狀態(tài)下的搜索UI,例如微信點(diǎn)擊搜索框后會(huì)出現(xiàn)3個(gè)按鈕《朋友圈,文章,公眾號(hào)》。
要定義這些UI,那么我們需要自定義一個(gè)自己的searchController了,創(chuàng)建一個(gè)類MySearchController繼承UISearchController。
首先我們重寫初始化方法
```
- (instancetype)initWithSearchResultsController:(UIViewController*)searchResultsController
{
MyResultViewController *resultVC = [[MyResultViewController ? alloc] init];
if(self= [super initWithSearchResultsController: resultVC]) {
[selfsetup];
}
returnself;
}
- (instancetype)init
{
MyResultViewController *resultVC = [[MyResultViewController? alloc] init];
if(self= [super initWithSearchResultsController:resultVC]) {
? ? ? [selfsetup];
}
returnself;
}
// 重寫init方法,好讓外部怎么創(chuàng)建都是我們自己的搜索結(jié)果控制器。外部使用不需要關(guān)心太多
// 可以把剛才自定義SearchBarUI代碼放到內(nèi)部了。
- (void)setup
{
self.searchBar.placeholder=@"搜索商家";
[self.searchBarsetSearchFieldBackgroundImage:[UIImagehcq_imageNamed:@"business_search_bg"]forState:UIControlStateNormal];
[self.searchBarsetImage:[UIImagehcq_imageNamed:@"business_search_icon"]forSearchBarIcon:UISearchBarIconSearchstate:UIControlStateNormal];
self.searchBar.tintColor=KC_RGB_COLOR(225,225,225);
self.searchBar.barTintColor=HCQ_VIEW_BACKGROUND_COLOR;
// Get the instance of the UITextField of the search bar
UITextField*searchField = [self.searchBarvalueForKey:@"_searchField"];
// Change the search bar placeholder text color
[searchFieldsetValue:self.searchBar.tintColorforKeyPath:@"_placeholderLabel.textColor"];
[[[self.searchBar.subviews.firstObjectsubviews]firstObject]removeFromSuperview];
[self.searchBarsetValue:@"完成"forKey:@"_cancelButtonText"];
}
```
然后來(lái)到viewDidLoad方法,添加我們需要定義的UI。
例如我想加一個(gè)switch到中間
```
UISwitch*st = [UISwitch new];
[self.view addSubview:st];
st.center = self.view.center;
```
一般我們會(huì)這樣寫,運(yùn)行后點(diǎn)擊搜索框進(jìn)入編輯狀態(tài),發(fā)現(xiàn)開關(guān)出來(lái)了,但是當(dāng)你輸入文字的時(shí)候,開關(guān)還在顯示還能點(diǎn)擊。這就神奇了。會(huì)不會(huì)是視圖層級(jí)不對(duì)呢?沒錯(cuò),就是視圖層級(jí)問題,SearchController.view內(nèi)部還有一個(gè)containerView(其實(shí)就是那個(gè)蒙版),而SearchController是把搜索結(jié)果控制器view添加到containerView上,所以開關(guān)跟containerView是同一層次,而且在containerView之上,所以不會(huì)消失。也可以打開層次結(jié)果圖看一下。
我們可以添加一個(gè)屬性
```
@property(nonatomic,weak)UIView*containerView;
```
重寫get方法
```
- (UIView*)containerView
{
if(!_containerView) {
_containerView=self.view.subviews.firstObject;
_containerView.backgroundColor=HCQ_VIEW_BACKGROUND_COLOR;
}
return_containerView;
}
```
然后在viewdidload中添加開關(guān)的代碼改一下
```
[self.containerView addSubview:st];
```
繼續(xù)運(yùn)行,發(fā)現(xiàn)還是不行,還是不會(huì)消失,為什么呢?我們知道view的subview有先后順序,后添加的越在上面,因?yàn)槟愕乃阉鹘Y(jié)果view先添加到containerView,開關(guān)是后面加的,固然開關(guān)還是能看到。所以,你不能直接add,正確姿勢(shì)是插入insert
```
[self.containerView insertSubview:st atIndex:0];
```
這樣就沒問題了。
再補(bǔ)充一點(diǎn),如果你想改containerView的frame,可以在這個(gè)方法修改
```
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
}
```
如果想做一些轉(zhuǎn)場(chǎng)過度動(dòng)畫,那么重寫viewWillAppear、viewWillDisappear在里邊做吧
例如
```
- (void)viewWillAppear:(BOOL)animated
{
[superviewWillAppear:animated];
// 修改textfield背景圖
[self.searchBar setSearchFieldBackgroundImage:[UIImagehcq_imageNamed:@"business_search_bg_highlighted"] forState:UIControlStateNormal];
}
- (void)viewWillDisappear:(BOOL)animated
{
[superviewWillDisappear:animated];
// 這里再改回來(lái)
[self.searchBar setSearchFieldBackgroundImage:[UIImagehcq_imageNamed:@"business_search_bg"]forState:UIControlStateNormal];
}
```
如果當(dāng)狀態(tài)為activity的時(shí)候想隱藏tabbar,建議實(shí)現(xiàn)UISearchControllerDelegate的代理方法
```
func willPresentSearchController(_searchController:UISearchController) {
tabBarController?.tabBar.isHidden=true
}
func willDismissSearchController(_searchController:UISearchController) {
tabBarController?.tabBar.isHidden=false
}
然后在viewwillAppear將tabbar再次隱藏,否則pop回來(lái)的時(shí)候又會(huì)出現(xiàn)
override func viewWillAppear(_animated:Bool) {
super.viewWillAppear(animated)
if searchController.isActive{
? ? ?tabBarController?.tabBar.isHidden = true
}
}
```
如果想push的時(shí)候把搜索框也跟著隱藏
那么在導(dǎo)航的棧頂控制器寫下
definesPresentationContext = true
必須要在棧頂控制器寫,而且push下一個(gè)子控制器的時(shí)候也要在導(dǎo)航棧里Push,否則會(huì)有莫名其妙的Bug
五、結(jié)語(yǔ)
基本上按照上面所說(shuō)的就能自定義基于系統(tǒng)的,屬于自己風(fēng)格的搜索框了。用系統(tǒng)提供的轉(zhuǎn)場(chǎng)動(dòng)畫還是挺好看的。定制起來(lái)也比較簡(jiǎn)單,有這個(gè)需求的可以參考下我寫的這編文章。