【轉】如何基於UISearchController深度自定義搜尋UI

weixin_34107955發表於2017-04-26

最近看UISearchController,看到這個小哥的文章內容不錯,就是格式實在沒有看下去的慾望,所以自己改了下,看的更清晰了點,原文可以看這裡

一、搜尋功能我相信很多app都用得上,iOS系統提供做搜尋的類有那幾種呢?
1.UISearchBar,這個相信大多數開發者都用過,這個只是提供搜尋框,其實就是跟一個UITextField沒啥區別,只是在textfield封裝了多點內容。
2.UISearchDisplayController,這個玩意是iOS8之前,專門用來做搜尋功能,對的,它就是一個控制器,他為你提供蘋果那種搜尋風格的轉場動畫以及搜尋檢視顯示邏輯等,但是使用起來相對複雜。所以在iOS8這個類已經被拋棄了,而引申一個新的搜尋類,就是以下要介紹的UISearchController。
3.UISearchController,這個類其實跟UISearchDisplayController功能差不多,只是蘋果對其再進行了高階封裝,從而使用起來更加簡單了。
二、其實自己在搞UISearchController之前,我已經到網上找過一下相關的資料,發現網上寫的都是千遍一律,教你怎麼用,就我個人覺得,難的是你要在它基礎上去對改它的UI,這就要你對它的檢視層次結構要很清楚了,那我們下面來開始幹活吧。
我們首先看一下系統自帶的搜尋框是怎樣的。

2195474-6b1a92d7d952637a.png

normal狀態下的searchBar

這個是正常狀態下的搜尋框,但是那灰色的背景真是醜出翔了,試問有那個產品設計能接受這個樣式的搜尋框?我們再看下編輯狀態下的吧。

2195474-bc53a693471d692e.png

編輯無文字狀態下

從normal到edit狀態會有一個轉場動畫,雖然介面還是很醜,而我們需要的正式這個動畫,如果我們完全自定義的話還需要做這個動畫就未免有點複雜了。

2195474-cf091393286f61a7.png

編輯有文字狀態

當你輸入文字是,你發現它那個蒙版消失了,而展現新的檢視,沒錯,那個就是你自己的搜尋結果控制器檢視了,下面再詳細介紹。
三、看了上面幾張圖,我們大概知道UISearchController的互動大概怎樣。
那我們再進去標頭檔案看一下他的一些屬性和方法吧


2195474-c4c7ea800fae06e4.png

2195474-4eb9a2f94c2695dd.png

它的指定初始化方法就是

- (instancetype)initWithSearchResultsController:(nullableUIViewController*)searchResultsController;

所以你能定製屬於你自己的搜尋結果view。當你傳nil的時候,預設是當前view就是搜尋結果view。
UISearchResultsUpdating -> 搜尋結果更新回撥的協議,實現裡面的方法就能更新搜尋邏輯了,這裡不詳細說,網上一大堆教這個的。
再說下比較常用的屬性吧

dimsBackgroundDuringPresentation -> 是否顯示灰色透明的蒙版,預設YES
hidesNavigationBarDuringPresentation -> 是否隱藏導航條,這個一般不需要管,都是隱藏的
searchResultsController -> 就是你初始化傳進去的搜尋結果VC
searchBar -> 它內部會建立一個搜尋框給你。

UISearchController就這幾個屬性了。我們看下先怎麼去用?

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]; // 設定搜尋框內部textField的背景圖
[self.searchBar setBackgroundImage:@""] // 設定搜尋框背景圖,要跟上面的區分哦,兩者不一樣
[searchController.searchBar setImage:[UIImagehcq_imageNamed:@"business_search_icon"] for SearchBarIcon:UISearchBarIcon Searchstate:UIControlStateNormal];// 設定搜尋框內放大鏡圖片
searchController.searchBar.tintColor=KC_RGB_COLOR(225,225,225);// 設定搜尋框內按鈕文字顏色,以及搜尋游標顏色。
searchController.searchBar.barTintColor=HCQ_VIEW_BACKGROUND_COLOR;// 設定搜尋框背景顏色
// Get the instance of the UITextField of the search bar
// 用KVC修改placeholder文字顏色
UITextField*searchField = [self.searchBar valueForKey:@"_searchField"]; // 先取出textfield
// Change the search bar placeholder text color
[searchField setValue:self.searchBar.tintColorforKeyPath:@"_placeholderLabel.textColor"]; // 然後setValueForKey,搞定
[searchController.searchBar setValue:@"完成"forKey:@"_cancelButtonText"]; // 設定搜尋框那個取消按鈕文字
// 如果你不想要搜尋框的背景或者希望背景透明,你加上這句程式碼吧
[[[searchController.searchBar.subviews.firstObject subviews] firstObject] removeFromSuperview];// 直接把背景imageView幹掉。在iOS8,9是沒問題的,7沒測試過。

到這裡,UISearchBar的UI就自定義完畢,想了解UISearchBar層次結構的話,可以用Xcode執行後開啟那個檢視層次結構看一下就一目瞭然。
四、下面介紹怎樣定義編輯無文字狀態下的搜尋UI,例如微信點選搜尋框後會出現3個按鈕《朋友圈,文章,公眾號》。
要定義這些UI,那麼我們需要自定義一個自己的searchController了,建立一個類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方法,好讓外部怎麼建立都是我們自己的搜尋結果控制器。外部使用不需要關心太多
// 可以把剛才自定義SearchBarUI程式碼放到內部了。
- (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"];
}

然後來到viewDidLoad方法,新增我們需要定義的UI。
例如我想加一個switch到中間

UISwitch*st = [UISwitch new];
[self.view addSubview:st];
st.center = self.view.center;

一般我們會這樣寫,執行後點選搜尋框進入編輯狀態,發現開關出來了,但是當你輸入文字的時候,開關還在顯示還能點選。這就神奇了。會不會是檢視層級不對呢?沒錯,就是檢視層級問題,SearchController.view內部還有一個containerView(其實就是那個蒙版),而SearchController是把搜尋結果控制器view新增到containerView上,所以開關跟containerView是同一層次,而且在containerView之上,所以不會消失。也可以開啟層次結果圖看一下。
我們可以新增一個屬性

@property(nonatomic,weak)UIView*containerView;

重寫get方法

- (UIView*)containerView
{
    if(!_containerView) {
        _containerView=self.view.subviews.firstObject;
        _containerView.backgroundColor=HCQ_VIEW_BACKGROUND_COLOR;
    }
    return_containerView;
}

然後在viewdidload中新增開關的程式碼改一下

[self.containerView addSubview:st];

繼續執行,發現還是不行,還是不會消失,為什麼呢?我們知道view的subview有先後順序,後新增的越在上面,因為你的搜尋結果view先新增到containerView,開關是後面加的,固然開關還是能看到。所以,你不能直接add,正確姿勢是插入insert

[self.containerView insertSubview:st atIndex:0];

這樣就沒問題了。
再補充一點,如果你想改containerView的frame,可以在這個方法修改

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
}

如果想做一些轉場過度動畫,那麼重寫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];
    // 這裡再改回來
    [self.searchBar setSearchFieldBackgroundImage:[UIImagehcq_imageNamed:@"business_search_bg"]forState:UIControlStateNormal];
}

如果當狀態為activity的時候想隱藏tabbar,建議實現UISearchControllerDelegate的代理方法

func willPresentSearchController(_searchController:UISearchController) {
    tabBarController?.tabBar.isHidden=true
}
func willDismissSearchController(_searchController:UISearchController) {
    tabBarController?.tabBar.isHidden=false
}
然後在viewwillAppear將tabbar再次隱藏,否則pop回來的時候又會出現
override func viewWillAppear(_animated:Bool) {
    super.viewWillAppear(animated)
    if searchController.isActive{
             tabBarController?.tabBar.isHidden = true
    }
}

如果想push的時候把搜尋框也跟著隱藏
那麼在導航的棧頂控制器寫下
definesPresentationContext = true
必須要在棧頂控制器寫,而且push下一個子控制器的時候也要在導航棧裡Push,否則會有莫名其妙的Bug
五、結語
基本上按照上面所說的就能自定義基於系統的,屬於自己風格的搜尋框了。用系統提供的轉場動畫還是挺好看的。定製起來也比較簡單,有這個需求的可以參考下我寫的這編文章。

相關文章