UIButton自定義圖片文字相對(duì)位置?layoutSubviews?UIEdgeInsetsMake?

首先,說(shuō)一下需要考慮這個(gè)問(wèn)題的場(chǎng)景

  • UIButton ,同時(shí)設(shè)置圖片和文字,默認(rèn)圖片在左,文字在右
  • 需求經(jīng)??赡苁菆D片上文字下?文字左圖片右?文字上圖片下?可能位置還需要?jiǎng)討B(tài)變化或動(dòng)態(tài)展示隱藏

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

  • 1、簡(jiǎn)單粗暴-直接寫(xiě)個(gè)UIImageView,用相對(duì)布局蓋上去
    好處是動(dòng)態(tài)變動(dòng)的,針對(duì)動(dòng)態(tài)變化的需求操作起來(lái)比較靈活。但總覺(jué)得這么寫(xiě)不大專業(yè)?
  • 2、重寫(xiě)UIButton的layoutSubview方法
    好處是比較直觀,符合正常布局設(shè)計(jì)思維
  • 3、利用UIButton里的setTitleEdgeInsets和setImageInsets方法,調(diào)整文字和圖片的位置及大小
    好處是代碼量少,但是比較不好理解

下面詳細(xì)寫(xiě)下后兩種寫(xiě)法:

# 寫(xiě)法二:重寫(xiě)UIButton的layoutSubview方法

layoutSubviews是對(duì)subviews重新布局。比如,我們想更新子視圖的位置的時(shí)候,可以通過(guò)調(diào)用layoutSubviews方法,即可以實(shí)現(xiàn)對(duì)子視圖重新布局。
layoutSubviews默認(rèn)是不做任何事情的,用到的時(shí)候,需要在子類進(jìn)行重寫(xiě)。

layoutSubviews調(diào)用場(chǎng)景
①、直接調(diào)用setLayoutSubviews。
②、addSubview的時(shí)候觸發(fā)layoutSubviews。
③、當(dāng)view的frame發(fā)生改變的時(shí)候觸發(fā)layoutSubviews。
④、第一次滑動(dòng)UIScrollView的時(shí)候觸發(fā)layoutSubviews。
⑤、旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的layoutSubviews事件。
⑥、改變一個(gè)UIView大小的時(shí)候也會(huì)觸發(fā)父UIView上的layoutSubviews事件。
注意:
init初始化不會(huì)觸發(fā)layoutSubviews,但是使用initWithFrame進(jìn)行初始化時(shí),當(dāng)rect的值不為CGRectZero時(shí),也會(huì)觸發(fā)。
引用自:iOS-layoutSubviews

結(jié)合工廠設(shè)計(jì)模式,再考慮選擇分類還是繼承的寫(xiě)法?由于所寫(xiě)類需要一個(gè)type屬性,來(lái)標(biāo)志按鈕圖片及標(biāo)題屬于哪一種位置關(guān)系;而分類一般不能添加屬性(可能會(huì)覆蓋原有類的屬性),同時(shí)分類中的方法都添加給了原有類,可能會(huì)影響原有類及其子類的使用。所以這里采用繼承的寫(xiě)法比較合適 LQButton:UIButton

  • 具體實(shí)現(xiàn)代碼如下:
//  LQButton.h
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, LQButtonType){
    LQButtonTypeCenterImageCenterTitle,//圖和文字都居中
    LQButtonTypeLeftImageRightTitle,//左圖右文字
    LQButtonTypeLeftTitleRightImage,//左文字右圖
    LQButtonTypeTopImageBottomTitle,//上圖下文字 略
    LQButtonTypeTopTitleBottomImage//上文字下圖 略
};

@interface LQButton : UIButton

@property (nonatomic, assign) LQButtonType type;

//初始化一個(gè)按鈕的同時(shí)設(shè)置其圖片文字位置關(guān)系
+ (instancetype)lqButtonWithType:(LQButtonType)buttonType;
//更改按鈕的圖片文字位置關(guān)系
- (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的實(shí)現(xiàn),具體引用如下:

//  ViewController.m
// 先引入頭文件 #import "LQButton.h"
// 然后開(kāi)始使用
- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *btnImg = [UIImage imageNamed:@"btnImg"];
//這種通過(guò)縮放圖片達(dá)到設(shè)置圖片大小的方法,會(huì)導(dǎo)致圖片清晰度下降,文章最后有進(jìn)行分析
    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];
//注意:用重寫(xiě)layoutSubviews的方式,frame改變、及addSubviews的時(shí)候,均會(huì)觸發(fā)layoutSubviews。
//所以如果在此處再使用setTitleEdgeInsets/setImageEdgeInsets,將不會(huì)有效果。
//只能通過(guò)LQButton中的updateButtonStyleWithType方法來(lái)更新文字圖片的相對(duì)位置。
    [self.view addSubview:btn5];
}

效果圖如下:
可以看出,通過(guò)frame設(shè)置位置,titleLabel的整體位置,包括文字背景所占位置,都可以靈活設(shè)置。


效果呈現(xiàn)·titleLabel的背景色設(shè)置了透明度

層級(jí)圖

# 寫(xiě)法三:利用UIButton里的setTitleEdgeInsets和setImageInsets方法,調(diào)整文字和圖片的位置及大小

其中最重要的是理解UIEdgeInsetsMake做了什么事?
先貼網(wǎng)上流傳最廣的一張圖:引用自一葉博客

控件內(nèi)邊距示意圖

// 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是一個(gè)結(jié)構(gòu)體,分別對(duì)控件top, left, bottom, right四個(gè)內(nèi)邊距進(jìn)行設(shè)置。

圖中,藍(lán)色標(biāo)識(shí)為可變區(qū)域, 綠色標(biāo)識(shí)為不變區(qū)域。UIEdgeInsets結(jié)構(gòu)體的屬性top與bottom為一對(duì),用來(lái)指定縱向可變區(qū)域(黑色虛線矩形),left與right為一對(duì),用來(lái)指定橫向可變區(qū)域(白色虛線矩形)。當(dāng)UIButton/UIImageView的size大于UIImage的size時(shí)(假設(shè)只有圖片),會(huì)調(diào)整圖片中可變區(qū)域大小以鋪滿整個(gè)控件,具體調(diào)整規(guī)則如下:
(1)控件寬度大于圖片寬度,拉伸白色虛線矩形
# 即白色虛線矩形變寬,拉寬圖片,以鋪滿整個(gè)控件
(2)控件高度大于圖片高度,拉伸黑色虛線矩形
# 即黑色虛線矩形變高,拉長(zhǎng)圖片,以鋪滿整個(gè)控件
(3)控制寬度小于圖片寬度時(shí),橫向整體縮小(可變區(qū)與不變區(qū)比例不變)
(4)控制高度小于圖片高度時(shí),縱向整體縮小(可變區(qū)與不變區(qū)比例不變)
# 與UIViewContentMode有關(guān),默認(rèn)為UIViewContentModeScaleToFill:圖片拉伸填充至整個(gè)UIImageView(圖片可能會(huì)變形)
(標(biāo) # 的為個(gè)人理解)

【重點(diǎn)·理解】

  • 因?yàn)閱蝹€(gè)空間的UIEdgeInsets,不設(shè)置時(shí),默認(rèn)內(nèi)邊距都是0,即上圖中白色框與藍(lán)色框都與最外層邊緣線重合。


    初始0內(nèi)邊距
  • 設(shè)置了UIEdgeInsets時(shí),即給了上圖(內(nèi)邊距示意圖)中top, left, bottom, right的紅線段一個(gè)長(zhǎng)度,概括的來(lái)說(shuō),值為正的時(shí)候,對(duì)應(yīng)紅色線條向里伸長(zhǎng),為負(fù)時(shí),向外伸長(zhǎng)。畢竟叫做“內(nèi)邊距”~

  • # 更準(zhǔn)確的來(lái)說(shuō),top和bottom為一對(duì),用來(lái)控制縱向可變區(qū)域(黑色線框);left和right為一對(duì),用來(lái)控制橫向可變區(qū)域(白色線框)。

  • 測(cè)試:
    經(jīng)過(guò)很多嘗試,得出以下結(jié)論 偽代碼

    默認(rèn)位置關(guān)系

    1. UIButton設(shè)置了圖片和標(biāo)題時(shí),默認(rèn)圖片在左標(biāo)題在右。且圖片緊貼文字,整體內(nèi)容上下左右居中顯示,并重置了titleLabel和imageView的UIEdgeInsets均為 (0,0,0,0),打印可得。


      圖片文字均居中
    2. 圖片文字均居中:在默認(rèn)左圖右字的基礎(chǔ)上,計(jì)算一下,圖片需要右移TitleWidth/2,即:
    [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth/2, 0, -TitleWidth/2)];
    

    另一種思路,是右邊距直接-titleWidth,向右擴(kuò)大一個(gè)標(biāo)題的寬度,而圖片會(huì)自己居中顯示,所以可以達(dá)到一樣的居中效果

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

    文字同理,需要左移imageWidth/2,或直接左邊距擴(kuò)大一個(gè)圖片的寬度。

    [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth/2, 0, imageWidth/2)];
    [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth, 0, 0)];
    
    左文字右圖片
    1. 左文字右圖片:在默認(rèn)的基礎(chǔ)上,計(jì)算一下,圖片需要右移titleWidth,文字 需要左移imageWidth,從而達(dá)到圖片文字調(diào)換位置的效果(這種情況比較好理解,也可以借助用來(lái)理解上一個(gè))
    [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth, 0, -TitleWidth)];
    [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth, 0, imageWidth)];
    
    上圖片下文字
    1. 上圖片下文字:在默認(rèn)的基礎(chǔ)上,圖片需要先右移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)];
    

    當(dāng)然,也可以用第二種思路:

    [btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth, titleHeight, 0)];
    [btn setTitleEdgeInsets:UIEdgeInsetsMake(imageHeight, 0, 0, imageWidth)];
    
    上文字下圖片
    1. 上文字下圖片:在默認(rèn)的基礎(chǔ)上,圖片需要先右移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計(jì)算titleLabel的寬度(tips:使用button.titleLabel.bounds.size.width的在iOS8以上會(huì)得到寬度為0的結(jié)果,造成錯(cuò)誤的結(jié)果)

網(wǎng)上說(shuō)的最多的,理解為偏移量,不太準(zhǔn)確??梢哉J(rèn)為是在初始0邊距的基礎(chǔ)上,進(jìn)行偏移。最好的驗(yàn)證方法,就是寫(xiě)兩次setTitleEdgeInsets/setImageInsets方法,驗(yàn)證是在前一次的基礎(chǔ)上繼續(xù)偏移,還是以后一次的設(shè)置為準(zhǔn)。親測(cè)為后者。

  1. 在以上實(shí)現(xiàn)的過(guò)程中,可能會(huì)遇到按鈕圖片大小不合適,需要調(diào)整(一般是需要縮?。?br> 思路大致有兩個(gè):
    a. 圖片縮放后再設(shè)置為按鈕的image,但是這樣操作后圖片會(huì)變模糊!

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

    b. 還是利用UIEdgeInsetsMake,再加上setContentMode UIViewContentMode詳解
    可以通過(guò)同時(shí)增加top-bottom/left-right,來(lái)縮小內(nèi)邊距。
    同時(shí),之前提過(guò),圖片默認(rèn)ContentMode 為UIViewContentModeScaleToFill:圖片拉伸填充至整個(gè)UIImageView(圖片可能會(huì)變形),所以在調(diào)整內(nèi)邊距前,修改[btn.imageView setContentMode:UIViewContentModeScaleAspectFit];
    UIViewContentModeScaleAspectFill
    //圖片拉伸至圖片的的寬度或者高度等于UIImageView的寬度或者高度為止,看圖片的寬高哪一邊最接近UIImageView的寬高,一個(gè)屬性相等后另一個(gè)就停止拉伸。這樣就可以保證圖片不會(huì)變形
    具體寫(xiě)一個(gè):

    左文字右圖片

    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];
    

    只要改兩行代碼就可以縮小圖片,且圖片不會(huì)模糊。(注意,如果使用了a思路中的圖片縮放,再改變圖片內(nèi)邊距將不起作用)


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

以上。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容