UIButton精准控制image、title位置和点击区域

这篇文章我不会介绍如何通过titleEdgeInsetsimageEdgeInsets来调整UIButton图片和文字的排版,这个属于基础部分,我就不费口舌了。

写这篇文章主要目的:

  1. 分析一下网上大多如出一辙的方案有什么问题。
  2. 我是如何处理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的中间的。

设置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,那么就需要我们设置准确的值了。

ize大小设置准确效果
// 字左,图右
[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.widthtitleLabel会被压缩,一旦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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容