首先,說(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ě)法三:利用UIButton里的setTitleEdgeInsets和setImageInsets方法,調(diào)整文字和圖片的位置及大小
其中最重要的是理解UIEdgeInsetsMake做了什么事?
先貼網(wǎng)上流傳最廣的一張圖:引用自一葉博客

// 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)系-
UIButton設(shè)置了圖片和標(biāo)題時(shí),默認(rèn)圖片在左標(biāo)題在右。且圖片緊貼文字,整體內(nèi)容上下左右居中顯示,并重置了titleLabel和imageView的UIEdgeInsets均為 (0,0,0,0),打印可得。
圖片文字均居中 - 圖片文字均居中:在默認(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)];左文字右圖片- 左文字右圖片:在默認(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)];上圖片下文字- 上圖片下文字:在默認(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)];上文字下圖片- 上文字下圖片:在默認(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è)為后者。
-
在以上實(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)];
以上。







