iOS定制UISearchBar导航栏同步iOS11的方法

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

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

实现思路与案例

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

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

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

案例

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

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

实战

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

先自定义一个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

这时候可以看到几处差异

  1. searchBar的高度
  2. searchBar的textField的放大镜和文字位置
  3. textField的圆角不一致
  4. 更细心的还会发现,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}; // 光标偏移量
}

同样的,先看下运行效果

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

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

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

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

searchField.borderStyle = UITextBorderStyleNone;

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

修改代码上面的代码

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

到此,便大功告成了。可以看下源码加深理解。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • iOS应用开发中导航栏按钮UIBarButtonItem的添加教程

    1.UINavigationController导航控制器如何使用 UINavigationController可以翻译为导航控制器,在iOS里经常用到. 我们看看它的如何使用: 下面的图显示了导航控制器的流程.最左侧是根视图,当用户点击其中的General项时 ,General视图会滑入屏幕:当用户继续点击Auto-Lock项时,Auto-Lock视图将滑入屏幕.相应地,在对象管理上,导航控制器使用了导航堆栈.根视图控制器在堆栈最底层,接下来入栈的是General视图控制器和Auto-Lock

  • iOS如何去掉导航栏(UINavigationBar)下方的横线

    网上有很多关于隐藏的方法,设置后能够成功,但是跳转到其他界面的时候发现,其他界面横线也被隐藏了. 目前主流的方法是将shadowImage用一张空的图片图片替换掉 可是这种方法不能解决navigationController里面的某个界面隐藏,其他不变的问题. [self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];

  • 两种iOS隐藏导航栏的正确方法

    简介 在项目中经常碰到首页顶部是无限轮播,需要靠最上面显示.有的设置导航栏为透明等一系列的方法,这个可以借助第三方.或者干脆简单粗暴的直接隐藏掉导航栏.可是push到下一个页面的时候是需要导航栏的,如何做了,这里给出两种方法. 第一种做法 -注意这里一定要用动画的方式隐藏导航栏,这样在使用滑动返回手势的时候效果最好,和上面动图一致.这样做有一个缺点就是在切换tabBar的时候有一个导航栏向上消失的动画. - (void)viewWillAppear:(BOOL)animated { [super

  • iOS App开发中导航栏的创建及基本属性设置教程

    文件目录如下:基本导航顺序: root -> First -> Second -> Third.其中,FirstViewController作为 navigation堆栈的rootview 1.创建navigation 如果是想直接把navigation导航作为项目一开始的跟视图,把RootViewController.h文件里的nav属性放到AppDelegate.h里即可,再把RootViewController.m文件里的action的代码复制到 AppDelegate.m里的di

  • iOS中导航栏的基本使用汇总

    一.设置导航栏样式 设置导航栏的样式可分为全局设置与局部设置: 1.全局设置 全局设置一般的都是在AppDelegate中设置,这样整个app都会生效,相关的代码与效果图如下: //1.设置导航栏背景颜色 [[UINavigationBar appearance] setBarTintColor:[UIColor orangeColor]]; //2.设置导航栏背景图片 [[UINavigationBar appearance] setBackgroundImage:[UIImage image

  • IOS仿今日头条滑动导航栏

    之前在我们平台给大家分享了网易首页导航封装类.网易首页导航封装类优化,今天在前两个的基础上仿下今日头条. 1.网易首页导航封装类中主要解决了上面导航的ScrollView和下面的页面的ScrollView联动的问题,以及上面导航栏的便宜量. 2.网易首页导航封装类优化中主要解决iOS7以上滑动返回功能中UIScreenEdgePanGestureRecognizer与ScrollView的滑动的手势冲突问题. 今天仿今日头条滑动导航和网易首页导航封装类优化相似,这个也是解决手势冲突,UIPanG

  • iOS实现顶部标签式导航栏及下拉分类菜单

    本文实例为大家分享了iOS实现顶部标签式导航栏及下拉分类菜单的全部过程,供大家参考,具体内容如下 当内容及分类较多时,往往采用顶部标签式导航栏,例如网易新闻客户端的顶部分类导航,最近刚好有这样的应用场景,参考网络上一些demo,实现了这种导航效果,记录一些要点. 效果图(由于视频转GIF掉帧,滑动和下拉动画显得比较生硬,刚发现quickTime可以直接录制手机视频,推荐一下,很方便) 1.顶部标签式导航栏 (1)实现思路 其实就是在上下两个UIScrollView上做文章,实现联动选择切换的效果

  • 详解iOS11关于导航栏问题

    前言 iOS11导航栏除了新加入了largeTitles和searchController两个新特性,可能是加入largeTitles的原因其结构较iOS 10发生了些变化. iOS11之前导航栏的navigationBarButton则直接添加在navigationBar上面 在iOS11之后,苹果添加了新的类来管理,可以看到titleView直接加在_UINavigationBarContentView上,UIBarButtonItem则添加在_UIButtonBarStackView上面,

  • 关于iOS导航栏返回按钮问题的解决方法

    最近遇到一个关于导航栏返回按钮的问题,因为之前项目里面都是用的系统默认的返回按钮样式所以没有想过要去更改,后来有需要将返回按钮箭头旁边的文字去掉,同时将该返回按钮的点击事件重新定义.一开始尝试自定义按钮然后设置为leftBarButtonItem,但是这样图片可能跟系统自带的不一样,还有就是返回按钮的位置跟系统自带的不一样.后来找了一些资料,发现将文字去掉比较简单,一般做法是控制器中添加如下代码,然后他的下一级控制就有一个只有箭头没有文字返回按钮: 复制代码 代码如下: UIBarButtonI

  • iOS界面跳转时导航栏和tabBar的隐藏与显示功能

    一.当A页面要push到B页面,需要将B页面的导航栏隐藏时,我们只需要在A页面中重写以下两个方法: override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true) } override func viewWillDisappear(animated: Bool)

随机推荐