前言
UIButton
是我们开发过程中使用频率很高的控件类。在使用UIButton实现需求时,通常会碰到需要改变UIButton
中image和title位置和大小的需求。如果对UIButton中的titleLabel和imageView理解不到位的话,只能是不停地修改参数,不停地调试,陷入恶性循环,既低效又很难正确地去实现。为了解决这个问题,我研究出了两套方案,分别是用imageEdgeInsets
、titleEdgeInsets
组合的方案以及imageRectContentRet
、titleRectContentRect
组合的方案。
titleEdgeInsets、imageEdgeInsets组合
这个方案是使用的比较多的解决方案,它主要依赖的是两个方法:
titleEdgeInsets
imageEdgeInsets
这个方案比较麻烦的地方在于很多人对这两个方法的理解不到位,导致使用的时候设置和显示的效果不一致。UIButton
继承自UIControl
,它因此也继承了两个属性contentVerticalAlignment
和contentHorizontalAlignment
。这两个属性是用来排列内部元素的,默认值都是Center,先看下它们的定义:
按照4*4的组合分为16种选择结果。为了在计算过程中更方便点,我通常选择UIControlContentVerticalAlignmentTop
、UIControlContentHorizontalAlignmentLeft
的组合,这样符合我们以左上角作为坐标原点布局的思维。这里要纠正几个误区:第一,edgeinsets
只是相对于当前位置的偏移量,并不是指距离UIButton边界的距离;第二,在同时存在图片还有文字的时候,只有UIButton的contentRect
的宽度大于image和title的宽度才能正确显示,否则,文字由于无法进行拉伸收缩的原因只能显示…或者图片被压缩;第三、imageView的宽高都能被压缩,titleLabel的宽只能压缩不能拉伸,titleLabel的高只能拉伸不能压缩。
采用left-top方案初始情况下,image和title的位置如下图所示:
在初始状态下,imageEdgeInsets
和titleEdgeInsets
均为0,因此在计算偏移量的时候,image的top、left、right、bottom以及title的top、bottom、right均能以button的边界作为参考,而title的left是以image的left为参考的。搞清楚了之后,来做一下image在上,title在下的button:
这里还牵扯到一个问题,有人可能会觉得既然有left和top,那就不需要right和bottom了,实际不然。这实际上是涉及到约束的优先级。其定义如下:
- top-left-bottom-right取负值 > 不能超出button边界 > imageView不能被压缩
- top-left-bottom-right取负值 > 不能超出button边界 > titleLabel水平方向不能被压缩
- titleLabel垂直方向不能被压缩 > 不能超出button边界
若有人运用发现image超出button边界,只需要同时调整contentEdgeInsets
即可。
imageRectForContentRect、titleRectForContentRect组合
第一个方案理解之后确实可以解决问题,不过,我觉得有些麻烦,就想着能不能有更方便的方案。于是,我翻阅文档和资料后,找到了更简单的方案,也就是这第二个方案。
第二个方案涉及到两个方法:
- (CGRect)titleRectForContentRect:(CGRect)contentRect
- (CGRect)imageRectForContentRect:(CGRect)contentRect
根据文档的解释,这两个方法返回值是矩形区域,区域内则是用来绘制image和title,返回值的参照系都是UIButton。要想自定义image和title在UIButton的位置和大小,只需要继承UIButton,重写这两个方法即可。
相比第一个方案,第二个方案确实方便不少,但是又有一个问题。要是想改变一个UIButton都得继承UIButton重写方法显得太麻烦,实际上并没有达到简便的目的。于是,我想能不能用runtime运行时机制,给UIButton添加两个变量,直接设置就可以修改image和title在UIButton中的rect,这样会方便很多。
先贴上实现代码:
|
|
|
|
简要说下实现的思路:由于在category中重写imageRectForContentRect
、titleRectForContentRect
会覆盖原来的方法实现,并且无法使用super关键字,因此重写方法行不通。转而我采用自己实现两个方法并分别与这两个方法的实现进行交换,这样就不会破坏原有方法的实现。此外,动态添加三个实例变量可以直接在实例对象中直接设置来调用imageRectForContentRect
、titleRectForContentRect
两个方法。
使用的时候导入头文件,再相应的设置一下实例变量就可以达到效果,有没有很方便。
TIPS
在开发过程中,我们经常会用到UIViewConentMode属性,有些人往往对属性值的意义不太了解,这里贴出一张图让你对此一目了然。