这篇文章我不会介绍如何通过titleEdgeInsets
、imageEdgeInsets
来调整UIButton
图片和文字的排版,这个属于基础部分,我就不费口舌了。
写这篇文章主要目的:
- 分析一下网上大多如出一辙的方案有什么问题。
- 我是如何处理
UIButton
图片和文字排版的。
举例分析网上如出一辙的方案会有什么问题
1.图片在左,文字在右,图片文字间加间距space
我们知道UIButton
默认是图片在左边,文字在右边的,并且自动布局不设置大小的时候,默认是有固有尺寸的intrinsicContentSize
,这个尺寸正好能包裹住图片和文字,效果如图1所示。
那么如果要给图片和文字间加一个大小为space的间距,常见的方式就是:1. 图片不动,文字往右移动space距离。2. 文字不动,图片往左移动space距离。3. 图片往左移动space0.5距离,文字往右移动space0.5距离。
如果把button单独拿出来看的话,这几种处理方式看起来效果是一样的,都达到了图片和文字有个space间距的效果,但是如果结合button融入的场景,这几种处理方式是有差别的,例如要求button右边与竖线对齐,图2(图片不动,文字右移space),图3(文字不动,图片左移space),看起来很明显,图3融入到场景中去是更合理的。同理,如图4,如果要保证button在白色区域中间,这个时候,图片和文字分别向两边移动space * 0.5距离,是最佳的。
而目前我在网上看到的方法,包过网上很多同学提供的category方法,都是单独拿button出来看完美解决了,融入场景中,就需要额外的再去重新调整button位置(坚决抵制反复拿各种数字去微调位置,这种处理方式太恶心)才能和场景完美结合的。不仅仅存在这个问题,还有更严重的一个问题,下面会说到。
2. 图片在右,文字在左,中间加间距space
我们把图片和文字移动拆解成两步去理解,第一步是:图片右移文字宽度,文字左移图片宽度,这样就实现了如图效果。第二步就是去控制间距了,间距如何去控制,就和上面分析的一样了,究竟是图片继续再往右边移动space,还是文字继续往左移动space,还是图片和文字分别继续往两边移动space*0.5,这个就根据button融入的场景去定了。
3.图片在上,文字在下,中间加间距space / 文字在上,图片在下,中间加间距space
假如有一个场景,要求button的图片在上,文字在下,并且button的顶部与上面的线对齐,那么如果使用网上的通用方式处理效果如图:
图1是默认的效果。图2是调整过后的效果,很明显,button的图片和文字都超出按钮范围了,而我们想要的效果是image的顶部和线条对齐。
细心的同学可能会发现,这里不仅仅是UI布局没满足效果,而且超出按钮的部分点击是无法响应事件的。
实际上上面的水平移动的例子也有图片、文字超出按钮的问题,不过超出的并不多,用户点击的时候几乎不会点到超出部分,所以问题不是很大,不过还是能处理的,后面再说。但是这种上下排版的按钮,加上间距的话,超出的部分就很多了,非常容易点到超出部分而没有响应,所以这个问题必须解决。
我处理的思路
上面也分析了一些我的思路,下面说说针对这个问题我是如何处理的。由于用自动布局时候,我们经常是不会去设置按钮大小的,默认就是按钮的intrinsicContentSize
。当然也不排除也有人会设置button的size,例如需求就要求按钮设置大一些。所以我针对这两种情况作了区分。
1. 设置了button的size
这种情况简单,思路就是,无论是左图右字、左字右图、上图下字、上字下图, 始终保持image和title整体是在设置的size的中间的。
从图中可以看出,image和title整体是在设置的size的中间的。下面贴出使用实例代码:
// 图左字右
[self.btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.contentView);
make.width.height.mas_equalTo(145);
}];
- (UIButton *)btn
{
if (!_btn) {
_btn = [[UIButton alloc] init];
_btn.backgroundColor = UIColor.purpleColor;
_btn.titleLabel.font = [UIFont systemFontOfSize:14];
[_btn setImage:[UIImage imageNamed:@"weixin"] forState:UIControlStateNormal];
[_btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_btn setTitle:@"微信好友" forState:UIControlStateNormal];
[_btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
[_btn layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleLeft
imageTitleSpace:10
buttonSize:CGSizeMake(145, 145)];
}
return _btn;
}
// 图上字下
- (UIButton *)btn2
{
if (!_btn2) {
... 略去了创建代码
[_btn2 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleTop
imageTitleSpace:10
buttonSize:CGSizeMake(145, 145)];
}
return _btn2;
}
- (void)layoutButtonWithEdgeInsetsStyle:(JLButtonEdgeInsetsStyle)style imageTitleSpace:(CGFloat)space buttonSize:(CGSize)size;
这个方法的实现最后会给出,先看上面,button的大小实际是比image和title区域要大的,如果我们想要button能正好包裹住调整位置后imaga和title,那么就需要我们设置准确的值了。
// 字左,图右
[self.btn1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.contentView1);
CGFloat width = self.btn1.titleLabel.intrinsicContentSize.width + self.btn1.imageView.intrinsicContentSize.width + 10;
CGFloat height = MAX(self.btn1.titleLabel.intrinsicContentSize.height, self.btn1.imageView.intrinsicContentSize.height);
make.width.mas_equalTo(width);
make.height.mas_equalTo(height);
}];
- (UIButton *)btn1
{
if (!_btn1) {
... 省略创建代码
CGFloat width = _btn1.titleLabel.intrinsicContentSize.width + _btn1.imageView.intrinsicContentSize.width + 10;
CGFloat height = MAX(_btn1.titleLabel.intrinsicContentSize.height, _btn1.imageView.intrinsicContentSize.height);
[_btn1 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleRight
imageTitleSpace:10
buttonSize:CGSizeMake(width, height)];
}
return _btn1;
}
// 图上,字下
[self.btn2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.contentView2);
CGFloat width = self.btn2.titleLabel.intrinsicContentSize.width + self.btn2.imageView.intrinsicContentSize.width;
CGFloat height = self.btn2.titleLabel.intrinsicContentSize.height + self.btn2.imageView.intrinsicContentSize.height + 10;
make.width.mas_equalTo(width);
make.height.mas_equalTo(height);
}];
- (UIButton *)btn2
{
if (!_btn2) {
... 省略创建代码
CGFloat width = _btn2.titleLabel.intrinsicContentSize.width + _btn2.imageView.intrinsicContentSize.width;
CGFloat height = _btn2.titleLabel.intrinsicContentSize.height + _btn2.imageView.intrinsicContentSize.height + 10;
[_btn2 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleTop
imageTitleSpace:10
buttonSize:CGSizeMake(width, height)];
}
return _btn2;
}
需要注意一点的是:上下布局的时候,宽度是没法做到完美包裹的,最理想的状态就是按钮的width = _btn2.titleLabel.intrinsicContentSize.width + _btn2.imageView.intrinsicContentSize.width
,因为UIButton
默认是图左字右,切间距为0的布局,如果设置的宽度小于_btn2.titleLabel.intrinsicContentSize.width + _btn2.imageView.intrinsicContentSize.width
,titleLabel
会被压缩,一旦titleLabel
被压缩,之后无论怎么移动titleLabel的位置,他始终是显示出被压缩状态的,所以width
必须要大于等于_btn2.titleLabel.intrinsicContentSize.width + _btn2.imageView.intrinsicContentSize.width
。这是Button内部处理机制的问题,跟我们是如何封装代码调整image和title位置无关,如果要完美包裹,那只能自己写view,自己去摆放image和title了。
2. 完全不设置button的size,就正常使用自动布局去处理
上面的方案1,button
调整image和title后,button
能够完美的融入场景;同时点击区域问题也完美的解决了。但是按钮如果要完美的包裹图片和文字的话,使用起来略显麻烦,从上面贴的使用代码也能看出,始终需要自己在布局的时候设置一下button的width,height。
然而自从有了Autolayout
后,像UIButton
这种有intrinsicContentSize的控件,是无需我们去设置尺寸的,为了达到让使用者不关心按钮大小,只关心:1. 按钮图片和文字排版方式。2. 调整过后不会影响按钮融入场景的效果。3. 点击区域最佳包裹。于是我额外提供了一个方法。使用者根据自己的业务场景,选择使用哪个方法就好了。
使用就跟正常使用UIButton一样,要调整图片和文字排版只需调用封装的方法就好了。
// 调整后效果图1
[self.btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contentView);
make.left.equalTo(self.line.mas_right);
}];
- (UIButton *)btn
{
if (!_btn) {
... 创建代码省略,省的影响你心情
[_btn layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleLeft
imageTitleSpace:10];
}
return _btn;
}
// 调整后效果图2
[self.btn1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contentView1);
make.right.equalTo(self.line1.mas_left);
}];
- (UIButton *)btn1
{
if (!_btn1) {
... 创建代码省略,省的影响你心情
[_btn1 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleRight imageTitleSpace:10];
}
return _btn1;
}
// 调整后效果图3
[self.btn2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentView2);
make.top.equalTo(self.line2.mas_bottom);
}];
- (UIButton *)btn2
{
if (!_btn2) {
... 创建代码省略,省的影响你心情
[_btn2 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleTop imageTitleSpace:10];
}
return _btn2;
}
// 调整后效果图4
[self.btn3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentView3);
make.top.equalTo(self.line3.mas_bottom);
}];
- (UIButton *)btn3
{
if (!_btn3) {
... 创建代码省略,省的影响你心情
[_btn3 layoutButtonWithEdgeInsetsStyle:JLButtonEdgeInsetsStyleBottom imageTitleSpace:10];
}
return _btn3;
}
最后:啰七八嗦讲了这么多,最后贴我封装的代码供大家参考使用
typedef NS_ENUM(NSInteger, JLButtonEdgeInsetsStyle) {
JLButtonEdgeInsetsStyleTop,
JLButtonEdgeInsetsStyleLeft,
JLButtonEdgeInsetsStyleBottom,
JLButtonEdgeInsetsStyleRight,
};
@interface UIButton (LayoutEdgeInsets)
/**
支持fram布局。
支持AutoLayout,并且要求设置button的size。
[self.btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.contentView);
make.width.height.mas_equalTo(145); // 设置你想要的大小
}];
根据button的特性,注意button按钮宽度的最小值要求
@param style 按钮图片文字排版方式
@param space 图片与文字间距
@param size button的size,和你设置的按钮大小保持一致
*/
- (void)layoutButtonWithEdgeInsetsStyle:(JLButtonEdgeInsetsStyle)style imageTitleSpace:(CGFloat)space buttonSize:(CGSize)size;
/**
支持AutoLayout,并且无需自己设置按钮size。
默认是intrinsicContentSize,调整了图片和文字位置和间距后,此方法内部会自动调整button的size,size的大小是最佳包裹尺寸
@param style 按钮图片文字排版方式
@param space 图片与文字间距
*/
- (void)layoutButtonWithEdgeInsetsStyle:(JLButtonEdgeInsetsStyle)style imageTitleSpace:(CGFloat)space;
@end
@implementation UIButton (LayoutEdgeInsets)
- (void)layoutButtonWithEdgeInsetsStyle:(JLButtonEdgeInsetsStyle)style imageTitleSpace:(CGFloat)space buttonSize:(CGSize)size
{
CGFloat imageWidth = self.imageView.intrinsicContentSize.width;
CGFloat imageHeight = self.imageView.intrinsicContentSize.height;
CGFloat titleWidth = self.titleLabel.intrinsicContentSize.width;
CGFloat titleHeight = self.titleLabel.intrinsicContentSize.height;
CGFloat btnWidth = MAX(size.width, imageWidth + titleWidth);
CGFloat btnHeight = MAX(size.height, imageHeight + titleHeight);
if (style == JLButtonEdgeInsetsStyleLeft || style == JLButtonEdgeInsetsStyleRight) {
space = MIN(btnWidth - imageWidth - titleWidth, space);
space = MAX(space, 0);
if (style == JLButtonEdgeInsetsStyleLeft) {
CGFloat offset = space * 0.5;
self.imageEdgeInsets = UIEdgeInsetsMake(0, -offset, 0, offset);
self.titleEdgeInsets = UIEdgeInsetsMake(0, offset, 0, -offset);
} else {
CGFloat imgOffset = titleWidth + space * 0.5;
CGFloat titleOffset = imageWidth + space * 0.5;
self.imageEdgeInsets = UIEdgeInsetsMake(0, imgOffset, 0, -imgOffset);
self.titleEdgeInsets = UIEdgeInsetsMake(0, -titleOffset, 0, titleOffset);
}
}
else if (style == JLButtonEdgeInsetsStyleTop || style == JLButtonEdgeInsetsStyleBottom) {
space = MIN(btnHeight - imageHeight - titleHeight, space);
space = MAX(space, 0);
CGFloat blankX = (btnWidth - imageWidth - titleWidth) * 0.5;
CGFloat blankY = (btnHeight - imageHeight - titleHeight - space) * 0.5;
CGFloat imgOffsetX = btnWidth * 0.5 - (blankX + imageWidth * 0.5);
CGFloat titleOffsetX = blankX + imageWidth + titleWidth * 0.5 - btnWidth * 0.5;
CGFloat imgOffsetY = btnHeight * 0.5 - (blankY + imageHeight * 0.5);
CGFloat titleOffsetY = btnHeight * 0.5 - (blankY + titleHeight * 0.5);
if (style == JLButtonEdgeInsetsStyleTop) {
self.imageEdgeInsets = UIEdgeInsetsMake(-imgOffsetY, imgOffsetX, imgOffsetY, -imgOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(titleOffsetY, -titleOffsetX, -titleOffsetY, titleOffsetX);
} else {
self.imageEdgeInsets = UIEdgeInsetsMake(imgOffsetY, imgOffsetX, -imgOffsetY, -imgOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(-titleOffsetY, -titleOffsetX, titleOffsetY, titleOffsetX);
}
}
}
- (void)layoutButtonWithEdgeInsetsStyle:(JLButtonEdgeInsetsStyle)style imageTitleSpace:(CGFloat)space
{
CGFloat imageWidth = self.imageView.intrinsicContentSize.width;
CGFloat imageHeight = self.imageView.intrinsicContentSize.height;
CGFloat titleWidth = self.titleLabel.intrinsicContentSize.width;
CGFloat titleHeight = self.titleLabel.intrinsicContentSize.height;
if (style == JLButtonEdgeInsetsStyleLeft || style == JLButtonEdgeInsetsStyleRight) {
CGFloat moveInterver = space * 0.5;
if (style == JLButtonEdgeInsetsStyleLeft) {
self.titleEdgeInsets = UIEdgeInsetsMake(0, moveInterver, 0, -moveInterver);
self.imageEdgeInsets = UIEdgeInsetsMake(0, -moveInterver, 0, moveInterver);
} else {
CGFloat imgOffsetX = titleWidth + moveInterver;
CGFloat titleOffsetX = imageWidth + moveInterver;
self.imageEdgeInsets = UIEdgeInsetsMake(0, imgOffsetX, 0, -imgOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(0, -titleOffsetX, 0, titleOffsetX);
}
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(imageWidth + titleWidth + space);
}];
}
else if (style == JLButtonEdgeInsetsStyleTop || style == JLButtonEdgeInsetsStyleBottom) {
CGFloat btnWidth = imageWidth + titleWidth;
CGFloat btnHeight = imageHeight + titleHeight + space;
CGFloat imgOffsetX = btnWidth * 0.5 - imageWidth * 0.5;
CGFloat titleOffsetX = (imageWidth + titleWidth * 0.5) - btnWidth * 0.5;
CGFloat imgOffsetY = btnHeight * 0.5 - imageHeight * 0.5;
CGFloat titleOffsetY = btnHeight * 0.5 - titleHeight * 0.5;
if (style == JLButtonEdgeInsetsStyleTop) {
self.imageEdgeInsets = UIEdgeInsetsMake(-imgOffsetY, imgOffsetX, imgOffsetY, -imgOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(titleOffsetY, -titleOffsetX, -titleOffsetY, titleOffsetX);
} else {
self.imageEdgeInsets = UIEdgeInsetsMake(imgOffsetY, imgOffsetX, -imgOffsetY, -imgOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(-titleOffsetY, -titleOffsetX, titleOffsetY, titleOffsetX);
}
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(btnHeight);
}];
}
}
@end