[iOS]系統UISearchController詳解

UISearchControlleriOS 8 之后推出的一個新的控件, 用于創建搜索條, 及管理搜索事件, 使用這個, 我們可以很容易的創建屬于自己的搜索框, 下面就來看看這個控件的一些使用.

一. 基本使用( 同一個控制器 )

UISearchController 一般是和 UITableView 結合使用, 很少會單獨使用他, 而且使用 UITableView 來展示數據, 也是最佳的選擇.
他的API十分簡單:

// 初始化方法, 參數是展示搜索結果的控制器, 如果是在當前控制器展示搜索結果, 就傳nil
- (instancetype)initWithSearchResultsController:(nullable UIViewController *)searchResultsController;

// 負責更新搜索結果的代理, 需要遵循 UISearchResultsUpdating 協議
@property (nullable, nonatomic, weak) id <UISearchResultsUpdating> searchResultsUpdater;

// 搜索控制器是否是活躍狀態, 當在一個控制器展示搜索結果的時候, 可以此來判斷返回的數據源
@property (nonatomic, assign, getter = isActive) BOOL active;
// 控制器代理  遵循 UISearchControllerDelegate協議
@property (nullable, nonatomic, weak) id <UISearchControllerDelegate> delegate;
// 當搜索框激活時, 是否添加一個透明視圖
@property (nonatomic, assign) BOOL dimsBackgroundDuringPresentation __TVOS_PROHIBITED; 
@property (nonatomic, assign) BOOL obscuresBackgroundDuringPresentation NS_AVAILABLE_IOS(9_1); // default is YES
// 當搜索框激活時, 是否隱藏導航條
@property (nonatomic, assign) BOOL hidesNavigationBarDuringPresentation;     // default is YES

@property (nullable, nonatomic, strong, readonly) UIViewController *searchResultsController;
@property (nonatomic, strong, readonly) UISearchBar *searchBar;

PS : 需要注意的是, UISearchController 在使用的時候, 需要設置為全局的變量或者控制器屬性, 使其生命周期與控制器相同; 如果設置為局部變量, 會提前銷毀, 導致無法使用.

我們先聲明一些屬性:

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchController *searchController;
// 數據源數組
@property (nonatomic, strong) NSMutableArray *datas;
// 搜索結果數組
@property (nonatomic, strong) NSMutableArray *results;

初始化相關屬性:

- (UITableView *)tableView {
    if (_tableView == nil) {
        _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        [self.view addSubview:_tableView];
    }
    
    return _tableView;
}

- (NSMutableArray *)datas {
    if (_datas == nil) {
        _datas = [NSMutableArray arrayWithCapacity:0];
    }
    
    return _datas;
}

- (NSMutableArray *)results {
    if (_results == nil) {
        _results = [NSMutableArray arrayWithCapacity:0];
    }
    
    return _results;
}

然后, 實現 tableViewsearchController 的一些協議, 及其方法:

<UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating>

創建 UISearchController:

// 創建UISearchController, 這里使用當前控制器來展示結果
    UISearchController *search = [[UISearchController alloc]initWithSearchResultsController:nil];
    // 設置結果更新代理
    search.searchResultsUpdater = self;
    // 因為在當前控制器展示結果, 所以不需要這個透明視圖
    search.dimsBackgroundDuringPresentation = NO;
    self.searchController = search;
    // 將searchBar賦值給tableView的tableHeaderView
    self.tableView.tableHeaderView = search.searchBar;

實現tableView的數據源及代理方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
    // 這里通過searchController的active屬性來區分展示數據源是哪個
    if (self.searchController.active) {
        
        return self.results.count ;
    }
    
    return self.datas.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellID"];
    }
    
    // 這里通過searchController的active屬性來區分展示數據源是哪個
    if (self.searchController.active ) {
        
        cell.textLabel.text = [self.results objectAtIndex:indexPath.row];
    } else {
        
        cell.textLabel.text = [self.datas objectAtIndex:indexPath.row];
    }
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (self.searchController.active) {
        NSLog(@"選擇了搜索結果中的%@", [self.results objectAtIndex:indexPath.row]);
    } else {
        
        NSLog(@"選擇了列表中的%@", [self.datas objectAtIndex:indexPath.row]);
    }
    
}

最后實現 UISearchController 的協議 UISearchResultsUpdating方法:

#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    
    NSString *inputStr = searchController.searchBar.text ;
    if (self.results.count > 0) {
        [self.results removeAllObjects];
    }
    for (NSString *str in self.datas) {
        
        if ([str.lowercaseString rangeOfString:inputStr.lowercaseString].location != NSNotFound) {
            
            [self.results addObject:str];
        }
    }
    
    [self.tableView reloadData];
}

這里我只是簡單的過濾了一遍數據源, 把數據源中包含輸入字符的內容, 添加到搜索結果的數組results中, 然后刷新表格來展示搜索結果:

效果圖

如果我們想在開始輸入文字之前或者之后做一些操作, 可以實現searchBar的相關代理方法;
需要注意的是 : 這里當搜索框激活時, 隱藏導航的動畫, 這是在和系統導航結合使用, 并且searchController的屬性hidesNavigationBarDuringPresentationYES的情況下, 才會有的動畫效果, 如果使用了自定義的導航, 那就沒有這個效果了, 但是我們可以在 searchBar 的代理方法里, 設置相關的動畫效果, 但是整體還是沒有系統動畫自然.

二. 使用自定義導航條

我們吧上面的代碼做一下修改,:

// 視圖顯示的時候, 隱藏系統導航  使用自定義導航
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    if (self.navigationController) {
        
        self.navigationController.navigationBarHidden = YES;
    }
}
// 視圖消失的時候, 將系統導航恢復
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    if (self.navigationController) {
        self.navigationController.navigationBarHidden = NO;
    }
}

然后, 自定義一個導航條:

- (UIView *)customNavBar {
    if (_customNavBar == nil) {
        _customNavBar = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 64)];
        _customNavBar.backgroundColor = [UIColor whiteColor];
        
        [self.view addSubview:_customNavBar];
    }
    
    return _customNavBar;
}

添加相關元素, 這里只是添加了一個返回按鈕:

- (void)setupNavBar {
    
    UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    backBtn.frame = CGRectMake(0, 20, 80, 44);
    [backBtn setTitle:@"返回" forState:UIControlStateNormal];
    [backBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [backBtn addTarget:self action:@selector(backBtnClick) forControlEvents:UIControlEventTouchUpInside];
    [self.customNavBar addSubview:backBtn];
}

- (void)backBtnClick {
    
    [self.navigationController popViewControllerAnimated:YES];
}

這時候的效果是這樣的:

可以看到, 這里并不難自動隱藏自定義的導航, 這需要我們自己處理;
剩下的就是怎么來處理導航條的問題, 這時候就用到了searchBar的代理方法:

#pragma mark - UISearchBarDelegate
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    
}

我這里, 主要是在上面的這兩個代理方法里處理的, 具體的實現如下:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    
    CGRect barFrame = self.customNavBar.frame;
    // 移動到屏幕上方
    barFrame.origin.y = - 64;
    
    
    // 調整tableView的frame為全屏
    CGRect tableFrame = self.tableView.frame;
    tableFrame.origin.y = 20;
    tableFrame.size.height = self.view.frame.size.height -20;
這里Y坐標從20開始, 是因為, searchBar的背景視圖會多出20的高度, 而tableView的tableHeaderView并沒有相應增加, 所以這里強制空出20像素, 防止searchBar遮擋cell
    
    self.customNavBar.frame = barFrame;
    self.tableView.frame = tableFrame;
    [UIView animateWithDuration:0.4 animations:^{
        
        [self.view layoutIfNeeded];
        [self.tableView layoutIfNeeded];
    }];
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
    
    
    return YES;
}

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    
    CGRect barFrame = self.customNavBar.frame;
    // 恢復
    barFrame.origin.y = 0;
    
    
    // 調整tableView的frame為全屏
    CGRect tableFrame = self.tableView.frame;
    tableFrame.origin.y = 64;
    tableFrame.size.height = self.view.frame.size.height - 64;
    
    
    [UIView animateWithDuration:0.4 animations:^{
        
        self.customNavBar.frame = barFrame;
        self.tableView.frame = tableFrame;
    }];
}

上面的開始編輯時, 設置 tableView Y 坐標時多出了20, 這里Y坐標從20開始, 是因為, searchBar 的背景視圖會多出20的高度, 而tableView** 的 tableHeaderView 并沒有相應增加, 所以這里強制空出20像素, 防止 searchBar 遮擋cell;這時的效果圖如下:


可以看出還是有些突兀, 以上只是大致思路, 具體實現細節還需要再做優化.

三. 使用單獨的控制器來展示搜索結果

以上搜索結果的展示都是在一個控制器里進行的, 使用searchControlleractive屬性來區分數據源; 我們還可以使用兩個控制器來進行, 一個展示原始數據, 一個來展示搜索的結果;
這其實只是在初始化searchController的時候有些區別, 然后兩個控制器分別管理自己的數據源即可, 這里直接給出代碼:
初始化:

 // 創建用于展示搜索結果的控制器
    LZResultDisplayController *result = [[LZResultDisplayController alloc]init];
    // 創建搜索框
    UISearchController *search = [[UISearchController alloc]initWithSearchResultsController:result];

    self.tableView.tableHeaderView = search.searchBar;
    
    search.searchResultsUpdater = result;
    
    self.searchController = search;

然后在 LZResultDisplayController.h 遵循協議UISearchResultsUpdating :

@interface LZResultDisplayController : UIViewController<UISearchResultsUpdating>

@end

實現協議方法:

#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    
    // 這里處理過濾數據源的邏輯
NSString *inputStr = searchController.searchBar.text ;
    if (self.results.count > 0) {
        [self.results removeAllObjects];
    }
    for (NSString *str in self.datas) {
        
        if ([str.lowercaseString rangeOfString:inputStr.lowercaseString].location != NSNotFound) {
            
            [self.results addObject:str];
        }
    }
    // 然后刷新表格
    [self.tableView reloadData];
}

其他的要做的, 就是各自管理分配自己的數據源了, 效果圖如下:

這里的綠色部分只是個區頭, 因為使用的UITableViewController, 沒有做特別的處理.

這里有一個demo, 有需要的可以參考LZSearchController

關于searchBar的相關設置, 可參考筆者的另一篇文章[iOS]關于UISearchBar, 看這個就夠了;

(完)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容

  • 工作了好久一直很忙,好不容易有些時間,今天有人問我如何開始寫一個項目,不禁回憶起自己寫第一個項目的時候,到現在,突...
    小東門兒閱讀 857評論 0 4
  • 概述在iOS開發中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,080評論 3 38
  • 曾 關上 所有的窗 藏起 對你 深深地 愛 深深地 牽掛 深深地 渴望 ...
    粒粒藍雪閱讀 149評論 0 0
  • 著名作家畢淑敏說“當我們年輕時無法懂得,當我們懂得時已不在年輕,有些事情可以彌補,有些事情錯過了就是永遠。” 懂得...
    馬榮軍閱讀 208評論 0 0
  • 等我成了公主是不是就能說慌了? 噢,是啊,皮皮?洛塔 你總是為所欲為 你總是說我為所欲為,所以我就這么做了
    觸角_閱讀 177評論 0 0