UISearchBar自定义

2023-10-25 02:08
文章标签 自定义 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的颜色。

这篇关于UISearchBar自定义的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/279310

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在

鸿蒙开发中实现自定义弹窗 (CustomDialog)

效果图 #思路 创建带有 @CustomDialog 修饰的组件 ,并且在组件内部定义controller: CustomDialogController 实例化CustomDialogController,加载组件,open()-> 打开对话框 , close() -> 关闭对话框 #定义弹窗 (CustomDialog)是什么? CustomDialog是自定义弹窗,可用于广告、中

mybatis框架基础以及自定义插件开发

文章目录 框架概览框架预览MyBatis框架的核心组件MyBatis框架的工作原理MyBatis框架的配置MyBatis框架的最佳实践 自定义插件开发1. 添加依赖2. 创建插件类3. 配置插件4. 启动类中注册插件5. 测试插件 参考文献 框架概览 MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,为开发者提供了极大的灵活性和便利性。以下是关于M

vue2实践:第一个非正规的自定义组件-动态表单对话框

前言 vue一个很重要的概念就是组件,作为一个没有经历过前几代前端开发的我来说,不太能理解它所带来的“进步”,但是,将它与后端c++、java类比,我感觉,组件就像是这些语言中的类和对象的概念,通过封装好的组件(类),可以通过挂载的方式,非常方便的调用其提供的功能,而不必重新写一遍实现逻辑。 我们常用的element UI就是由饿了么所提供的组件库,但是在项目开发中,我们可能还需要额外地定义一