關于UIButton大家都很熟悉,系統默認的樣式,是image在左,title在右的,如下圖所示:
但是,很多情況下UI的設計可不是這么樣的,最常用的是image在右邊,title在左的button,和類似分享頁面的那種上面是image,下面是title的button;如下圖所示:
筆者每次需要這種需求的時候,都是查很多資料,看了一些不痛不癢的介紹,然后再很復雜的實現;效果暫且不說,耽誤了大量的時間,所以,花點時間研究了一下這種效果實現,在這里總結一下:
這里,筆者介紹兩種實現的方式:
一種是通過對系統的UIButton進行擴展(category)demo1
一種是通過繼承自UIButtondemo2,自定義:
1.對UIButton擴展(category)
這種方式主要是用到了UIButton的下面兩個屬性:
@property(nonatomic) UIEdgeInsets titleEdgeInsets; // default is UIEdgeInsetsZero
@property(nonatomic) UIEdgeInsets imageEdgeInsets; // default is UIEdgeInsetsZero
下面以設置為title在左,image在右為例進行介紹:
首先來看一下這兩個屬性的類型:
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;
他有四個參數: top left bottom right表示上左下右的偏移量,其含義為:
top : 為正數的時候,是往下偏移,為負數的時候往上偏移;
left : 為正數的時候往右偏移,為負數的時候往左偏移;
bottom : 為正數的時候往上偏移,為負數的時候往下偏移;
right :為正數的時候往左偏移,為負數的時候往右偏移;
回過頭來看這兩個屬性titleEdgeInsets就是設置title的偏移量,imageEdgeInsets就是設置image的偏移量;
那么問題來了,怎樣設置這個偏移量呢?很多介紹這種方式的使用的偏移量的都是固定的值(但是這些值是從哪里參考而來并沒有說明),一旦你的button的frame和他介紹的不一樣,就需要多次修改,以找到最合適的偏移量,看得云里霧里不說,調整偏移量也花費了很多時間;經過筆者的一些測試,發現還是有些公共的東西可以使用的:
相信大家第一個想到的就是UIButton的titleLabel和imageView的frame;所以,首先獲取他們的size:
CGSize titleSize = button.titleLabel.bounds.size;
CGSize imageSize = button.imageView.bounds.size;
然后設置他們各自偏移量:
button.imageEdgeInsets = UIEdgeInsetsMake(0,titleSize.width, 0, -titleSize.width);
button.titleEdgeInsets = UIEdgeInsetsMake(0, -imageSize.width, 0, imageSize.width);
這兩個屬性的默認值都是0,所以,在不需要偏移的方向上,偏移量設置為0即可,那么,怎么知道哪個方向需要偏移,哪個方向不需要偏移呢?
其實,很簡單,只需要仔細觀察,偏移前(系統默認布局)和偏移后(你想要的布局)有哪些變化:
對于image:由左邊移動到右邊,可知,上下不變,左右偏移,即image的left和right變化;
對于title:由右邊移動到左邊,同樣是上下不變,左右偏移,即title的left和right變化;
清楚了哪些方向上有變化,接下來就是變化多少的問題了:
因為要把image移動到button的右邊,需要往右移動,所以imageEdgeInsets距左邊界(left)的偏移量需要設置為標題的寬度,即:titleSize.width,右邊的偏移量(right)同樣是titleSize.width,但是應該是負的,即:titleSize.width;其他方向沒有移動,直接設為默認值0;
同理,title需要往左移動,需要設置titleEdgeInsets距離左邊界(left)的偏移量為負的image的寬度,即:-imageSize.width,此時title距離右邊界的偏移量(right)就不是0了,而應該是image的寬度,即:imageSize.width;
設置完后,看一下效果,似乎并不是預想的那樣:
回頭仔細看了下設置,邏輯上似乎并沒有錯誤,那是哪兒出了問題呢?仔細查找后找到了問題,因為這時獲取到的titleSize的值為0:
- (void)LZSetbuttonType:(LZCategoryType)lzType {
//需要在外部修改標題背景色的時候將此代碼注釋,注意這行代碼確保了titleLabel和imageview的偏移能夠到達指定位置
// self.titleLabel.backgroundColor = [UIColor darkGrayColor];
// self.imageView.backgroundColor = [UIColor yellowColor];
CGSize titleSize = self.titleLabel.bounds.size;
NSLog(@"titleSize.height = %f,titleSize.width = %f",titleSize.height,titleSize.width);
CGSize imageSize = self.imageView.bounds.size;
CGFloat interval = 1.0;
if (lzType == LZCategoryTypeLeft) {
/*
UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
top : 為正數的時候,是往下偏移,為負數的時候往上偏移;
left : 為正數的時候往右偏移,為負數的時候往左偏移;
bottom : 為正數的時候往上偏移,為負數的時候往下偏移;
right :為正數的時候往左偏移,為負數的時候往右偏移;
*/
[self setImageEdgeInsets:UIEdgeInsetsMake(0,titleSize.width + interval, 0, -(titleSize.width + interval))];
[self setTitleEdgeInsets:UIEdgeInsetsMake(0, -(imageSize.width + interval), 0, imageSize.width + interval)];
} else if(lzType == LZCategoryTypeBottom) {
[self setImageEdgeInsets:UIEdgeInsetsMake(0,0, titleSize.height + interval, -(titleSize.width))];
[self setTitleEdgeInsets:UIEdgeInsetsMake(imageSize.height + interval, -(imageSize.width), 0, 0)];
}
}
打印結果如下:
2017-02-08 10:40:06.020 LZButtonCategory[5040:359483] titleSize.height =
0.000000,titleSize.width = 0.000000
將代碼片段中的注釋內容
// 需要在外部修改標題背景色的時候將此代碼注釋,注意這行代碼確保了titleLabel和imageview的偏移能夠到達指定位置
self.titleLabel.backgroundColor = self.backgroundColor;
self.imageView.backgroundColor = self.backgroundColor;
放開后發現:
2017-02-08 10:42:29.640 LZButtonCategory[5058:361026] titleSize.height =
21.500000,titleSize.width = 73.500000
這樣設置之后,基本能夠實現需求了,但是子控件之間是有間隙的,這里我設置了1像素的寬度:
CGFloat interval = 1.0;
然后設置imageEdgeinsets
button.imageEdgeInsets = UIEdgeInsetsMake(0,titleSize.width + interval, 0, -(titleSize.width + interval));
button.titleEdgeInsets = UIEdgeInsetsMake(0, -(imageSize.width + interval), 0, imageSize.width + interval);
效果圖如下:
在設置偏移量的時候,誤差還是有的,經過測試,最好的解決方式是,button的大小設置,要恰到好處能夠容下標題和圖片,對于追求完美的人,可自己修改偏移參數;
針對此方法,本人寫了一個UIButton的 demo地址,里面的類目可直接拿來使用,簡單調用一個方法即可;
2.通過自定義Button
該方法,是通過自定義一個button,繼承自UIButton,然后重寫下面兩個方法:
- (CGRect)titleRectForContentRect:(CGRect)contentRect;
- (CGRect)imageRectForContentRect:(CGRect)contentRect;
對于這兩個方法,網上的一些介紹真是不得不吐槽了,模糊不清;后來根據筆者多次嘗試,個人理解為:
方法的參數contentRect,即內容的frame,其值和button的bounds是一樣的,通過這個參數可以獲取到當前button的size;返回值為CGRect類型,即是title或image在button的絕對坐標值;換句話說,這里返回的是一個絕對坐標,即button的子控件在button上的絕對布局,這里可以返回一個寫死的frame(查到的使用此方法的也都是寫死的frame),但要注意,不要超過contentRect的范圍;
有一點需要說明:
這兩個方法不是只調用一次,會被多次調用,只要button的title改變,都會調用此方法,最后一次調用,返回的frame值,才是最終的布局frame,所以,在這里,可以通過獲取button的標題,動態地修改其frame,使title和image顯示緊湊;
明白了這兩個方法,下面就開始使用它:
- (void)setLzType:(LZRelayoutButtonType)lzType {
_lzType = lzType;
if (lzType != LZRelayoutButtonTypeNomal) {
self.titleLabel.textAlignment = NSTextAlignmentCenter;
}
}
//重寫父類方法,改變標題和image的坐標
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
if (self.lzType == LZRelayoutButtonTypeLeft) {
CGFloat x = contentRect.size.width - self.offset - self.imageSize.width ;
CGFloat y = contentRect.size.height - self.imageSize.height;
y = y/2;
CGRect rect = CGRectMake(x,y,self.imageSize.width,self.imageSize.height);
return rect;
} else if (self.lzType == LZRelayoutButtonTypeBottom) {
CGFloat x = contentRect.size.width - self.imageSize.width;
CGFloat y = self.offset ;
x = x / 2;
CGRect rect = CGRectMake(x,y,self.imageSize.width,self.imageSize.height);
return rect;
} else {
return [super imageRectForContentRect:contentRect];
}
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
if (self.lzType == LZRelayoutButtonTypeLeft) {
return CGRectMake(0, 0, contentRect.size.width - self.offset - self.imageSize.width , contentRect.size.height);
} else if (self.lzType == LZRelayoutButtonTypeBottom) {
return CGRectMake(0, self.offset + self.imageSize.height , contentRect.size.width , contentRect.size.height - self.offset - self.imageSize.height );
} else {
return [super titleRectForContentRect:contentRect];
}
}
顯示效果如下:
總結
以上兩種方式都能實現重新布局UIButton的子控件的效果,各有優缺點:
第一種方式:需要精確的設置偏移量,但是有些量是無法獲取的,只能在使用時調整,特別是設置標題在底部時,總感覺image的左右距離button的左右間隙不一致;
第二種方式:對frame的控制就比較自由了,需要注意的是對間隔的把控,使用這種方式就沒有第一種的方式問題了;
不管是系統默認,還是我們修改之后的button,其frame的設置都是很重要的,不合適的frame會讓button看起來很不舒服...