首先,說一下需要考慮這個問題的場景
- UIButton 棠隐,同時設置圖片和文字石抡,默認圖片在左,文字在右
- 需求經持螅可能是圖片上文字下汁雷?文字左圖片右?文字上圖片下报咳?可能位置還需要動態(tài)變化或動態(tài)展示隱藏
查了很久侠讯,大致有以下三種做法:
- 1、簡單粗暴-直接寫個UIImageView暑刃,用相對布局蓋上去
好處是動態(tài)變動的厢漩,針對動態(tài)變化的需求操作起來比較靈活。但總覺得這么寫不大專業(yè)岩臣? - 2溜嗜、重寫UIButton的layoutSubview方法
好處是比較直觀,符合正常布局設計思維 - 3架谎、利用UIButton里的setTitleEdgeInsets和setImageInsets方法炸宵,調整文字和圖片的位置及大小
好處是代碼量少,但是比較不好理解
下面詳細寫下后兩種寫法:
# 寫法二:重寫UIButton的layoutSubview方法
layoutSubviews是對subviews重新布局谷扣。比如土全,我們想更新子視圖的位置的時候,可以通過調用layoutSubviews方法会涎,即可以實現對子視圖重新布局裹匙。
layoutSubviews默認是不做任何事情的,用到的時候末秃,需要在子類進行重寫概页。
layoutSubviews調用場景
①、直接調用setLayoutSubviews练慕。
②惰匙、addSubview的時候觸發(fā)layoutSubviews技掏。
③、當view的frame發(fā)生改變的時候觸發(fā)layoutSubviews项鬼。
④零截、第一次滑動UIScrollView的時候觸發(fā)layoutSubviews。
⑤秃臣、旋轉Screen會觸發(fā)父UIView上的layoutSubviews事件涧衙。
⑥、改變一個UIView大小的時候也會觸發(fā)父UIView上的layoutSubviews事件奥此。
注意:
init初始化不會觸發(fā)layoutSubviews弧哎,但是使用initWithFrame進行初始化時,當rect的值不為CGRectZero時稚虎,也會觸發(fā)撤嫩。
引用自: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的時候,均會觸發(fā)layoutSubviews垮卓。
//所以如果在此處再使用setTitleEdgeInsets/setImageEdgeInsets垫桂,將不會有效果。
//只能通過LQButton中的updateButtonStyleWithType方法來更新文字圖片的相對位置粟按。
[self.view addSubview:btn5];
}
效果圖如下:
可以看出诬滩,通過frame設置位置,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四個內邊距進行設置矾利。
圖中姑裂,藍色標識為可變區(qū)域, 綠色標識為不變區(qū)域男旗。UIEdgeInsets結構體的屬性top與bottom為一對舶斧,用來指定縱向可變區(qū)域(黑色虛線矩形),left與right為一對察皇,用來指定橫向可變區(qū)域(白色虛線矩形)茴厉。當UIButton/UIImageView的size大于UIImage的size時(假設只有圖片),會調整圖片中可變區(qū)域大小以鋪滿整個控件,具體調整規(guī)則如下:
(1)控件寬度大于圖片寬度什荣,拉伸白色虛線矩形
# 即白色虛線矩形變寬矾缓,拉寬圖片,以鋪滿整個控件
(2)控件高度大于圖片高度稻爬,拉伸黑色虛線矩形
# 即黑色虛線矩形變高嗜闻,拉長圖片,以鋪滿整個控件
(3)控制寬度小于圖片寬度時桅锄,橫向整體縮小(可變區(qū)與不變區(qū)比例不變)
(4)控制高度小于圖片高度時琉雳,縱向整體縮小(可變區(qū)與不變區(qū)比例不變)
# 與UIViewContentMode有關,默認為UIViewContentModeScaleToFill:圖片拉伸填充至整個UIImageView(圖片可能會變形)
(標 # 的為個人理解)
【重點·理解】
-
因為單個空間的UIEdgeInsets友瘤,不設置時翠肘,默認內邊距都是0,即上圖中白色框與藍色框都與最外層邊緣線重合辫秧。
設置了UIEdgeInsets時锯茄,即給了上圖(內邊距示意圖)中top, left, bottom, right的紅線段一個長度,概括的來說茶没,值為正的時候肌幽,對應紅色線條向里伸長,為負時抓半,向外伸長喂急。畢竟叫做“內邊距”~
# 更準確的來說,top和bottom為一對笛求,用來控制縱向可變區(qū)域(黑色線框)廊移;left和right為一對,用來控制橫向可變區(qū)域(白色線框)探入。
-
測試:
經過很多嘗試狡孔,得出以下結論偽代碼
:
-
UIButton設置了圖片和標題時,默認圖片在左標題在右蜂嗽。且圖片緊貼文字苗膝,整體內容上下左右居中顯示,并重置了titleLabel和imageView的UIEdgeInsets均為 (0植旧,0辱揭,0离唐,0),打印可得问窃。
- 圖片文字均居中:在默認左圖右字的基礎上亥鬓,計算一下,圖片需要右移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)];
- 左文字右圖片:在默認的基礎上,計算一下啊送,圖片需要右移titleWidth偿短,文字 需要左移imageWidth,從而達到圖片文字調換位置的效果(這種情況比較好理解馋没,也可以借助用來理解上一個)
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, TitleWidth, 0, -TitleWidth)]; [btn setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageWidth, 0, imageWidth)];
- 上圖片下文字:在默認的基礎上昔逗,圖片需要先右移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)];
- 上文字下圖片:在默認的基礎上笔链,圖片需要先右移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方法,驗證是在前一次的基礎上繼續(xù)偏移悉抵,還是以后一次的設置為準肩狂。親測為后者摘完。
-
在以上實現的過程中姥饰,可能會遇到按鈕圖片大小不合適,需要調整(一般是需要縮行⒅巍)
思路大致有兩個:
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)];
以上。