美文网首页
UIButton精准控制image、title位置和点击区域

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

作者: 长不大的帅小伙 | 来源:发表于2019-01-07 15:39 被阅读18次

    这篇文章我不会介绍如何通过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_makeConstraints:^(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_makeConstraints:^(MASConstraintMaker *make) {
                make.height.mas_equalTo(btnHeight);
            }];
        }
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:UIButton精准控制image、title位置和点击区域

          本文链接:https://www.haomeiwen.com/subject/uohsrqtx.html