UISearchController的使用以及模仿京東、美團(tuán)搜索框的實(shí)現(xiàn)

UISearchController 是 iOS 8 之后推出的用于管理搜索事件的控件掀泳, 在使用該控件的過程中要注意很多坑雪隧,下面我將帶領(lǐng)大家來一步步的學(xué)習(xí)該如何使用該控件,并且最后帶領(lǐng)大家來模仿一下京東首頁(yè)的搜索框以及美團(tuán)的地址搜索框,具體如下圖:

jdSearchHistory.gif

廢話不多說 我們開始

基本使用

iOS 11.0 之前版本

1. 結(jié)果控制器 是nil

定義 tableViewsearchController 兩個(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)

Demo鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梦湘,一起剝皮案震驚了整個(gè)濱河市瞎颗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捌议,老刑警劉巖哼拔,帶你破解...
    沈念sama閱讀 211,423評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瓣颅,居然都是意外死亡倦逐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門宫补,熙熙樓的掌柜王于貴愁眉苦臉地迎上來檬姥,“玉大人,你說我怎么就攤上這事粉怕〗∶瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,019評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵贫贝,是天一觀的道長(zhǎng)秉犹。 經(jīng)常有香客問我,道長(zhǎng)稚晚,這世上最難降的妖魔是什么崇堵? 我笑而不...
    開封第一講書人閱讀 56,443評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮客燕,結(jié)果婚禮上鸳劳,老公的妹妹穿的比我還像新娘。我一直安慰自己幸逆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評(píng)論 6 385
  • 文/花漫 我一把揭開白布暮现。 她就那樣靜靜地躺著还绘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栖袋。 梳的紋絲不亂的頭發(fā)上拍顷,一...
    開封第一講書人閱讀 49,798評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音塘幅,去河邊找鬼昔案。 笑死尿贫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的踏揣。 我是一名探鬼主播庆亡,決...
    沈念sama閱讀 38,941評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼捞稿!你這毒婦竟也來了又谋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤娱局,失蹤者是張志新(化名)和其女友劉穎彰亥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衰齐,經(jīng)...
    沈念sama閱讀 44,152評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡任斋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耻涛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片废酷。...
    茶點(diǎn)故事閱讀 38,629評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖犬第,靈堂內(nèi)的尸體忽然破棺而出锦积,到底是詐尸還是另有隱情,我是刑警寧澤歉嗓,帶...
    沈念sama閱讀 34,295評(píng)論 4 329
  • 正文 年R本政府宣布丰介,位于F島的核電站,受9級(jí)特大地震影響鉴分,放射性物質(zhì)發(fā)生泄漏哮幢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評(píng)論 3 313
  • 文/蒙蒙 一志珍、第九天 我趴在偏房一處隱蔽的房頂上張望橙垢。 院中可真熱鬧,春花似錦伦糯、人聲如沸柜某。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喂击。三九已至,卻和暖如春淤翔,著一層夾襖步出監(jiān)牢的瞬間翰绊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留监嗜,地道東北人谐檀。 一個(gè)月前我還...
    沈念sama閱讀 46,333評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像裁奇,于是被迫代替她去往敵國(guó)和親桐猬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容