UISearchController的使用以及模仿京东、美团搜索框的实现

UISearchController 是 iOS 8 之后推出的用于管理搜索事件的控件, 在使用该控件的过程中要注意很多坑,下面我将带领大家来一步步的学习该如何使用该控件,并且最后带领大家来模仿一下京东首页的搜索框以及美团的地址搜索框,具体如下图:

jdSearchHistory.gif

废话不多说 我们开始

基本使用

iOS 11.0 之前版本

1. 结果控制器 是nil

定义 tableViewsearchController 两个属性,以及遵守相关的协议:

@interface GSNormalSearchVC ()<UITableViewDelegate,UITableViewDataSource,UISearchResultsUpdating,UISearchControllerDelegate>{
    NSArray *_dataArray;//数据源
    NSArray *_filterArray;
}
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchController *searchController;

对这两个属性懒加载:

#pragma mark --getter method
-(UISearchController *)searchController{
    if (!_searchController) {
        //创建UISearchController,当ResultsController为nil的时候当前控制器就是结果控制器
        _searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
        _searchController.hidesNavigationBarDuringPresentation = YES;//当搜索框激活时, 是否隐藏导航条 default is YES;
        _searchController.dimsBackgroundDuringPresentation = YES;//当搜索框激活时, 是否添加一个透明视图 default is YES
        _searchController.searchBar.placeholder = @"查找您所在的区域";//搜索框占位符
        _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;
}

实现遵守的协议:

#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;
}

效果图大致如下:


如果此时我们尝试着将 automaticallyAdjustsScrollViewInsets 设置为 NO 的时候,我们可以看到当搜索控件处于激活状态的时候 SearchBar和底部内容之间的间距突然就拉大了,具体如下图所示:

所以这也告诉我们一个问题 在使用 UISearchController的时候切记不要轻易的 改变 automaticallyAdjustsScrollViewInsets

内部结构

通过 Debug View Hierarchy 来看看此时它的内部结构是怎样的.

当 UISearchController 未处于激活状态 下的结构如下图所示:

当处于激活状态下,通过下图我们可以看到多了几个View: UIDimmingView UISearchBarContainerView

通过了解 UISearchController的内部结构有助于下文我们自定义一个属于我们自己的搜索控件 , 具体细节 后文会详细介绍

自定义 结果控制器

如果我们想自定义一个 结果控制器 我们可以在创建 UISearchController 的时候直接指定即可:

  GSSearchResultVC *resultVC = [[GSSearchResultVC alloc] init];
        _searchController = [[UISearchController alloc] initWithSearchResultsController:resultVC];

然后在 updateSearchResultsForSearchController:中将搜索到的结果传递给结果控制器即可:

#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];
}

运行效果大致如图所示:

此时我们便遇到了使用过程中的第一个坑 相信细心的朋友已经发现了:当搜索框处于激活状态的时候 SearchBar和搜索到的结果之间的距离越来越大.

而且当UISearchController处于active状态下用户push到下一个控制器的时候 SearchBar 却仍然仍留在界面上

那么该如何解决上述问题呢?

此时我们只需要将 self.definesPresentationContext = YES;即可,添加该行代码之后运行起来一切正常.

内部结构图
跟上面的非常类似,只是当 UISearchController 处于激活状态的时候,我们看到 UISearchControllerView 中多了一个 UIView,该View其实就是 resultViewController.view,如下图所示:

注意
在使用 UISearchController 的时候, 我们需要将其设置为全局变量或者控制器属性, 使其生命周期与控制器相同; 如果设置为局部变量, 则会提前销毁, 导致无法使用.

iOS 11.0 的新变化

我们将上述代码在 iOS 11 的环境下运行起来以后 如下图所示:


上图是在iOS 11 环境下的一点小变化, 我相信细心的朋友已经发现问题了, 当搜索框处于 active的状态的时候 默认情况下 第一行和 searchBar之间的间距变大了,我们通过 Debug View Hierarchy 来看看结构发生了什么变化,为什么会这样呢?

通过上图我们发现在 iOS 11环境中 , 激活状态下 此时 UISearchBar已经不是添加 到 UISearchControllerView中 而是被添加到了 导航栏中了,所以为了解决这个问题 我们需要单独的适配iOS 11

    if (@available(iOS 11, *)) {
        self.navigationItem.searchController = self.searchCtrl;
        self.navigationItem.hidesSearchBarWhenScrolling = NO;
    } else {
        self.tableView.tableHeaderView = self.searchCtrl.searchBar;
    }

searchBar 个性化设置

   ///将Cancel 修改为 取消
    [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitle:@"取消"];

    //修改取消文字颜色以及光标的颜色
    self.searchCtrl.searchBar.tintColor = [UIColor redColor];

    //textField 设置圆角
    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];
        }
    }
    
    //调节放大镜的位置
    [self.searchCtrl.searchBar setPositionAdjustment:UIOffsetMake(100.f, 0.f) forSearchBarIcon:UISearchBarIconSearch];

京东搜索框

下面我们通过模仿京东首页的搜索框来强化一下 UISearchController 的结构 具体效果如下图:

通过上图我们可以看到这个搜索框自带一些动画效果,而系统并没有为我们提供这些动画效果,为了更方便的实现上述效果 这里我采取了自定义 SearchController的方式 这样我们实现起来也就更加灵活 也不用去适配 SearchController在不同版本下的差异了.

首先我们自定义一个继承自 UIViewController的控制器,在该控制器中 定义两个代理方法(其实就是从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:方法中将结果控制器添加进来

-(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;
}

最后根据 SearchBar 的内容来决定是否将 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];
    }
}

这里我只摘了部分代码 感兴趣的同学可以去 GitHub 看看具体的代码实现

Demo链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容