前言
平常開發中,UIButton是使用頻率非常高的控件。除了可點擊之外,還因為其能夠同時顯示文案和圖片。默認的UIButton,圖片在左,文字在右,且文字是緊緊挨著圖片的。大多數情況下,UIButton已經可以滿足需求了,然而,總有一些意外……比如,設計讓文字和圖片不要離得很近;比如,設計讓文字在左,圖片在右;比如,設計讓圖片在上,文字在下……
這時候就會用到本文的主角:imageEdgeInsets和titleEdgeInsets了。使用imageEdgeInsets和titleEdgeInsets時,可能或多或少的都會有一種感覺,就是難以捉摸,每次都是嘗試多次才能達到一個良好的效果。再徹底會使用imageEdgeInsets和titleEdgeInsets之前,我們先來分析下面對的問題。
圖片和文字保持間距
首先面臨的第一個問題就是圖片和文字之間保持一個距離。其實碰到這樣的需求解決方法有很多,如果對imageEdgeInsets和titleEdgeInsets不熟悉,完全沒必要使用這兩個屬性。
圖片右側透明
UIButton 默認圖片在左,文字在右,且文字和圖片緊鄰,現在的需求是圖片和文字之間留有一定的間距。一種方法是,如果設計提供的圖片右側有一些透明的地方,那么整體給用戶的感覺就是圖片和文字之間有一定的間距。只不過這種方法需要讓設計切圖。
文案加空格
其實如果只是想讓圖片和文字之間有一定間距,不用設計切圖,代碼完全可以控制,而且特別簡單。方法就是在文案的前面加空格,比如說本來要顯示的文案是"聯系我們",程序中可以設置為"????? 聯系我們",這樣最終展示給用戶的效果圖片和文件之間也有一定的間距。這種方法完全是開發人員可控的。
圖片和文字位置調整
圖片和文字的位置調整,主要是指圖片在右,文字在左,或者圖片在上,文字在下這兩種情況,目前還沒有碰到過文字在上,圖片在下的需求。如果設計的圖是圖片和文字的位置有了調整,那么僅僅靠增加圖片透明度、文案加空格等類似的方法是滿足不了需求的。
解決方法有兩種,一種是使用UIView,另外就是使用imageEdgeInsets和titleEdgeInsets。
使用UIView解決
使用UIView的思路非常簡單。就是使用三個控件,一個UIView作為父控件,一個UILabel用來顯示內容,一個UIImageView用來顯示圖片。由于UILabel和UIImageView都是我們自己寫的,其frame我們可以隨意控制,圖片是在左、在右,還是在上,都是可以控制的。
至于UIButton的點擊事件,因為UIView不具備點擊事件,我們可以給UIView增加手勢UITapGestureRecognizer,用來模擬點擊行為。看上去完美解決了這個問題,但是這種方案是有缺點的,看一下:
首先這種方法是使用了3個控件,而如果使用UIButton,只需要使用1個控件。雖然UIButton內部也包含了一個UIImageView和一個UILabel,性能上可能沒有太大的差距,但是這種寫法麻煩啊。本來使用UIButton5行代碼就可以搞定了,結果使用UIView的形式,寫了20行代碼,實現比較繁瑣。
雖然可以給UIView添加手勢模擬點擊行為,但是,UIView是沒有高亮狀態的。UIButton默認是有普通狀態和高亮狀態,點擊時會顯示默認的高亮狀態,這個效果UIView是實現不了的。如果想實現,需要再增加更多的代碼。
可以說,使用UIView能夠解決問題,但是解決方法不夠優雅。
imageEdgeInsets和titleEdgeInsets
蘋果可能已經考慮到了開發者會有這樣的需求,于是提供了imageEdgeInsets和titleEdgeInsets兩個屬性。然而,由于蘋果沒有介紹這兩個屬性的原理,對兩個屬性的描述又不是特別清晰,導致使用起來難度較大,對于經驗不足的開發者,每次使用都要嘗試多次。
先來看下兩者的定義。
imageEdgeInsets和titleEdgeInsets的定義
imageEdgeInsets和titleEdgeInsets的定義在UIButton.h中,如下:
@property(nonatomic) UIEdgeInsets titleEdgeInsets; // default is UIEdgeInsetsZero
@property(nonatomic) UIEdgeInsets imageEdgeInsets; // default is UIEdgeInsetsZero
可以看到,imageEdgeInsets和titleEdgeInsets都是UIEdgeInsets類型,且默認取值是UIEdgeInsetsZero。看一下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是一個結構體,四個值分別是上、左、下、右,值可以為正,也可以為負。
在了解imageEdgeInsets和titleEdgeInsets的使用之前,有三點一定要明白:
無論是imageEdgeInsets還是titleEdgeInsets,都是和原控件的位置相比較的。imageEdgeInsets是和原imageView的位置比較,titleEdgeInsets是和原label的位置比較。
imageEdgeInsets和titleEdgeInsets中的值為正,則是該方向上的擴張,如果值為負,則是該方向上的縮減。舉例來說,對于左側,擴張是更向左,即frame的x值減小;對于右側擴張是更向右,frame的width值更大。
UIEdgeInsets是對稱的,左右對稱,上下對稱。因此,在設置imageEdgeInsets和titleEdgeInsets時盡量也要對稱,比如[0,-5,0,5],左右對稱,同理上下也要對稱。之所以要對稱,是為了不拉伸imageView和titleLabel。
imageEdgeInsets和titleEdgeInsets的使用
有了上面的了解和介紹,來看一下使用imageEdgeInsets和titleEdgeInsets如何解決文中最開始提到的問題。
首先是圖片和文字之間保持間距。圖片和文字保持間距有兩種處理方式,一種是圖片左移,一種是文字右移。實際上,最開始提到的給圖片右側增加透明度以及文案加空格正是分別對應了圖片左移和文字右移。如果使用imageEdgeInsets和titleEdgeInsets,對應的也是這兩種處理方式。我們可以調整imageEdgeInsets來使圖片左移,同理也可以調整titleEdgeInsets來使圖片右移,可以達到相同的效果。
寫代碼驗證一下。首先看一下普通狀態的UIButton:
為了后續方便,先定義一些宏:
#define DefaultFont [UIFont systemFontOfSize:20.0f]
#define DefaultImage [UIImage imageNamed:@"mail"]
#define DefaultText @"技術支持"
生成一個普通的UIButton:
- (UIButton *)createBtn
{
UIButton *btn = [[UIButton alloc] init];
[btn setTitle:DefaultText forState:UIControlStateNormal];
btn.titleLabel.font = DefaultFont;
[btn setImage:DefaultImage forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.titleLabel.backgroundColor = [UIColor redColor];
return btn;
}
將普通button顯示到屏幕上:
UIButton *btn0 = [self createBtn];
[btn0 sizeToFit];
btn0.frame = CGRectMake(100,100,btn0.frame.size.width,btn0.frame.size.height);
[self.view addSubview:btn0];
看一下效果:
可以看到圖片和文字是緊緊貼在一起的。
調整imageEdgeInsets的值,使兩者保持一定間距。像上文說的,讓圖片左移,既然是左移,相當于是左側擴張,為了不拉伸圖片,對應的右側就要縮減。也就是說左側的值為負,右側的值為正,且兩者的絕對值要相等。
UIButton *btn1 = [self createBtn];
btn1.imageEdgeInsets = UIEdgeInsetsMake(0, -5, 0, 5);
[btn1 sizeToFit];
btn1.frame = CGRectMake(100,200,btn1.frame.size.width,btn1.frame.size.height);
[self.view addSubview:btn1];
看一下效果:
[圖片上傳失敗...(image-af7662-1545731919275)]
可以看到圖片和文字之間有了一定的間距。
接下來再來看一下,使用imageEdgeInsets和titleEdgeInsets,讓文字在左,圖片在右,為達到這種效果,imageEdgeInsets和titleEdgeInsets的值都需要調整。先看下圖:
[圖片上傳失敗...(image-7c71bb-1545731919275)]
默認圖片在左,文字在右,現在要將文字放到左側,圖片放到右側。從上圖可以看出,最終文字是向左移了,圖片是向右移了。根據前面的介紹,文字向左側移動,那么titleEdgeInsets中的left字段應該為負,right字段應該為正,那么應該移動多少呢?從上面的圖也很清晰的看到,移動的距離就是圖片的寬度。再看圖片,圖片整體右移,因此imageEdgeInsets中的left字段應該為正,right字段應該為負,右移的距離是多少呢?正好是文字的寬度。
看一下實現代碼:
UIButton *btn2 = [self createBtn];
UILabel *label = [[UILabel alloc] init];
label.font = DefaultFont;
label.text = DefaultText;
[label sizeToFit];
btn2.imageEdgeInsets = UIEdgeInsetsMake(0, label.frame.size.width, 0, label.frame.size.width * -1);
btn2.titleEdgeInsets = UIEdgeInsetsMake(0, DefaultImage.size.width * -1, 0, DefaultImage.size.width);
[btn2 sizeToFit];
btn2.frame = CGRectMake(100,300,btn2.frame.size.width,btn2.frame.size.height);
[self.view addSubview:btn2];
效果如下:
如果想讓兩者之間有間距,也很簡單,移動時只需要把值調大一些即可:
UIButton *btn3 = [self createBtn];
btn3.imageEdgeInsets = UIEdgeInsetsMake(0, label.frame.size.width, 0, label.frame.size.width * -1);
btn3.titleEdgeInsets = UIEdgeInsetsMake(0, DefaultImage.size.width * -1 - 5, 0, DefaultImage.size.width + 5);
[btn3 sizeToFit];
btn3.frame = CGRectMake(100,400,btn3.frame.size.width,btn3.frame.size.height);
[self.view addSubview:btn3];
讓兩者之間保持間距為5,效果如下:
再來看最后一種效果,即圖片在上,文字在下。為達到這樣的效果,我們可以將圖片向上移,文字的y值保持不變;也可以圖片保持不變,文字的位置下移;可以達到相同的效果。如下圖:
當然,也可以圖片上移一部分,文字下移一部分,只是沒有這個必要。
我們以圖片上移為例。圖片上移,說明imageView的edgeInsets的top值擴張,即top為負,那么bottom值為正。移動的距離正好是圖片的高度。
btn4.imageEdgeInsets = UIEdgeInsetsMake(-DefaultImage.size.height, 0, DefaultImage.size.height, 0);
為了保持美觀,我們還需要讓titleLabel和imageView的中心點對齊,即titleLabel左移。titleLabel左移,說明titleLabel的edgeInsets的left擴張,值為負,right值為正。titleLabel左移的距離需要計算。從上圖可以看出,titleLabel原來的x值正好是imageView.width,現在的x值是(imageView.width - titleLabel.width) * 0.5,因此,titleLabel需要左移的距離是:
CGFloat diff5X = DefaultImage.size.width - (DefaultImage.size.width - label.frame.size.width) * 0.5;
通常情況下,titleLabel的高度和imageView的高度是不同的,如下圖:
[圖片上傳失敗...(image-6c05b6-1545731919276)]
titleLabel和imageView豎直方向的中心點一致,但是兩者的高度是不同的。如果titleLabel的y值不調整,那么圖片和文字之間會有一定的間距,這個間距對于開發者來說不好控制。因此,最好的辦法是將titleLabel向上調整,和imageView緊鄰。調整的距離是多少呢?根據上圖也很容易看出來
CGFloat diffY = (DefaultImage.size.height - label.frame.size.height) * 0.5;
完整代碼如下:
UIButton *btn4 = [self createBtn];
btn4.imageEdgeInsets = UIEdgeInsetsMake(-DefaultImage.size.height, 0, DefaultImage.size.height, 0);
CGFloat diff = DefaultImage.size.width - (DefaultImage.size.width - label.frame.size.width) * 0.5;
CGFloat diffY = (DefaultImage.size.height - label.frame.size.height) * 0.5;
btn4.titleEdgeInsets = UIEdgeInsetsMake(-diffY, diff * -1, diffY, diff);
[btn4 sizeToFit];
btn4.frame = CGRectMake(20,550,btn4.frame.size.width,btn4.frame.size.height);
[self.view addSubview:btn4];
效果圖如下:
如果想讓文字和圖片之間保持間距,只需要把文字向下移動一些即可,代碼如下:
UIButton *btn5 = [self createBtn];
btn5.imageEdgeInsets = UIEdgeInsetsMake(-DefaultImage.size.height, 0, DefaultImage.size.height, 0);
CGFloat diff5X = DefaultImage.size.width - (DefaultImage.size.width - label.frame.size.width) * 0.5;
CGFloat diff5Y = (DefaultImage.size.height - label.frame.size.height) * 0.5;
btn5.titleEdgeInsets = UIEdgeInsetsMake(-diffY + 5, diff * -1, diffY - 5, diff);
[btn5 sizeToFit];
btn5.frame = CGRectMake(200,550,btn5.frame.size.width,btn5.frame.size.height);
[self.view addSubview:btn5];
效果圖如下:
封裝
了解了imageEdgeInsets和titleEdgeInsets如何使用后,之后再碰到文中開頭提到打需求,相信大家已經可以從容應對了。然而,imageEdgeInsets和titleEdgeInsets實在難以理解,一段時間不用,可能就忘了應該如何調整偏移量……每次使用之前都研究一下其偏移量的原理,好像又有點浪費時間。
為了方便日常開發,我封裝了ACEdgeInsetsBtn,ACEdgeInsetsBtn是UIButton的子類,主要目的就是解決UIButton中文字、圖片有間隔、或者位置調整的問題。使用也很簡單,只需要傳入image,文案,字體,imageView和titleLabel的間距,類型即可,如下:
/**
初始化方法
@param image btn所要顯示的image
@param text btn所要顯示的text
@param font btn的字體信息
@param edgeInsetsType 圖片的位置類型
@param space image和文字之間的間距
@return 返回實例button對象
*/
- (instancetype)initWithImage:(UIImage *)image text:(NSString *)text font:(UIFont *)font edgeInsetsType:(ACEdgeInsetsBtnType)edgeInsetsType space:(CGFloat)space;
ACEdgeInsetsBtnType是一個枚舉類型,目前包含了圖片左文子右、圖片右文字左、圖片上文字下三種類型,如下:
/**
btn類型,以圖片的位置為標準
*/
typedef NS_ENUM(NSInteger, ACEdgeInsetsBtnType){
// 普通狀態,即圖片在左,文字在右
ACEdgeInsetsBtnTypeNormal = 1,
// 圖片在右,文字在左
ACEdgeInsetsBtnTypeRight ,
// 圖片在上,文字在下
ACEdgeInsetsBtnTop
};
傳入對應的參數,直接生成期望的Btn:
ACEdgeInsetsBtn *btn = [[ACEdgeInsetsBtn alloc] initWithImage:DefaultImage text:DefaultText font:DefaultFont edgeInsetsType:ACEdgeInsetsBtnTypeNormal space:5];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.frame = CGRectMake(50,80,btn.frame.size.width,btn.frame.size.height);
[self.view addSubview:btn];
項目放在了github上,地址。