美文网首页
UIButton自定义图片文字相对位置?layoutSubvie

UIButton自定义图片文字相对位置?layoutSubvie

作者: 李琪_59dc | 来源:发表于2019-02-20 14:21 被阅读0次

    首先,说一下需要考虑这个问题的场景

    • UIButton ,同时设置图片和文字,默认图片在左,文字在右
    • 需求经常可能是图片上文字下?文字左图片右?文字上图片下?可能位置还需要动态变化或动态展示隐藏

    查了很久,大致有以下三种做法:

    • 1、简单粗暴-直接写个UIImageView,用相对布局盖上去
      好处是动态变动的,针对动态变化的需求操作起来比较灵活。但总觉得这么写不大专业?
    • 2、重写UIButton的layoutSubview方法
      好处是比较直观,符合正常布局设计思维
    • 3、利用UIButton里的setTitleEdgeInsets和setImageInsets方法,调整文字和图片的位置及大小
      好处是代码量少,但是比较不好理解

    下面详细写下后两种写法:

    # 写法二:重写UIButton的layoutSubview方法

    layoutSubviews是对subviews重新布局。比如,我们想更新子视图的位置的时候,可以通过调用layoutSubviews方法,即可以实现对子视图重新布局。
    layoutSubviews默认是不做任何事情的,用到的时候,需要在子类进行重写。

    layoutSubviews调用场景
    ①、直接调用setLayoutSubviews。
    ②、addSubview的时候触发layoutSubviews。
    ③、当view的frame发生改变的时候触发layoutSubviews。
    ④、第一次滑动UIScrollView的时候触发layoutSubviews。
    ⑤、旋转Screen会触发父UIView上的layoutSubviews事件。
    ⑥、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
    注意:
    init初始化不会触发layoutSubviews,但是使用initWithFrame进行初始化时,当rect的值不为CGRectZero时,也会触发。
    引用自:iOS-layoutSubviews

    结合工厂设计模式,再考虑选择分类还是继承的写法?由于所写类需要一个type属性,来标志按钮图片及标题属于哪一种位置关系;而分类一般不能添加属性(可能会覆盖原有类的属性),同时分类中的方法都添加给了原有类,可能会影响原有类及其子类的使用。所以这里采用继承的写法比较合适 LQButton:UIButton

    • 具体实现代码如下:
    //  LQButton.h
    #import <UIKit/UIKit.h>
    
    typedef NS_ENUM(NSInteger, LQButtonType){
        LQButtonTypeCenterImageCenterTitle,//图和文字都居中
        LQButtonTypeLeftImageRightTitle,//左图右文字
        LQButtonTypeLeftTitleRightImage,//左文字右图
        LQButtonTypeTopImageBottomTitle,//上图下文字 略
        LQButtonTypeTopTitleBottomImage//上文字下图 略
    };
    
    @interface LQButton : UIButton
    
    @property (nonatomic, assign) LQButtonType type;
    
    //初始化一个按钮的同时设置其图片文字位置关系
    + (instancetype)lqButtonWithType:(LQButtonType)buttonType;
    //更改按钮的图片文字位置关系
    - (void)updateButtonStyleWithType:(LQButtonType)buttonType;
    
    @end
    
    //  LQButton.m
    #import "LQButton.h"
    
    @implementation LQButton
    
    + (instancetype)lqButtonWithType:(LQButtonType)buttonType {
        LQButton *btn = [LQButton buttonWithType:UIButtonTypeCustom];
        btn.type = buttonType;
        return btn;
    }
    
    - (void)updateButtonStyleWithType:(LQButtonType)buttonType {
        self.type = buttonType;
        [self layoutSubviews];
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
        if (self.type == LQButtonTypeCenterImageCenterTitle) {
            
            [self resetBtnCenterImageCenterTitle];
            
        }else if (self.type == LQButtonTypeLeftImageRightTitle) {
            
            [self resetBtnLeftImageRightTitle];
            
        }else if (self.type == LQButtonTypeLeftTitleRightImage) {
            
            [self resetBtnLeftTitleRightImage];
            
        }
    }
    
    - (void)resetBtnCenterImageCenterTitle {
        self.imageView.frame = self.bounds;
        [self.imageView setContentMode:UIViewContentModeCenter];
        
        self.titleLabel.frame = self.bounds;
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
    }
    
    - (void)resetBtnLeftImageRightTitle {
        
        CGRect frame = self.bounds;
        frame.size.width *= 0.5;
        self.imageView.frame = frame;
        [self.imageView setContentMode:UIViewContentModeCenter];
        
        frame.origin.x = (self.bounds.size.width - frame.size.width);
        self.titleLabel.frame = frame;
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
    }
    
    - (void)resetBtnLeftTitleRightImage {
        
        CGRect frame = self.bounds;
        frame.size.width *= 0.5;
        self.titleLabel.frame = frame;
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        
        frame.origin.x = (self.bounds.size.width - frame.size.width);
        self.imageView.frame = frame;
        [self.imageView setContentMode:UIViewContentModeCenter];
    }
    
    @end
    

    以上是自定义LQButton的实现,具体引用如下:

    //  ViewController.m
    // 先引入头文件 #import "LQButton.h"
    // 然后开始使用
    - (void)viewDidLoad {
        [super viewDidLoad];
        UIImage *btnImg = [UIImage imageNamed:@"btnImg"];
    //这种通过缩放图片达到设置图片大小的方法,会导致图片清晰度下降,文章最后有进行分析
        btnImg = [self imageWihtoutScale:btnImg size:CGSizeMake(50, 50)];
    
        LQButton *btn3 = [LQButton lqButtonWithType:LQButtonTypeLeftTitleRightImage];
        btn3.frame = CGRectMake(80, 590, 300, 80);
        btn3.backgroundColor = [UIColor lightGrayColor];
        [btn3 setImage:btnImg forState:UIControlStateNormal];
        [btn3 setTitle:@"左文字右图片" forState:UIControlStateNormal];
        btn3.titleLabel.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.8];
        [self.view addSubview:btn3];
        
        LQButton *btn4 = [LQButton lqButtonWithType:LQButtonTypeLeftImageRightTitle];
        btn4.frame = CGRectMake(80, 680, 300, 80);
        btn4.backgroundColor = [UIColor lightGrayColor];
        [btn4 setImage:btnImg forState:UIControlStateNormal];
        [btn4 setTitle:@"左图片右文字" forState:UIControlStateNormal];
        btn4.titleLabel.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.8];
        [self.view addSubview:btn4];
        
        LQButton *btn5 = [LQButton lqButtonWithType:LQButtonTypeCenterImageCenterTitle];
        btn5.frame = CGRectMake(80, 770, 300, 80);
        btn5.backgroundColor = [UIColor lightGrayColor];
        [btn5 setImage:btnImg forState:UIControlStateNormal];
        [btn5 setTitle:@"图片文字均居中" forState:UIControlStateNormal];
        btn5.titleLabel.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.8];
    //注意:用重写layoutSubviews的方式,frame改变、及addSubviews的时候,均会触发layoutSubviews。
    //所以如果在此处再使用setTitleEdgeInsets/setImageEdgeInsets,将不会有效果。
    //只能通过LQButton中的updateButtonStyleWithType方法来更新文字图片的相对位置。
        [self.view addSubview:btn5];
    }
    

    效果图如下:
    可以看出,通过frame设置位置,titleLabel的整体位置,包括文字背景所占位置,都可以灵活设置。


    效果呈现·titleLabel的背景色设置了透明度
    层级图

    # 写法三:利用UIButton里的setTitleEdgeInsets和setImageInsets方法,调整文字和图片的位置及大小

    其中最重要的是理解UIEdgeInsetsMake做了什么事?
    先贴网上流传最广的一张图:引用自一叶博客

    控件内边距示意图
    // UIEdgeInsets定义
    typedef struct UIEdgeInsets {
        CGFloat top, left, bottom, right;  // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
    } UIEdgeInsets;
    

    UIEdgeInsets是一个结构体,分别对控件top, left, bottom, right四个内边距进行设置。

    图中,蓝色标识为可变区域, 绿色标识为不变区域。UIEdgeInsets结构体的属性top与bottom为一对,用来指定纵向可变区域(黑色虚线矩形),left与right为一对,用来指定横向可变区域(白色虚线矩形)。当UIButton/UIImageView的size大于UIImage的size时(假设只有图片),会调整图片中可变区域大小以铺满整个控件,具体调整规则如下:
    (1)控件宽度大于图片宽度,拉伸白色虚线矩形
    # 即白色虚线矩形变宽,拉宽图片,以铺满整个控件
    (2)控件高度大于图片高度,拉伸黑色虚线矩形
    # 即黑色虚线矩形变高,拉长图片,以铺满整个控件
    (3)控制宽度小于图片宽度时,横向整体缩小(可变区与不变区比例不变)
    (4)控制高度小于图片高度时,纵向整体缩小(可变区与不变区比例不变)
    # 与UIViewContentMode有关,默认为UIViewContentModeScaleToFill:图片拉伸填充至整个UIImageView(图片可能会变形)
    (标 # 的为个人理解)

    【重点·理解】

    • 因为单个空间的UIEdgeInsets,不设置时,默认内边距都是0,即上图中白色框与蓝色框都与最外层边缘线重合。


      初始0内边距
    • 设置了UIEdgeInsets时,即给了上图(内边距示意图)中top, left, bottom, right的红线段一个长度,概括的来说,值为正的时候,对应红色线条向里伸长,为负时,向外伸长。毕竟叫做“内边距”~

    • # 更准确的来说,top和bottom为一对,用来控制纵向可变区域(黑色线框);left和right为一对,用来控制横向可变区域(白色线框)。

    • 测试:
      经过很多尝试,得出以下结论 伪代码

      默认位置关系
      1. UIButton设置了图片和标题时,默认图片在左标题在右。且图片紧贴文字,整体内容上下左右居中显示,并重置了titleLabel和imageView的UIEdgeInsets均为 (0,0,0,0),打印可得。


        图片文字均居中
      2. 图片文字均居中:在默认左图右字的基础上,计算一下,图片需要右移TitleWidth/2,即:
      [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth/2, 0, -TitleWidth/2)];
      

      另一种思路,是右边距直接-titleWidth,向右扩大一个标题的宽度,而图片会自己居中显示,所以可以达到一样的居中效果

      [btn setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, -TitleWidth)];
      

      文字同理,需要左移imageWidth/2,或直接左边距扩大一个图片的宽度。

      [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth/2, 0, imageWidth/2)];
      [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth, 0, 0)];
      
      左文字右图片
      1. 左文字右图片:在默认的基础上,计算一下,图片需要右移titleWidth,文字 需要左移imageWidth,从而达到图片文字调换位置的效果(这种情况比较好理解,也可以借助用来理解上一个)
      [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth, 0, -TitleWidth)];
      [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth, 0, imageWidth)];
      
      上图片下文字
      1. 上图片下文字:在默认的基础上,图片需要先右移titleWidth/2,再上移titleHeight/2,文字先左移imageWidth/2,再下移imageHeight/2。即:
      [btn setImageEdgeInsets:UIEdgeInsetsMake(-titleHeight/2, TitleWidth/2, titleHeight/2, -TitleWidth/2)];
      [btn setTitleEdgeInsets:UIEdgeInsetsMake(imageHeight/2, -imageWidth/2, -imageHeight/2, imageWidth/2)];
      

      当然,也可以用第二种思路:

      [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth, titleHeight, 0)];
      [btn setTitleEdgeInsets:UIEdgeInsetsMake(imageHeight, 0, 0, imageWidth)];
      
      上文字下图片
      1. 上文字下图片:在默认的基础上,图片需要先右移titleWidth/2,再下移titleHeight/2,文字先左移imageWidth/2,再上移imageHeight/2。
      [btn setImageEdgeInsets:UIEdgeInsetsMake(titleHeight/2, TitleWidth/2, -titleHeight/2, -TitleWidth/2)];
      [btn setTitleEdgeInsets:UIEdgeInsetsMake(-imageHeight/2, -imageWidth/2, imageHeight/2, imageWidth/2)];
      

      第二种思路的就省略了....

    【注意!】以上所取的width, height,要使用button.titleLabel.intrinsicContentSize.width计算titleLabel的宽度(tips:使用button.titleLabel.bounds.size.width的在iOS8以上会得到宽度为0的结果,造成错误的结果)

    网上说的最多的,理解为偏移量,不太准确。可以认为是在初始0边距的基础上,进行偏移。最好的验证方法,就是写两次setTitleEdgeInsets/setImageInsets方法,验证是在前一次的基础上继续偏移,还是以后一次的设置为准。亲测为后者。

    1. 在以上实现的过程中,可能会遇到按钮图片大小不合适,需要调整(一般是需要缩小)
      思路大致有两个:
      a. 图片缩放后再设置为按钮的image,但是这样操作后图片会变模糊!

      UIImage *btnImg = [UIImage imageNamed:@"btnImg"];
      btnImg = [self imageWihtoutScale:btnImg size:CGSizeMake(50, 50)];
      [btn setImage:btnImg forState:UIControlStateNormal];
      

      b. 还是利用UIEdgeInsetsMake,再加上setContentMode UIViewContentMode详解
      可以通过同时增加top-bottom/left-right,来缩小内边距。
      同时,之前提过,图片默认ContentMode 为UIViewContentModeScaleToFill:图片拉伸填充至整个UIImageView(图片可能会变形),所以在调整内边距前,修改[btn.imageView setContentMode:UIViewContentModeScaleAspectFit];
      UIViewContentModeScaleAspectFill
      //图片拉伸至图片的的宽度或者高度等于UIImageView的宽度或者高度为止,看图片的宽高哪一边最接近UIImageView的宽高,一个属性相等后另一个就停止拉伸。这样就可以保证图片不会变形
      具体写一个:

      左文字右图片
      UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
      btn1.frame = CGRectMake(80, 300, 300, 180);
      btn1.backgroundColor = [UIColor lightGrayColor];
      [btn1 setTitle:@"按钮" forState:UIControlStateNormal];
      btn1.titleLabel.backgroundColor = [UIColor grayColor];
      UIImage *btnImg1 = [UIImage imageNamed:@"Image1"];
      [btn1 setImage:btnImg1 forState:UIControlStateNormal];
      
      CGFloat titleWidth = btn1.titleLabel.intrinsicContentSize.width;
      CGFloat imgWidth = btn1.imageView.intrinsicContentSize.width;
      
      [btn1 setTitleEdgeInsets:UIEdgeInsetsMake(0, -imgWidth, 0, imgWidth)];
      [btn1 setImageEdgeInsets:UIEdgeInsetsMake(0, titleWidth, 0, -titleWidth)];
      [self.view addSubview:btn1];
      

      只要改两行代码就可以缩小图片,且图片不会模糊。(注意,如果使用了a思路中的图片缩放,再改变图片内边距将不起作用)


      缩小图片
      [btn1.imageView setContentMode:UIViewContentModeScaleAspectFit];
      [btn1 setImageEdgeInsets:UIEdgeInsetsMake(50, titleWidth, 50, -titleWidth)];
      

    以上。

    相关文章

      网友评论

          本文标题:UIButton自定义图片文字相对位置?layoutSubvie

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