首页 > 代码库 > UISearchBar

UISearchBar

非常菜的iOS菜鸟一枚~~~~~最近做项目里需要一个搜索框,UI设计的是使用自定义的外观,所以需要修改很多内置的东西,写代码和调试的过程搜了很多资料,也在stack上问了很多问题,发现关于UISearchBar的资料少得可怜,尤其是iOS7里search bar的资料更是几近没有,所以我整理了一下我这段时间的研究出来的东西,希望能帮助大家自定义search bar,也方便日后自己查看。备注:因为我用到的是iOS默认的搜索框交互模式,即搜索框获取焦点时,输入框变窄,显示取消按钮,失去焦点时输入框变宽,取消按钮隐藏,所以下面的介绍全部基于这一情况。内容比较多,慢慢整理。

 

1.UISearchBar结构
     网上关于iOS6及以下版本的search bar结构分析很多,但是,iOS7 search bar结构修改较大,下面截图说明。iOS6下UISearchBar的subviews数组如下,失去焦点时有两个对象,分别对应:背景图、搜索输入框,获取焦点时有三个对象,分别对应:背景图、搜索输入框、取消按钮。 

 

    
         图1 iOS6下搜索框失去焦点时subviews                 图2 iOS6下搜索框获取焦点时subviews
 

而iOS7下,UISearchBar的subviews数组如下,只有一个对象,类型是UIView,我继续挖掘了这个UIView的subviews,

 

 

图3 iOS7下搜索框的subviews


subviewContainerView就是刚刚提到的search bar 的subviews数组中的那个UIView(即上图中的0x08a83400),它的subviews如下,和iOS6不同的是,图4是search bar从未获得过焦点情况下的截图,而图5是获得过一次焦点以后的截图(无论当下它是否是第一响应者),也就是说,search bar初始化时,不会创建取消按钮,当它第一次获取焦点时,会创建取消按钮,并存入subviewContainerView的subviews数组,这之后,即使search bar失去焦点,取消按钮需要隐藏,也不会remove取消按钮。iOS7速度上比iOS6快很多,不知道和这些改变有没有关系。

 
图4                                                                      图5

总结:

iOS6下,UISearchBar的结构是:

不是第一响应者状态:                                    第一响应者状态:

 

 

                                                               



iOS7下,UISearchBar的结构是:

             首次获得焦点前:                                  首次获得焦点之后(无论是否是第一响应者):

 

 

 

2.自定义外观--输入框、取消按钮、背景
     知道UISearchBar结构后,就可以按照结构来修改UISearchBar各个部分,定制自己的搜索框外观。首先获取subviews:

  1. float version = [[[UIDevice currentDevice] systemVersion] floatValue];  
  2. NSArray *subviewContainer = version>7.0 ? ((UIView *)self.subviews[0]).subviews : self.subviews;  

 

然后依次读取该数组中的元素subview,修改它的颜色、frame等即可:

 

  1.         if ([subview isKindOfClass:NSClassFromString(@"UISearchBarBackground") ] ){   
  2. //subview是背景图  
  3.         }   
  4.         if ([subview isKindOfClass:NSClassFromString(@" UISearchBarTextField") ] ){  
  5. //subview是输入框  
  6.         }   
  7.         if ([subview isKindOfClass:NSClassFromString(@"UINavigationButton") ] ){  
  8. //subview是取消按钮  
  9.         }  



 

iOS6里,背景和输入框一直存在,所以关于它们的颜色、字体等修改可以在init函数里实现,而它们的frame每次绘制均会重置,所以需要在layoutSubviews里修改,而且输入框有时宽有时窄需要自己判断。而取消按钮在初始化时并不会创建,它会在需要显示时创建,需要隐藏时remove掉,所以,每次绘制的取消按钮其实都是一个全新的按钮,有关它的设置需要全部在layoutSubviews里完成。

 

     iOS7里,背景框和输入框情况和iOS6中一样,需要特别留意的是取消按钮,第一部分讲过了,iOS7里,取消按钮会在第一次需要显示时创建,创建后一直存在,并未被remove掉,我打印了它的属性发现,它的hidden始终为NO,而且它位于subviews最后一个,应该在最上层,那么它为什么可以藏起来呢?打印这个对象发现,它的frame在未显示时是origin.x是大于320的,原来被藏到屏幕外去了。所以,iOS7时可以通过检测取消按钮被重置后的默认x坐标来判断它是显示还是隐藏,如果需要显示,再修改为你想要的frame。
     iOS7里,你会发现用上面的方法修改subview的title外观等并未成功,apple的文档建议利用Appearance来修改:
     

  1. [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBarclass],nil] set……];  


不过这个方法又修改不了背景图片和title文字……而取消按钮初始化时也不会被创建……所以这几个还是在layoutSubviews实现……

输入框在获取焦点时,是窄的,失去焦点时变宽,所以修改frame需要判断它是哪种状态。我是通过判断取消按钮是否存在或是否存在在屏幕里来设置的,好像有点绕弯,用UITextField的isEditing或者isFirstResponder就可以,其实iOS6里确实这样就可以,但是我遇到了一个变态的情况…… 我的搜索框是放在一个view controller里的,这个view controller是tab bar三个view controller中的一个……问题来了,当我点击了搜索框,却未输入任何东西的情况下,我切换tab bar,然后再切换回来,这个时候输入框不是第一响应者也不在编辑,所以输入框是宽的……而且因为我偷懒,输入框不是第一响应者也不在编辑的时候我就让取消按钮按默认frame显示了,反正它在界面外,但是这时候这个按钮却意料外的在界面内,frame还很不符合要求……我查看了一下,是因为在切回来的时候,iOS7里又调用了一次layoutSubviews函数,而这个时候又不符合输入框是第一响应者或在编辑这一条件,所以悲剧了……我也没明白具体是哪个事件或者哪个属性决定取消按钮隐藏或者显示,总之不是UITextField的isEditing和isFirstResponder,所以我就改用这个取消按钮的x坐标来判断了,有点绕,但是不出错。iOS6里切回来也会看到输入框并不是第一响应者,没在编辑,不出错的原因在于,layoutSubivew压根没有重新调用……而且iOS6里我用subviews count判断,反正取消按钮存在,我就让输入框变小,也不会出错。如果哪位大神知道这里面具体是怎么隐藏取消按钮的,还望指点!

总结:
UISearchBar三部分:
1.背景和输入框的外观在init修改,尺寸在layoutSubviews实现,输入框尺寸在iOS6里通过subviews count判断,iOS7通过取消按钮frame.origin.x判断;
2.取消按钮,iOS6里在subviews count是3的时候,获取lastObject并修改它的外观和尺寸,iOS7里,部分外观利用Appearance在init修改,部分在layoutSubviews修改,尺寸在layoutSubviews根据其每次被绘制前的默认x坐标判断是否修改。

3.搜索

     iOS自带一个控件和搜索框配套使用UISearchDisplayController,初始化时,需要指明它的search bar是哪个,它的searchContentsController是哪个。刚刚才发现,如果只添加search bar,不设置它的UISearchDisplayController,search bar使用非常不正常,所以,一定要设置UISearchDisplayController。

  1. [[ UISearchDisplayController  alloc ]initWithSearchBar:searchBar contentsController:controller];  


controller是要搜索的view controller,其实就是它和search bar所在的那个view controller。搜索结果会显示在searchResultsTableView中,它是一个UITableView。
     然后实现它的委托:id<UISearchDisplayDelegate> delegate、id<UITableViewDataSource> searchResultsDataSource、id<UITableViewDelegate> searchResultsDelegate第一个负责搜索,后两个负责搜索结果的显示。
     UISearchDisplayDelegate里有很多可选择的函数,会在不同的时候自动调用,为了实时显示搜索结果,采用了

  1. - (BOOL) searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString;  


输入框内文字一有变动,就会自动调用。然后可以根据输入框里的文字searchBar.text,调用自己的具体搜索函数,返回一个数组,reload searchResultsTableView就可以了,这个数组就是搜索结果table view的数据源。如果搜索结果为空,那么就遍历searchResultsTableView的subviews,找到那个类型时UILabel的subview,修改它就好了,如果搜索过程比较慢,也可以让它在搜索结果显示前提示“搜索中”。
     因为我们的搜索比较费时,所以采用了多线程处理,最后在显示前检测了一下这个结果的搜索关键字和当前的搜索框里的文本是否一致,如果一直再显示。


4.搜索结果高亮显示
    和普通的UITableView一样,搜索结果列表的cell也可以自己定制,用CATextLayer和NSMutableAttributedString高亮显示搜索关键字。如果一个cell同时有UILabel和CATextLayer,那么在reloadData的时候会很明显的发现,label会一瞬间刷新为新的值,layer会有个渐变的过程,而且这个情况在cell复用时没什么影响,还不明白为什么。
     retina屏里CATextLayer会出现字体模糊,设置

  1. layer.contentsScale = [[UIScreen mainScreen]scale];  


即可,这是retina屏幕分辨率和尺寸的比例问题。其实search bar取消按钮也会有模糊,那是因为shadow offset的原因,设为0就ok了。

 



5.点击search bar下面的阴影--灰色半透明背景修改
     我不太清楚这个部分到底该叫什么名字,就是如图7中灰色的部分,这一部分是半透明的,把除了键盘和搜索框的其余部分全部遮盖起来了,也就是告诉用户,这一部分暂时不能操作。这一部分应该时接到上面的search bar的底部,然后输入框是第一响应者,但是没有输入任何文字时,它存在,如果输入文字了,那么显示的就是searchResultsTableView了。点击这一部分,和点击取消按钮一样,退出搜索框,键盘收起。研究这个的起因是UI要求修改它的颜色,最后被逼无奈找见它是因为iOS7……
     我们的搜索框是比默认高度矮的,只修改三个子view的frame会导致search bar底部和这个灰色view的顶部有一段距离,灰色的背景不能完全盖住后面的view。其实,在iOS6里解决这个问题比较简单,你只需要修改这个search bar的frame 高度就好。所以寻觅这个view的计划就被我搁浅了。但是iOS7里,悲剧了,不管你是修改search bar自己的高度,还是search bar subviews[0]的高度,这个部分都还是盖不住。所以我从search bar开始,逐个往上研究。假设search bar所在的view controller叫做vc1,我想search bar是在vc1里的,这个灰色的背景也该在vc1里面吧,所以我查看了这个vc1的subviews,发现里面有一个类型是UISearchDisplayControllerContainerView的subview,望文生义,搜索-显示-控制器-容器-视图,那就应该是这个灰色view了,是不是呢?这个view的y坐标为0,我将它设置为 我的search bar的高度 - 默认搜索框的高度44,这个view就向上移动了。所以这个灰色的view就是UISearchDisplayControllerContainerView。
     iOS6和iOS7在这个问题上,同样处理不一样,在iOS7里,这个view类型是UISearchDisplayControllerContainerView,这个类型的view一般在一个controller里就只有一个,所以可以放心地直接用if语句把这个类型的subview挑出来,修改subview就好了,但是,iOS6里,这个view的类型是UIControl,这个类型的view就不敢保证只用一个了,比如我的界面里除了这个灰色的view,还有两个也是这个类型的view或者这个类型派生的其他类型的view,所以,直接if挑出来很有可能把别的view也摘出来了。不过好在这个灰色view不管是在iOS6里还是iOS7里,都是在显示的时候添加在search bar的view controller的最顶端,不显示时remove掉,所以可以判断search bar所在view controller的subviews的长度来确定这个view存在否,如果存在,即为subviews的最后一个subview,修改它就可以。
     既然找到这个view了,是不是修改这个view的backgroundColor就可以达到UI的要求了呢?尝试了下,iOS6确实是这样滴!但是……iOS7里又悲剧了……iOS7下,这个view有3个subview,结构如图8所示。

                       图7                                                                                                                  图8                                                                   

subview1和整个window一样大,subview2在search bar默认位置是320X44(search bar默认大小)大小,subview3是紧接着subview2的纵坐标,然后到window底部的一个view。所以以3.5屏为例,上面修改这个view的frame的效果其实是:

假如search bar 的frame是(0,100,320,24),那么view的frame是(0,0,320,480), subview1的frame是(0,0,320,480),subview2 (0,100,320,44),subview3(0,144,320,336)。subview3就是我们想要修改的那个灰色背景,从它们的frame可以看出y坐标125-144中间空了一段。上面修改view的frame的纵坐标为我的search bar的高度 - 默认搜索框的高度44,即24-44=-20,总高度加上20,其实是将整个view向上拉伸20,subview3自然也就向上移了20.修改颜色的话,修改subview3的就好啦。其实iOS6下,这个view就是subview3。

不过不管在iOS6还是7,这个灰色背景默认有一个灰色的附加值,所以你将其设置为红色背景,会看起来像红色底上加一个灰色的膜。

总结:

 

  • iOS6下,修改灰色半透明背景的高度只需要修改search bar的frame即可,修改颜色通过修改点击search bar时它所在的controller新添加的那个view的颜色来实现。
  • iOS7下,修改高度通过修改点击search bar时它所在的controller新添加的那个view的y坐标来实现,颜色通过修改这个view的第三个subview的颜色来实现。
实现:
通用一点,我是不修改这个search bar的frame,自定义外观时只修改三个subview的frame。无论是iOS6,还是iOS7,(1)获取search bar所在controller在search bar获取焦点时新添加的这个view,(2)修改这个view的frame以修改高度,(3)然后判断这个view的subview有几个,(4)0个,就直接修改view的颜色以修改灰色为想要的颜色,3个就修改第三个subview的颜色。