前言
我们在日常开发中经常用到 UIButton
,有时候需要 icon 和文字一起展示,所以需要做一些定制,比如以下几种情况:
- 图片在上,文字在下;
- 图片在下,文字在上;
- 图片在左,文字在右;
- 图片在右,文字在左;
其实就是改变 UIButton
内部的 imageView
和 titleLabel
的位置而已,默认的位置是:imageView
在左 titleLabel
在右且紧密相连,在水平方向: imageView
和 titleLabel
整体居中,在垂直方向:垂直居中,如下图:
这种布局很多时候并不是我们想要的,可能我们还需要imageView
和 titleLabel
之间加点间距,再或者改变一下两者的位置等等,这时候就用到了 UIButton
的 imageEdgeInsets
和 titleEdgeInsets
两个属性了,但是经过一番折腾,很多小伙伴还是不明白这里面的原理和套路,尽管网上有关这俩属性的资料很多,都是教大家如何设置,但很多资料并没有说清楚真正的本质,今天就给大家讲讲imageEdgeInsets
和 titleEdgeInsets
两个属性的本质,希望大家看完这篇文章能有所收获。
关键知识点
-
UIEdgeInsetsMake
所对应的4个参数分别是上、左、下、右
UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
-
edgeInsets说明.pngUIButton
的属性titleEdgeInsets
和imageEdgeInsets
顾名思义其实就是titleLabel
和imageView
的内边距,可以理解为向内压缩,当然还少了一个前提:如果是正值则为向内压缩,如果是负值,则为向外扩张。
(为了便于理解,大家也可以可参考 CSS 的padding
。) 看图说话:
控件的有效活动区域(名字自己起的):此区域就是
titleLabel或
imageView所能展示的区域,它们各自有各自的区域,是互不干扰的,这点要注意一下。如果区域容纳不了真实的内容,就会被压缩,具体如何压缩后面会具体讲解
默认情况下:titleEdgeInsets
和imageEdgeInsets
各个边的值都是0,此时,黄色区域覆盖蓝色区域; -
UIControl
的属性contentVerticalAlignment
和contentHorizontalAlignment
,这两个属性都是枚举类型的,定义了content
在UIControl
内部是如何定位布局的,分为垂直方向和水平方向,此处的定位就是相对于上面👆所说的控件的有效活动区域,即在有效活动区域内,控件的垂直方向和水平方向对齐方式。(此处还是要注意,这个对齐也是指每个控件在自己单独的有效活动区域的对齐,并非是整体。举个例子:titleLabel
在自己的有效活动区域内居中对齐,imageView
在自己的有效活动区域内居中对齐,两个区域可能有交集,也可能无交集,互不干扰,并不是titleLabel
和imageView
整体在UIButton
内居中对齐)具体枚举值如下:
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) {
UIControlContentHorizontalAlignmentCenter = 0,
UIControlContentHorizontalAlignmentLeft = 1,
UIControlContentHorizontalAlignmentRight = 2,
UIControlContentHorizontalAlignmentFill = 3,
UIControlContentHorizontalAlignmentLeading API_AVAILABLE(ios(11.0), tvos(11.0)) = 4,
UIControlContentHorizontalAlignmentTrailing API_AVAILABLE(ios(11.0), tvos(11.0)) = 5,
};
初始默认值:中心对齐(垂直方向居中,水平方向居中)
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
-
找出
titleLabel
或imageView
各自的初始有效活动区域,即button.imageEdgeInsets = UIEdgeInsetsZero
和button.titleEdgeInsets = UIEdgeInsetsZero
时它们各自的有效活动区域,为什么要找到它们的初始有效活动区域呢,因为我们设置的imageEdgeInsets
和titleEdgeInsets
都是针对初始有效活动区域做的操作。 -
AutoLayout
的Content Hugging Priority
(抗拉伸优先级) 和Content Compression Resistance Priority
(抗压缩优先级),从字面意思可以看出Content Hugging Priority
是内容拥抱优先级(Hugging:拥抱,抱紧),可以理解为内容与承载内容的视图紧凑的抱紧,比如说label
里的文字内容是充满整个label
的;Content Compression Resistance Priority
是内容压缩阻力优先级。这两个属性的值都是越大,越抗拉伸和抗压缩,属于float
类型的,取值范围是1--1000。不过还有4种系统类型的值如下:
typedef float UILayoutPriority NS_TYPED_EXTENSIBLE_ENUM;
// A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000;
// This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750;
// This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250;
// When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
获取和设置
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
以上这些知识点是大家需要了解的,弄懂这些,对后面的操作会有很大的帮助。接下来我们研究 UIButton
的 imageEdgeInsets
和 titleEdgeInsets
是如何向内压缩和向外扩展的。
如何找出titleLabel
和 imageView
各自的初始有效活动区域
提示:利用 UIControl
的属性 contentVerticalAlignment
和 contentHorizontalAlignment
。
UIButton
继承自 UIControl
,所以我们可以通过设置UIButton
的这两个属性,得到titleLabel
和 imageView
各自的初始有效活动区域,前面我们说过,这两个属性是针对它们各自的有效活动区域而言的,并不是UIButton
本身,所以我们在不改变imageEdgeInsets
和 titleEdgeInsets
时,分别设置它们在有效区域内左上对齐和右下对齐,就可以知道它们的有效区域范围了,如果理解不了,可以单独设置4次,分别是居上对齐、居左对齐、居下对齐、居右对齐,也可以得到相同的结果;还是数形结合更直观一点,我请大家看图,你们自备茶🍵吧!😆😆😆
通过上图,大家有没有发现,不管是什么对齐方式, imageView
的活动区域始终是在 {{0, 0}, {43, 40}} ({{x, y}, {width, height}})
这个区域内进行的,同时也是对齐方式为 Fill
模式下各自的 Frame
(由于精度问题,会有小于0.5的误差在内,属于正常情况,不影响结果)。而 titleLabel
的活动区域始终是在 {{30, 0}, {70, 40}} 这个区域内进行的,也就是 button
的宽度减去了 imageView
的宽度的范围,但是在对齐方式为 Fill
模式下, titleLabel
的活动区域并非是上图中绿色背景区域那些,真实值应该是再加上右边与 button
的间距。
仔细的小伙伴们可能也发现了,imageView
和 titleLabel
的初始活动区域的上边界和下边界正好是 button
的上下边界,但是左右边界好像确定不下来啊,再看上面的图,前两个对齐方式:
左上对齐的时候, imageView
的左边界是 button
左边界, titleLabel
的左边界是 imageView
的宽度;
右下对齐的时候,titleLabel
的右边界是 button
右边界, imageView
的右边界是 button
的宽度减掉 titleLabel
的宽度;
小结(划重点)
// 有效活动区域各边界的初始位置
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
imageView :top:0 left:0 bottom:0 right:buttonWidth-labelWidth
titleLabel:top:0 left:imageWidth bottom:0 right:buttonWidth
下面我用图给大家讲解下找imageView
的右边界和titleLabel
的左边界,其它边界上面已经说得很清楚了,这里就不再赘述了。
图解:
以上图应该可以让大家完全明白了吧!
如何设置正确 imageEdgeInsets 和 titleEdgeInsets
上面我们已经找到了 imageView 和 titleLabel 有效活动区域的初始边界,接下来我们就通过设置 imageEdgeInsets 和 titleEdgeInsets 来满足我们的需求。
- 图片在上,文字在下;
- 图片在下,文字在上;
- 图片在左,文字在右;(默认情况)
- 图片在右,文字在左;
imageEdgeInsets
和 titleEdgeInsets
是 UIEdgeInsets
类型的,上面关键知识点里面已经说了,
UIEdgeInsetsMake
所对应的4个参数分别是上、左、下、右UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
imageEdgeInsets
和titleEdgeInsets
的各个参数值的正负决定是向内压缩还是向外扩展(如果是正值则为向内压缩,如果是负值,则为向外扩张。
)
就拿 imageEdgeInsets
举例说明一下:
对于 imageEdgeInsets.top
- 为 0 时,
imageView
有效活动区域的上边界不动; - 为正值时,
imageView
有效活动区域的上边界向内收缩,即下移; - 为负值时,
imageView
有效活动区域的上边界向外扩张,即上移;
对于 imageEdgeInsets.left
- 为 0 时,
imageView
有效活动区域的左边界不动; - 为正值时,
imageView
有效活动区域的左边界向内收缩,即右移; - 为负值时,
imageView
有效活动区域的左边界向外扩张,即左移;
对于 imageEdgeInsets.bottom
- 为 0 时,
imageView
有效活动区域的下边界不动; - 为正值时,
imageView
有效活动区域的下边界向内收缩,即上移; - 为负值时,
imageView
有效活动区域的下边界向外扩张,即下移;
对于 imageEdgeInsets.right
- 为 0 时,
imageView
有效活动区域的右边界不动; - 为正值时,
imageView
有效活动区域的右边界向内收缩,即左移; - 为负值时,
imageView
有效活动区域的右边界向外扩张,即右移;
titleEdgeInsets
同理
举例说明
例1:图片在左,文字在右,且间距为6;
分析:默认情况就是图片在左,文字在右,所以我们只需要加间距就行,在对齐方式(水平和垂直方向)都为居中对齐时,我们只需要让 imageView
有效活动区域保持大小不变,整体往左移间距的二分之一,也就是3, titleLabel
有效活动区域保持大小不变,整体往右移间距的二分之一即可实现。
// imageView 与 titleLabel 的间距
CGFloat spacing = 6.f;
button.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing/2, 0, spacing/2);
button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing/2, 0, -spacing/2);
加间距.png
这个图片是用电脑在模拟器上直接截的图,截下来是正常居中的,但是传到这里,貌似看起来 imageView
的顶部间距比下面底部间距大一点点似的,不过大家也可以自己跑个 demo 看看,数据都是没问题的。可以直接通过 Xcode 的 Debug View Hierarchy
功能看视图层次结构和它们的 frame。
问题:上面的方案虽然实现了我们的需求,但是你们有没有发现,对 imageView
和 titleLabel
的有效活动区域保持大小不变,整体进行了左移和右移,导致了它们各自的有效活动区域跑出了 button
的外面,可以先后设置水平方向居左和居右对齐,看看会出现什么效果。
尽管实际使用的时候不会这么操作,但是对于追求完美的人来说实在是不能忍啊!
思考:除了上面的方案,还有没有其他方案?
方案一:只左移 imageView
有效活动区域的右边界,和右移 titleLabel
有效活动区域的左边界,间距多少就移多少(在水平对齐方式为居中对齐的时候, imageView
有效活动区域的右边界左移 spacing , imageView
就会整体左移 spacing/2,同理,titleLabel
会右移 spacing/2,整体而言间距就是 spacing)。
// imageView 与 titleLabel 的间距
CGFloat spacing = 6.f;
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
此方案在水平对齐方式为居左或居右的时候 imageView
和 titleLabel
不会超出 button
的边界。
方案二:既然我们知道了各自初始的有效活动区域,通过加间距,我们也可以算出来最终 imageView
和 titleLabel
的位置,那我们直接把 imageView
和 titleLabel
各自有效活动区域压缩或扩展到与最终 imageView
和 titleLabel
的位置(即frame)一致不就行了,针对各自有效活动区域的每条边界做移动操作就可满足,听起来是要做很多的计算啊,不过此方案有一个好处就是,最终结果不会受 button
的对齐属性 contentHorizontalAlignment
和 contentVerticalAlignment
影响,因为有效活动区域与它们各自本身的 boundSize 一致了,不论居左,居右,居上,居下,居中还是填充, imageView
和 titleLabel
的位置都只有一种情况。
CGFloat buttonHeight = button.bounds.size.height;
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageHeight = CGRectGetHeight(button.imageView.frame);
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
CGFloat labelHeight = CGRectGetHeight(button.titleLabel.frame);
// imageView 与 titleLabel 的间距
CGFloat spacing = 6.f;
// 垂直居中对齐时,imageView 顶部和底部距离 button 的间距
// 也是 imageView 有效活动区域上边界下移的距离和下边界上移的距离
CGFloat imageSpaceVertical = (buttonHeight-imageHeight) * 0.5f;
// 垂直居中对齐时,titleLabel 顶部和底部距离 button 的间距
// 也是 titleLabel 有效活动区域上边界下移的距离和下边界上移的距离
CGFloat labelSpaceVertical = (buttonHeight-labelHeight) * 0.5f;
// 水平居中对齐时,imageView 和 titleLabel 加间距后,各自距离 button 的间距(两者是等间距的)
// 也是 imageView 有效活动区域左边界右移的距离 titleLabel 有效活动区域右边界左移的距离
CGFloat spaceHorizontal = (buttonWidth-imageWidth-labelWidth-spacing) * 0.5f;
// imageView 最终的 frame
CGRect imageViewFrame = CGRectMake(spaceHorizontal, imageSpaceVertical, imageWidth, imageHeight);
CGFloat titleLabel_X = CGRectGetMaxX(imageViewFrame) + spacing;
// titleLabel 最终的 frame
CGRect titleLabelFrame = CGRectMake(titleLabel_X, labelSpaceVertical, labelWidth, labelHeight);
// imageView 有效活动区域右边界左移的距离
CGFloat imageViewRightEdgeLeftSpace = (buttonWidth-labelWidth) - CGRectGetMaxX(imageViewFrame);
// titleLabel 有效活动区域左边界右移的距离
CGFloat titleLabelLeftEdgeRightSpace = titleLabel_X - imageWidth;
button.imageEdgeInsets = UIEdgeInsetsMake(imageSpaceVertical, spaceHorizontal, imageSpaceVertical, imageViewRightEdgeLeftSpace);
button.titleEdgeInsets = UIEdgeInsetsMake(labelSpaceVertical, titleLabelLeftEdgeRightSpace, labelSpaceVertical, spaceHorizontal);
// 对齐方式:左上
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
// 对齐方式:右下
//button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentBottom;
// 对齐方式:填充
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
//button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
虽然看起来计算的偏移量有点多,但是如果理解了这个方案的逻辑,那么今天讲的本质也就理解了,此方案就当一个测试案例吧,以上这几种方案都是在水平和垂直居中对齐方式下进行计算的,大家也可以自己动手练习一下其它方式,更有助于理解。
例2:图片在上,文字在下,紧密相连,不加间距;
分析:上图下字,这种类似于叠罗汉,此时在水平方向,只有 imageView
和 titleLabel
,所以为了简化计算,水平方向的对齐方式采用居中,设置各自水平方向的有效活动区域为整个button
的宽度即可,至于垂直方向,则只需计算出最终的 imageView
和 titleLabel
的 frame.origin.y
值即可。
// 垂直居中对齐时,imageView 和 titleLabel ,各自距离 button 的间距(两者是等间距的)
// 也是 imageView 有效活动区域上边界下移的距离 titleLabel 有效活动区域下边界上移的距离
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最终的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最终的 frame.origin.y
// 也是 titleLabel 有效活动区域上边界下移的距离
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活动区域下边界上移的距离
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// imageView 有效活动区域右边界右移的距离
CGFloat imageRightEdgeRightSpace = labelWidth;
// titleLabel 有效活动区域下边界上移的距离
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
// titleLabel 有效活动区域左边界左移的距离
CGFloat labelLeftEdgeLeftSpace = imageWidth;
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, 0, imageBottomEdgeTopSpace, -imageRightEdgeRightSpace);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -labelLeftEdgeLeftSpace, labelBottomEdgeTopSpace, 0);
// 对齐方式:水平方向为居中,垂直方向任意都行,不影响结果
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentBottom;
//button.contentVerticalAlignment = UIControlContentHorizontalAlignmentFill;
此方案是固定住了 imageView
和 titleLabel
有效活动区域的上边界和下边界,也就是上边界和它们各自最终的 frame.origin.y
一致,下边界和 (frame.origin.y + frame.size.height)
,所以垂直方向的对齐方式不会影响到它们的位置,水平方向采用将有效活动区域放大至 button
的宽度,利用了水平居中对齐。
图片在上,文字在下,很明显,这种布局当前 button
的高度容纳不下,相应的我们增加其高度即可。
思考:大家有没有发现一个问题,我们以上所讲的情况都是 button
的宽度大于 imageView
和 titleLabel
的之和的,也就是说,button
始终包含着 imageView
和 titleLabel
,大家有没有想过 button
的宽度小于 imageView
和 titleLabel
之和的时候还会按我们的预期偏移吗?
填坑
上面让大家思考的问题其实是个坑,当button
的宽度小于 imageView
和 titleLabel
之和的时候,此时 titleLabel
会被压缩,也就是文字显示不全,甚至直接显示“...”。如图:
此时, button
的宽高都为60,无法完全显示两者,默认优先压缩了 titleLabel
。接下来看看为什么会压缩 titleLabel
而不是 imageView
。
在上面的关键知识点里的第5条说到了两个优先级, Content Hugging Priority
(抗拉伸优先级) 和 Content Compression Resistance Priority
(抗压缩优先级)正式因为 titleLabel
的 Content Compression Resistance Priority
(抗压缩优先级)小于 imageView
的,所以才会在宽度不够的时候会优先压缩 titleLabel
。我们来看看它们的默认值各是多少,还是通过 Xcode 的 Debug View Hierarchy
功能。
代码获取:
// imageView 水平方向的抗压缩阻力
UILayoutPriority layoutPriority_image = [button.imageView contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
// titleLabel 水平方向的抗压缩阻力
UILayoutPriority layoutPriority_label = [button.titleLabel contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
上图可以看出:
imageView 的
Content Hugging Priority
:
Horizontal:250(UILayoutPriorityDefaultLow)
Vertical:250(UILayoutPriorityDefaultLow)imageView 的
Content Compression Resistance Priority
:
Horizontal:750(UILayoutPriorityDefaultHigh)
Vertical:750(UILayoutPriorityDefaultHigh)titleLabel 的
Content Hugging Priority
:
Horizontal:250(UILayoutPriorityDefaultLow)
Vertical:250(UILayoutPriorityDefaultLow)titleLabel 的
Content Compression Resistance Priority
:
Horizontal:749.5
Vertical:749.5
titleLabel 的抗压缩阻力小于 imageView 的抗压缩阻力,所以是压缩了 titleLabel 。
也许有小伙伴在想,如果把 titleLabel 的抗压缩阻力设置为大于 imageView 的抗压缩阻力,那会不会就会优先显示 titleLabel 而压缩 imageView 呢,那不好意思,让你失望了,并不会出现你想要的结果。可能这是为了保护 imageView 不会被压缩变形吧,当然这也只是我的猜测。😆😆😆
接下来我们把上面提到的问题做了吧,请看题。
例:当 button 的宽度小于 imageView 和 titleLabel 宽度之和时,使图片在上,文字在下,不加间距。
分析:既然是叠罗汉放置,那就还是按水平居中的对齐方式,放大它们的有效活动区域至 button 的宽度,让它们自动居中对齐就行,我们只需计算上下边界偏移量就行。
// 垂直居中对齐时,imageView 和 titleLabel ,各自距离 button 的间距(两者是等间距的)
// 也是 imageView 有效活动区域上边界下移的距离 titleLabel 有效活动区域下边界上移的距离
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最终的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最终的 frame.origin.y
// 也是 titleLabel 有效活动区域上边界下移的距离
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活动区域下边界上移的距离
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// titleLabel 有效活动区域下边界上移的距离
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, 0, imageBottomEdgeTopSpace, -labelWidth);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -imageWidth, labelBottomEdgeTopSpace, 0);
不按套路出牌.png
What? 这是什么鬼👻,imageView 为啥没居中啊?怎么让和我们想的不一样呢? 结果却让人意想不到啊!
通过多次测试,我发现当 imageView 有效活动区域的右边界恰好右移到接近 button 右边界处的某个位置时,imageView 有效活动区域的右边界会被压缩至 button 宽度的一半,不知道这是什么机制,这种问题之前没出现,就是因为 imageView 的宽度与 button 的宽度正好发生了什么不可告人的秘密㊙️,也可能是精度丢失的问题导致的吧,暂且就这么安慰自己吧,哈哈...
那既然水平居中对于 imageView 是不安全的,那我们就采取绝对定位(也就是让 imageView 的有效活动区域 frame 与它最终的 frame 一致),只不过就是需要多做点计算的事儿。
// 垂直居中对齐时,imageView 和 titleLabel ,各自距离 button 的间距(两者是等间距的)
// 也是 imageView 有效活动区域上边界下移的距离 titleLabel 有效活动区域下边界上移的距离
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最终的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最终的 frame.origin.y
// 也是 titleLabel 有效活动区域上边界下移的距离
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活动区域下边界上移的距离
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// imageView 有效活动区域左边界右移的距离
CGFloat imageLeftEdgeRightSpace = (buttonWidth-imageWidth) * 0.5f;
// imageView 有效活动区域右边界移动的距离(此处不确定是左移还是右移,因为 titleLabel 被压缩后宽度未知)
// 所以还是通过算术表达式计算,正值就是左移,负值就是右移
CGFloat imageRightEdgeLeftSpace = (buttonWidth-labelWidth) - (buttonWidth-imageLeftEdgeRightSpace);
// 简化一下
imageRightEdgeLeftSpace = imageLeftEdgeRightSpace-labelWidth;
// titleLabel 有效活动区域下边界上移的距离
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, imageLeftEdgeRightSpace, imageBottomEdgeTopSpace, imageLeftEdgeRightSpace-labelWidth);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -imageWidth, labelBottomEdgeTopSpace, 0);
达到预期效果.png
以上方案是把 imageView 的有效活动区域给绝对定位了,titleLabel 的有效活动区域是放大,采用水平居中对齐的方式,自动居中对齐的。大家可以试试 button 宽度小的时候,两者的有效活动区域都采用绝对定位的方案,看看会有什么意外的收获。😏😏😏答案是尽管居中了,但是 titleLabel 还是被压缩了,没有显示出文字。此处给大家提示一下,在计算 titleLabel 有效活动区域左右边界移动的时候,切忌,不要用 titleLabel 的宽度(即CGRectGetWidth(button.titleLabel.frame)
)去做计算,因为此时 titleLabel 的宽度是被压缩了的,与真实的 titleString 宽度不符,是小于 titleString 宽度的,所以 titleLabel 最终移动了左右边界后还是之前的那么宽,固然无法完全显示文字。此时做计算的时候一定要去计算 titleLabel.text
的文字宽度。但是 imageView 有效活动区域右边界移动的计算还是使用 titleLabel 的宽度(即CGRectGetWidth(button.titleLabel.frame)
)去计算。自己试着理解一下,这里可能有点绕,自己最好动手练习一下,就明白了!
NSDictionary *dic = @{NSFontAttributeName:button.titleLabel.font};
CGFloat stringWidth = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, labelHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:dic
context:nil].size.width;
总结
说了这么多,最后也该做个大总结了,主要说下以下几个重点以及需要注意的地方吧。
- imageView 和 titleLabel 有效活动区域的初始位置,所有的便宜都是在这个基础上进行的;
// 有效活动区域各边界的初始位置
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
imageView :top:0 left:0 bottom:0 right:buttonWidth-labelWidth
titleLabel:top:0 left:imageWidth bottom:0 right:buttonWidth
-
button.contentHorizontalAlignment
和button.contentVerticalAlignment
两个方向的对齐方式都是针对 imageView 和 titleLabel 在各自的有效区域内而言的,且互不影响,但是在填充模式下,即
// 对齐方式:填充
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
imageView 会拉伸充满自己的有效活动区域,titleLabel 只会在垂直方向充满自己的有效活动区域,水平方向是与自己的有效活动区域左对齐的,不会充满整个 titleLabel 的有效活动区域。
-
当 button 的宽度不够容纳 imageView 和 titleLabel 时, imageView 不合适用水平方向扩大有效区域的右边界至 button 的右边界,然后居中对齐的方式,不安全,但是 titleLabel 不会有问题,可采用此方式。
-
当 button 的宽度不够容纳 imageView 和 titleLabel 时, titleLabel 有效活动区域如果采用绝对定位的话,切记分清 labelWidth 和 stringWidth 的区别,计算 imageView 有效活动区域右边界移动时用 labelWidth ,计算 titleLabel 有效活动区域的左右边界移动时用 stringWidth。
-
当 button 的宽度不够容纳 imageView 和 titleLabel 时, 始终优先压缩 titleLabel。
-
设置
button.imageEdgeInsets
和button.titleEdgeInsets
时,button.contentHorizontalAlignment
和button.contentVerticalAlignment
有多种对齐方式可选,自己喜欢哪一种就选哪一种对齐方式,当然还是计算量少的最好,哈哈... -
以上讲的所有边界偏移量的计算有一个很重要的前提就是
button.contentEdgeInsets
为默认值UIEdgeInsetsZero
,否则在做偏移量计算的时候需要加上button.contentEdgeInsets
各边界的值,当然一般很少会去设置button.contentEdgeInsets
。
今天的分享就到此结束🎬,希望大家能有所收获,祝各位帅哥靓女周末愉快!!!
网友评论