美文网首页
iOS定制UISearchBar导航栏 同步iOS11

iOS定制UISearchBar导航栏 同步iOS11

作者: 123123dfgj656 | 来源:发表于2018-04-14 16:47 被阅读223次

    五花八门的searchBar

    系统原生的UISearchBar在iOS 11经历了一次变革,高度由原来的44变成了56 (使用默认高度的估计都被坑了),样式也发生了些微的变化,比如在未输入状态下圆角变化,放大镜图标和文本的文字不再居中而是靠左了。具体看图


    image

    一些主流App也常见在导航栏嵌入searchBar,以网易云音乐和知乎为例,左边是主页,右边是搜索页面 (注意光标)。


    image
    image

    实现思路与案例

    核心思想是设置导航栏的titleView和左右的barButtonItem。主要有3种方式

    • 首页导航栏的titleView使用button来实现,搜索页面使用searchBar。
    • 首页和搜索页面导航栏的titleView都是用searchBar,searchBar的样式针对两个页面做不同的修改。这种方式可以重用我们定制的searchBar,减少冗余。
    • 首页导航栏titleView使用button来实现,搜索页面的使用textField。这种方式更彻底,更灵活,相对也更复杂一些。

    为什么上面的titleView说是button不是其他的?其他的当然也可以实现。button自带imageView和titleLabel,只需要设置偏移量更容易达到我们想要的,而且视图层级更少,在流畅性方面更有保证些。

    案例

    image
    image

    网易云音乐首页和搜索页面的导航栏视图层级,titleView都使用MCSearchBar来实现,并且设置了导航栏左右两边的按钮 。这类似上文所说的第二种思路。

    image
    image

    图中可以清楚看到知乎首页导航栏由2个button组成,搜索页面使用了textField,这类似上文提到的第三种思路。

    实战

    通过自定义SearchBar实现一个如下样式的导航栏

    image

    先自定义一个UISearchBar的初始化方法,观察一下首页和搜索页的异同,像searchField的大小背景色是一致的,可以这部分可以直接给定,而placeholder是不一样的,所以应该在调用的时候提供。以此类推,新建一个OHSearchBar类,一个初始化方法

    - (instancetype)initWithFrame:(CGRect)frame placeholder:(NSString *)placeholder textFieldLeftView:(UIImageView *)leftView showCancelButton:(BOOL)showCancelButton tintColor:(UIColor *)tintColor { 
        if (self = [super initWithFrame:frame]) {
            self.frame = frame;
            self.tintColor = tintColor; //光标颜色
            self.barTintColor = [UIColor whiteColor];
            self.placeholder = placeholder;
            self.showsCancelButton = showCancelButton;
            self.leftView = leftView; // 用来代替左边的放大镜
            [self setImage:[UIImage imageNamed:@"clear"] forSearchBarIcon:UISearchBarIconClear state:UIControlStateNormal]; // 替换输入过程中右侧的clearIcon
        }
        return self;
    }
    

    新建一个首页OHHomeViewController,设置导航栏的titleView和rightBarButton

    // navigation buttom
        UIButton *messageButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [messageButton setImage:[UIImage imageNamed:@"msg"] forState:UIControlStateNormal];
        messageButton.bounds = CGRectMake(0, 0, 30, 30);
        UIBarButtonItem *messageBarButton = [[UIBarButtonItem alloc] initWithCustomView:messageButton];
        self.navigationItem.rightBarButtonItem = messageBarButton;
        
        // search bar
        UIImageView *leftView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"scan"]];
        leftView.bounds = CGRectMake(0, 0, 24, 24);
        self.ohSearchBar = [[OHSearchBar alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 44)
                                                  placeholder:@"点击我跳转"
                                            textFieldLeftView:leftView
                                             showCancelButton:NO
                                                    tintColor:[UIColor clearColor]];
        self.navigationItem.titleView = self.ohSearchBar; 
    

    让我们来看下效果,左边为iOS 9,右边iOS 11


    image

    这时候可以看到几处差异

    • searchBar的高度
    • searchBar的textField的放大镜和文字位置
    • textField的圆角不一致
    • 更细心的还会发现,textField的位置不一致

    解决方法:
    第一和第二个问题,判断设备是否是iOS 11,若是则设置其高度,不是则让其placeholder居左。关键代码如下

        if ([[UIDevice currentDevice] systemVersion].doubleValue >= 11.0) {
            [[self.heightAnchor constraintEqualToConstant:44.0] setActive:YES];
        } else {
            [self setLeftPlaceholder];
        }
    
    - (void)setLeftPlaceholder {
        SEL centerSelector = NSSelectorFromString([NSString stringWithFormat:@"%@%@", @"setCenter", @"Placeholder:"]);
        if ([self respondsToSelector:centerSelector]) {
            BOOL centeredPlaceholder = NO;
            NSMethodSignature *signature = [[UISearchBar class] instanceMethodSignatureForSelector:centerSelector];
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
            [invocation setTarget:self];
            [invocation setSelector:centerSelector];
            [invocation setArgument:&centeredPlaceholder atIndex:2];
            [invocation invoke];
        }
    }
    
    

    对于第三和第四个问题,用KVC获取textField,并对其进行定制。令textField位置、大小、圆角一致。

    - (void)layoutSubviews{
        [super layoutSubviews];
    
        // search field
        UITextField *searchField = [self valueForKey:@"searchField"];
        searchField.backgroundColor = DARK_BLUE_COLOR;
        searchField.textColor = [UIColor whiteColor];
        searchField.font = [UIFont systemFontOfSize:16];
        searchField.leftView = self.leftView;
        searchField.frame = CGRectMake(0, 8, SCREEN_WIDTH, 28);
        searchField.layer.cornerRadius = 5;
        searchField.layer.masksToBounds = YES;
        [searchField setValue:[UIColor whiteColor] forKeyPath:@"placeholderLabel.textColor"];
        [self setValue:searchField forKey:@"searchField"];
        
        self.searchTextPositionAdjustment = (UIOffset){10, 0}; // 光标偏移量
    }
    

    同样的,先看下运行效果


    image

    原本以为这下是没什么问题的,结果简直是坑


    image

    textFild的长度、位置、圆角都不一样
    解释下这里出现的问题

    • 观察上方图片上方的searchBar,会发现textField左边是圆角,右边是直角,说明是被截取的。导航栏titleView的范围就划分到了那个部分,而下边的searchBar连rightBarButton都不放过,直接抢占了位置。推测这是由于iOS 11导航栏视图层级变化产生的,可以这里了解下http://www.jianshu.com/p/352f101d6df1,或者自行科普,不详细展开。所以对于searchBar的size设置要小心了,尽量控制在合适的范围。

    • textField的圆角是不一致的,自定义圆角大小时,取消其本身的圆角样式

      searchField.borderStyle = UITextBorderStyleNone;
      
    • 查看视图层级会发现,iOS 11以下,设置titleView,x的默认坐标居然是12,而iOS 11是0。所以设置textField的x坐标的话,在iOS 11下必须多出12才会是一致的位置。


      image
      image

    修改代码上面的代码

    - (void)layoutSubviews{
        [super layoutSubviews];
    
        // search field
        UITextField *searchField = [self valueForKey:@"searchField"];
        searchField.backgroundColor = DARK_BLUE_COLOR;
        searchField.textColor = [UIColor whiteColor];
        searchField.font = [UIFont systemFontOfSize:16];
        searchField.leftView = self.leftView;
    
        if (@available(iOS 11.0, *)) {
            // 查看视图层级,在iOS 11之前searchbar的x是12
            searchField.frame = CGRectMake(12, 8, SCREEN_WIDTH*0.8, 28);
    
        } else {
            searchField.frame = CGRectMake(0, 8, SCREEN_WIDTH*0.8, 28);
        }
    
        searchField.borderStyle = UITextBorderStyleNone;
        searchField.layer.cornerRadius = 5;
    
        searchField.layer.masksToBounds = YES;
        [searchField setValue:[UIColor whiteColor] forKeyPath:@"placeholderLabel.textColor"];
        [self setValue:searchField forKey:@"searchField"];
        
        self.searchTextPositionAdjustment = (UIOffset){10, 0}; // 光标偏移量
    }
    

    这时候就是我们想要的结果了。

    首页暂时告一段落,接着开始我们的搜索页面。与首页不同的是需要searchBar与searchController配合使用。新建一个OHSearchController类
    添加一个属性

    @property (nonatomic, strong) OHSearchBar *ohSearchBar;
    

    初始化代码

    - (instancetype)initWithSearchResultsController:(UIViewController *)searchResultsController searchBarFrame:(CGRect)searchBarFrame placeholder:(NSString *)placeholder textFieldLeftView:(UIImageView *)leftView showCancelButton:(BOOL)showCancelButton barTintColor:(UIColor *)barTintColor{
        if (self = [super initWithSearchResultsController:searchResultsController]) {
            self.ohSearchBar = [[OHSearchBar alloc] initWithFrame:searchBarFrame
                                                      placeholder:placeholder
                                                textFieldLeftView:leftView
                                                 showCancelButton:YES
                                                        tintColor:barTintColor];
            
            UIButton *button = [self.ohSearchBar valueForKey:@"cancelButton"];
            button.tintColor = [UIColor whiteColor];
            [button setTitle:@"取消" forState:UIControlStateNormal];
            [self.ohSearchBar setValue:button forKey:@"cancelButton"];
        }
        return self;
    }
    

    接着是我们的视图控制器OHSearchViewController

        UIImageView *leftView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"search"]];
        leftView.bounds = CGRectMake(0, 0, 24, 24);
        self.ohSearchController = [[OHSearchController alloc] initWithSearchResultsController:self
                                                                               searchBarFrame:CGRectMake(0, 0, SCREEN_WIDTH, 44)
                                                                                  placeholder:@"请输入搜索内容进行搜索"
                                                                            textFieldLeftView:leftView
                                                                             showCancelButton:YES
                                                                                 barTintColor:BASE_BLUE_COLOR];
        
        [self.ohSearchController.ohSearchBar becomeFirstResponder];
        self.ohSearchController.ohSearchBar.delegate = self;
        [self.ohSearchController.ohSearchBar setLeftPlaceholder];
        self.navigationItem.titleView = self.ohSearchController.ohSearchBar;
        self.navigationItem.hidesBackButton = YES;
    

    完成这一步后到了交互环节了,点击首页的searchBar跳转搜索页面,点击搜索页面的取消按钮返回到首页。
    首页设置searchbar的代理,并完成一下代理方法

    - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
        OHSearchViewController *ohSearchViewController = [[OHSearchViewController alloc] init];
        [self.navigationController pushViewController:ohSearchViewController animated:NO];
        return YES;
    }
    

    搜索页设置searchbar的代理,并完成一下代理方法

    - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
        [self.navigationController popViewControllerAnimated:NO];
    }
    
    - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
        [self.ohSearchController.ohSearchBar resignFirstResponder];
        // 让取消按钮一直处于激活状态
        UIButton *cancelBtn = [searchBar valueForKey:@"cancelButton"];
        cancelBtn.enabled = YES;
    }
    

    这时候问题又出现了,点击搜索页面的取消按钮,没有跳回首页而是还在这个页面。但是可以看到屏幕的闪动。通过打印消息发现,点了取消按钮,执行了首页的- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar方法。
    仔细推敲之后想明白了原因是没有取消第一响应者,加上导航栏的交互机制,pop到上个页面的时候并不会进行页面刷新导致了这个问题。
    解决办法在首页要push搜索页面的时候取消第一响应者

    - (void)viewWillDisappear:(BOOL)animated {
        [self.ohSearchBar resignFirstResponder];
    }
    

    到此,便大功告成了。可以看下源码加深理解。
    项目源码传送门:
    HasjOH/OHSearchBarInNaviBar

    相关文章

      网友评论

          本文标题:iOS定制UISearchBar导航栏 同步iOS11

          本文链接:https://www.haomeiwen.com/subject/himlkftx.html