淺析UIButton的imageEdgeInsets與titleEdgeInsets

導(dǎo)語

系統(tǒng)的 UIButton 默認(rèn)狀態(tài)下的樣式是圖標(biāo)在左標(biāo)題在右缎谷,但有時(shí)候可能需要不同的排版莉钙。當(dāng)然可以通過繼承添加子視圖來實(shí)現(xiàn)需求,但本文打算通過理解 UIButton 自帶的 imageEdgeInsetstitleEdgeInsets 屬性實(shí)現(xiàn)該功能阳距。

主要內(nèi)容包含以下兩點(diǎn):

  • 淺析 imageEdgeInsetstitleEdgeInsets 的屬性的原理 [個(gè)人觀點(diǎn)]
  • 簡(jiǎn)單實(shí)現(xiàn)圖標(biāo)在右標(biāo)題在左,圖標(biāo)在上標(biāo)題在下腐巢。

環(huán)境

macOS Sierra 10.12.4
Xcode 8.3.2
iPhone 6S (10.1.1)

流程

先從蘋果官方對(duì)該方法的注釋入手

The inset or outset margins for the rectangle around the button’s title text.

使用此屬性可調(diào)整按鈕標(biāo)題的有效繪圖矩形的大小并重新定位。(來自 google 翻譯)

Use this property to resize and reposition the effective drawing rectangle for the button title. You can specify a different value for each of the four insets (top, left, bottom, right). A positive value shrinks, or insets, that edge—moving it closer to the center of the button. A negative value expands, or outsets, that edge. Use the UIEdgeInsetsMake function to construct a value for this property. The default value is UIEdgeInsetsZero.

關(guān)于 UIEdgeInsetsMaketop, left, bottom, right正數(shù)表明更靠近按鈕的中心嘲驾,負(fù)數(shù)表示更靠近按鈕的邊緣淌哟,默認(rèn)為 UIEdgeInsetsZero

問題 1

  • margins 是邊距的含義辽故,那原始的位置在哪?

測(cè)試代碼

#import <UIKit/UIKit.h>

@interface JAButton : UIButton

@end

#import "JAButton.h"

@implementation JAButton
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.layer.borderColor = [UIColor blueColor].CGColor;
        self.layer.borderWidth = 1;
        [self setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        
        // [1]
        [self setTitle:@"測(cè)試" forState:UIControlStateNormal];
        
        // [2]
        [self setImage:[UIImage imageNamed:@"arrow"] forState:UIControlStateNormal];
    }
    return self;
}
- (void)layoutSubviews {
    [super layoutSubviews];
    
    // [3]
    self.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);
    
    // [4]
    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);
}
@end

...

JAButton *b = [[JAButton alloc] initWithFrame:CGRectMake(10, 100, 100, 40)];
[self.view addSubview:b];

測(cè)試 1

只有標(biāo)題 [注釋 2 3 4]

標(biāo)題的位置如下


標(biāo)題居中

測(cè)試 2

只有圖標(biāo) [注釋 1 3 4]

圖標(biāo)位置信息如下


圖標(biāo)居中

測(cè)試 3

標(biāo)題 + 圖標(biāo) [注釋 3 4]

圖標(biāo)


標(biāo)題


為了維持居中就要將標(biāo)題和圖標(biāo)看做一個(gè)整體即寬度和為 37 + 10 = 47 徒仓,用 UIButton 的寬度來減去總寬度再乘以 0.5 即可實(shí)現(xiàn)整體居中,因此圖標(biāo)的 X 就是 (100 - 47) * 0.5 = 26.5

測(cè)試 4

只調(diào)整 titleEdgeInsets [注釋 2 4]

標(biāo)題的位置信息如下

測(cè)試 5

只調(diào)整 imageEdgeInsets [注釋 1 3]

圖標(biāo)的位置信息如下

問題 2

比較測(cè)試 4 與測(cè)試 1 會(huì)發(fā)現(xiàn)

  • titleEdgeInsetsright 設(shè)置了 10 , X 卻向左偏移了 5 ?

同樣對(duì)比 測(cè)試 5 與測(cè)試 2 會(huì)發(fā)現(xiàn)

  • imageEdgeInsetsright 設(shè)置了 10 , X 卻向左偏移了 5

為什么會(huì)有這種減半的現(xiàn)象呢?

尋找 & 分析

在搜索后誊垢,這篇 博客對(duì)我有所啟發(fā)

回答 1

它們只是image和button相較于原來位置的偏移量掉弛,那什么是原來的位置呢?就是這個(gè)

沒有設(shè)置edgeInset時(shí)候的位置了喂走。
如要要image在右邊殃饿,label在左邊,那image的左邊相對(duì)于button的左邊右移了labelWidth的距離芋肠,image的右邊相對(duì)于label的左邊右移了labelWidth的距離
所以乎芳,self.oneButton.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth, 0, -labelWidth); 為什么是負(fù)值呢?因?yàn)檫@是contentInset帖池,是偏移量奈惑,不是距離
同樣的,label 的右邊相對(duì)于 button 的右邊左移了 imageWith 的距離睡汹,label 的左邊相對(duì)于 image 的右邊左移了 imageWith 的距離
所以 self.oneButton.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWith, 0, imageWith); 這樣就完成image在右邊肴甸,label在左邊的效果了。

但是對(duì)下面的前置知識(shí)點(diǎn)囚巴,感覺有些疑惑

前置知識(shí)點(diǎn):titleEdgeInsets是title相對(duì)于其上下左右的inset原在,跟tableView的contentInset是類似的,如果只有title彤叉,那它上下左右都是相對(duì)于button的庶柿,image也是一樣;
如果同時(shí)有image和label秽浇,那這時(shí)候image的上左下是相對(duì)于button澳泵,右邊是相對(duì)于label的;title的上右下是相對(duì)于button兼呵,左邊是相對(duì)于image的。

我認(rèn)為雖然兩者都在 UIButton 中腊敲,但 Apple 既然將 imageEdgeInsetstitleEdgeInsets 拆成兩個(gè)屬性击喂,兩者的位置應(yīng)該不互相依賴才對(duì),即使依賴碰辅,也應(yīng)該依賴 UIButton 這個(gè)父視圖比較合適懂昂。

測(cè)試 6

在標(biāo)題和圖標(biāo)同時(shí)存在的情況下,調(diào)整 titleEdgeInsets [注釋 4]

標(biāo)題

圖標(biāo)


測(cè)試 7

在標(biāo)題和圖標(biāo)同時(shí)存在的情況下没宾,調(diào)整 imageEdgeInsets [注釋 3]

標(biāo)題


圖標(biāo)

測(cè)試 8

在標(biāo)題和圖標(biāo)同時(shí)存在的情況下凌彬,調(diào)整 imageEdgeInsetstitleEdgeInsets [不注釋]

標(biāo)題


圖標(biāo)


小結(jié)

將測(cè)試 3 和 測(cè)試 6 或 測(cè)試 3 和測(cè)試 7 對(duì)比會(huì)發(fā)現(xiàn)即使在標(biāo)題和圖標(biāo)同時(shí)存在的情況下沸柔,單獨(dú)調(diào)整 imageEdgeInsetstitleEdgeInsets 都只會(huì)對(duì)對(duì)應(yīng)的視圖的位置產(chǎn)生影響,而且影響同樣是 減半 的铲敛。而通過將測(cè)試 3 和 測(cè)試 8 比較褐澎, titleEdgeInsetsimageEdgeInsets 同時(shí)作用的情況下也是一樣的。

佐證

上面的參考博客中提到 Aligning text and image on UIButton with imageEdgeInsets and titleEdgeInsetsStackOverflow 上關(guān)于這個(gè)問題的一個(gè)討論伐蒋。里面有這樣一段話工三,對(duì)我有所啟發(fā)

I believe that this documentation was written imagining that the button has no title, just an image. It makes a lot more sense thought of this way, and behaves how UIEdgeInsets usually do. Basically, the frame of the image (or the title, with titleEdgeInsets) is moved inwards for positive insets and outwards for negative insets。

官方的注釋也許正如上面這段話所表達(dá)的先鱼,只是在告訴我們 imageEdgeInsets/titleEdgeInsets 其實(shí)只是描述了父視圖(UIButton)與它們各自視圖的間距俭正。

回答 2

在理解了 imageEdgeInsets/titleEdgeInsets 的獨(dú)立性后,我嘗試用自己的話來說明為什么會(huì)存在"減半"的效果焙畔。

比如下面的代碼

self.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);

在水平方向上 titleLabel 左側(cè)偏移量為 0掸读,titleLabel 右側(cè)偏移量為 10 ,但是偏移量 X 的變化量宏多。

X 左移 10 那么實(shí)際上的偏移屬性值應(yīng)該為 (0,-10,0,10) :

左偏移量(相對(duì)于 button 左側(cè))儿惫,title 從原始向目標(biāo)是往邊緣方向,所以是負(fù)值(參考本文最初的文檔理解)绷落,偏移量為 10姥闪,右偏移量(相對(duì) button 右側(cè)),從原始到目標(biāo)是往中間方向砌烁,所以是正值筐喳。

因此上面的代碼表明 titleLabel 相對(duì) UIButton 左側(cè)不改變,右側(cè)改變 10 函喉,應(yīng)該是做不到的避归,左側(cè)間距增加1,右側(cè)間距必然會(huì)減少1管呵。會(huì)被等價(jià)轉(zhuǎn)換為

self.titleEdgeInsets = UIEdgeInsetsMake(0, -5, 0, 5);

個(gè)人推測(cè)

如何轉(zhuǎn)換?

參考 iOS 的坐標(biāo)系梳毙,水平向右為 X 軸正方向 ,垂直向下為 Y 正方向,要根據(jù) titleEdgeInsets / imageEdgeInsets 去計(jì)算 titleimage 的坐標(biāo)捐下,可以對(duì) UIEdgeInsets 結(jié)構(gòu)體的四個(gè)成員( top , left , bottom , right ) 進(jìn)行處理 (在負(fù)方向留正偏移量即是往正方向偏移)账锹。

公式如下

  • 水平方向上 X 的偏移量: (left + (-1) * right) / 2
  • 垂直方向上 Y 的偏移量: (top + (-1) * bottom) / 2

直白點(diǎn)的話: 負(fù)間距(left,top)更靠近,正間距(left,top)更遠(yuǎn)離坷襟,原始狀態(tài)就是 UIEdgeInsets 全為 0 即你不去操作 titleEdgeInsets / imageEdgeInsets 時(shí)奸柬。

個(gè)人推測(cè)

實(shí)踐

實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn);不管黑貓白貓婴程,能抓老鼠的就是好??...

圖標(biāo)在右標(biāo)題在左

根據(jù)上面的猜想廓奕,要實(shí)現(xiàn)圖標(biāo)與標(biāo)題的位置交換,很簡(jiǎn)單: imageView 左側(cè)相對(duì)于 UIButton 向中央移動(dòng)了 titleLabel 的寬度,記為 titleLabel.w桌粉,右側(cè)相對(duì)于 UIButton 向邊緣同樣移動(dòng)了 titleLabel.w 因此

- (void)layoutSubviews {
    [super layoutSubviews];
    
    // [3]
//    self.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);
//    
//    // [4]
//    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);
    
    self.imageEdgeInsets = UIEdgeInsetsMake(0, self.titleLabel.frame.size.width, 0, -self.titleLabel.frame.size.width);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView.frame.size.width, 0, self.imageView.frame.size.width);
}

標(biāo)題


圖標(biāo)


但是如果用直接去設(shè)置 UIButton

UIButton *b = [[UIButton alloc] initWithFrame:CGRectMake(10, 100, 100, 40)];
b.layer.borderColor = [UIColor blueColor].CGColor;
b.layer.borderWidth = 1;
[b setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[b setTitle:@"測(cè)試" forState:UIControlStateNormal];
[b setImage:[UIImage imageNamed:@"arrow"] forState:UIControlStateNormal];
    [self.view addSubview:b];
    b.imageEdgeInsets = UIEdgeInsetsMake(0, b.titleLabel.frame.size.width, 0, -b.titleLabel.frame.size.width);
    b.titleEdgeInsets = UIEdgeInsetsMake(0, -b.imageView.frame.size.width, 0, b.imageView.frame.size.width);

你會(huì)發(fā)現(xiàn)效果并不如意蒸绩,原因是因?yàn)?titleLabel 的尺寸不正確

我想到的解決方法有三種

  • 繼承 UIButton ,在子類的 layoutSubviews 中進(jìn)行處理
  • 參考上面博客的作者在 Demo_ButtonImageTitleEdgeInsets 提供的铃肯,用 CGFloat labelWidth = [self.titleLabel.text sizeWithFont:self.titleLabel.font].width; 來實(shí)現(xiàn)患亿,通過字符串計(jì)算出 titlelabel 的尺寸,來設(shè)置 titleEdgeInsets 缘薛,詳情見代碼段 1
  • 第三種通過 sizeToFit 和第二種思路是一樣的窍育,詳情見代碼段 2

代碼段 1

@interface NSString(UIStringDrawing)

// Single line, no wrapping. Truncation based on the NSLineBreakMode.
- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;

代碼段 2

UIButton *b = [[UIButton alloc] initWithFrame:CGRectMake(10, 100, 100, 40)];
b.layer.borderColor = [UIColor blueColor].CGColor;
b.layer.borderWidth = 1;
[b setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[b setTitle:@"測(cè)試" forState:UIControlStateNormal];
[b setImage:[UIImage imageNamed:@"arrow"] forState:UIControlStateNormal];
[self.view addSubview:b];

// 看這里 --- 
[b.titleLabel sizeToFit];

b.imageEdgeInsets = UIEdgeInsetsMake(0, b.titleLabel.frame.size.width, 0, -b.titleLabel.frame.size.width);
b.titleEdgeInsets = UIEdgeInsetsMake(0, -b.imageView.frame.size.width, 0, b.imageView.frame.size.width);

雖然位置不對(duì),但尺寸已經(jīng)是正確的了宴胧,也能實(shí)現(xiàn)合適的效果漱抓。

圖標(biāo)在下標(biāo)題在上

根據(jù)上面的原理,應(yīng)該可以比較簡(jiǎn)單的推算出的 imageEdgeInsetstitleEdgeInsets

要求: imageViewtitleLabel 都居中恕齐,且 imageVeiw 在上

兩者都存在的情況下乞娄,原始的左右間距是( UIButton 的寬度 - 兩者的寬度之和) * 0.5

imageViewX 要向右移動(dòng) ((UIButton 的寬度 - imageView 的寬度) - (UIButton 的寬度 - 兩者的寬度之和)) * 0.5

titleLable 的寬度 * 0.5

階段 1

    self.imageEdgeInsets = UIEdgeInsetsMake(0, self.titleLabel.frame.size.width * 0.5, 0, -self.titleLabel.frame.size.width * 0.5);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView.frame.size.width * 0.5, 0, self.imageView.frame.size.width * 0.5);

兩者都居中了显歧,然后調(diào)整垂直方向

階段 2

self.imageEdgeInsets = UIEdgeInsetsMake(-5, self.titleLabel.frame.size.width * 0.5, 5, -self.titleLabel.frame.size.width * 0.5);
self.titleEdgeInsets = UIEdgeInsetsMake(5, -self.imageView.frame.size.width * 0.5, -5, self.imageView.frame.size.width * 0.5);

效果有點(diǎn)丑...

說明: 這里忽略了 UIButton 放不下 titleLabel 或者用 xib 創(chuàng)建有 intrinsicSize 引用的問題仪或。

假想的實(shí)現(xiàn)

CGFloat imageX = (CGRectGetWidth(self.imageView.frame)+ CGRectGetWidth(self.titleLabel.frame)) * 0.5 + (self.imageEdgeInsets.left - self.imageEdgeInsets.right) / 2;
CGFloat imageY = (CGRectGetHeight(self.frame) - self.imageView.image.size.height) * 0.5 + (self.imageEdgeInsets.top - self.imageEdgeInsets.bottom) / 2;
CGFloat imageW = self.imageView.image.size.width;
CGFloat imageH = self.imageView.image.size.height;
    
CGFloat titleX = (CGRectGetWidth(self.titleLabel.frame) + CGRectGetWidth(self.titleLabel.frame) * 0.5) + (self.titleEdgeInsets.left - self.titleEdgeInsets.right) / 2;
CGFloat titleY = (CGRectGetWidth(self.frame) - CGRectGetHeight(self.titleLabel.frame)) * 0.5 + (self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) / 2;
CGFloat titleW = CGRectGetWidth(self.titleLabel.frame);
CGFloat titleH = CGRectGetHeight(self.titleLabel.frame);

總結(jié)

本文通過控制變量法?? 測(cè)試了 UIButtonimageEdgeInsetstitleEdgeInsets 屬性的作用效果,發(fā)現(xiàn)兩者是相互獨(dú)立且只參考父視圖 ( UIButton ) 士骤,同時(shí)對(duì)實(shí)現(xiàn)圖標(biāo)在右標(biāo)題在上范删,圖標(biāo)在上標(biāo)題在下這兩種樣式提供了一點(diǎn)思路。

參考

  1. UIButton的titleEdgeInsets屬性和imageEdgeInsets屬性實(shí)現(xiàn)圖片文字按要求排列
  2. Aligning text and image on UIButton with imageEdgeInsets and titleEdgeInsets
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拷肌,一起剝皮案震驚了整個(gè)濱河市到旦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巨缘,老刑警劉巖添忘,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異若锁,居然都是意外死亡搁骑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門又固,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仲器,“玉大人,你說我怎么就攤上這事仰冠÷χ埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵沪停,是天一觀的道長。 經(jīng)常有香客問我,道長木张,這世上最難降的妖魔是什么众辨? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮舷礼,結(jié)果婚禮上鹃彻,老公的妹妹穿的比我還像新娘。我一直安慰自己妻献,他們只是感情好蛛株,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著育拨,像睡著了一般谨履。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熬丧,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天笋粟,我揣著相機(jī)與錄音,去河邊找鬼析蝴。 笑死害捕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闷畸。 我是一名探鬼主播尝盼,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼佑菩!你這毒婦竟也來了盾沫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤倘待,失蹤者是張志新(化名)和其女友劉穎疮跑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凸舵,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祖娘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啊奄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渐苏。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菇夸,靈堂內(nèi)的尸體忽然破棺而出琼富,到底是詐尸還是另有隱情,我是刑警寧澤庄新,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布鞠眉,位于F島的核電站薯鼠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏械蹋。R本人自食惡果不足惜出皇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哗戈。 院中可真熱鬧郊艘,春花似錦、人聲如沸唯咬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胆胰。三九已至狞贱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煮剧,已是汗流浹背斥滤。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勉盅,地道東北人佑颇。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像草娜,于是被迫代替她去往敵國和親挑胸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容