iOS button的imageEdgeInsets和titleEdgeInsets原理

demo地址: SPButton

前言

最近我竟花了幾天的時間去深入研究button,研究的過程當中,被imageEdgeInsetstitleEdgeInsets兩個屬性困惑甚久,我為此徹夜不眠,網上也查閱各種資料,可以說,對于這兩個屬性的解釋,網上的答案滿天飛,但是,沒有一個人真正說出了它們的原理。

重要關聯屬性contentHorizontalAlignment和contentVerticalAlignment

這是兩個枚舉,即整個內容的水平對齊方式和垂直對齊方式

typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) {
    UIControlContentHorizontalAlignmentCenter = 0,
    UIControlContentHorizontalAlignmentLeft   = 1,
    UIControlContentHorizontalAlignmentRight  = 2,
    UIControlContentHorizontalAlignmentFill   = 3,
    UIControlContentHorizontalAlignmentLeading  API_AVAILABLE(ios(11.0), tvos(11.0)) = 4,
    UIControlContentHorizontalAlignmentTrailing API_AVAILABLE(ios(11.0), tvos(11.0)) = 5,
};

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter  = 0,
    UIControlContentVerticalAlignmentTop     = 1,
    UIControlContentVerticalAlignmentBottom  = 2,
    UIControlContentVerticalAlignmentFill    = 3,
};
// 默認:
 button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
 button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

其中UIControlContentHorizontalAlignmentLeading和UIControlContentHorizontalAlignmentTrailing為iOS11新增,在我們大中華地區,Leading就是Left,Trailing就是Right ,對于部分國家,他們的語言是從右往左寫,這時Leading就是Right,Trailing就Left

正文

創建一個按鈕,設置文字和圖片,按鈕的內容默認排布如圖:為了便于理解,我給的titleLabel和imageView是等寬的


9EC15FFC4CC9871442AD43C376C72DF8.jpg

截圖中:

  1. 黑色邊框為按鈕矩形區域,其bounds為:(0,0,200,100),為了便于研究,contentEdgeInset默認UIEdgeInsetsZero,即按鈕的內容區域就是按鈕的bounds;
  2. imageView的frame為(50,25,50,50)
  3. titleLabel的frame為(100,37.5,50,50)

現在,我設置

button.imageEdgeInsets = UIEdgeInsetsMake(0,50, 0,0);

經過上面的設置后,請大家猜想一下,圖片的位置會在什么地方?
思考 1s、2s、3s、.......
大家心中差不多有想法了,圖片的原x值為50,現在設置UIEdgeInsetsMake(0,50, 0,0),相當于整個圖片向右平移50,那么現在圖片的x值應該為100,大家想象的結果是不是這樣的,如圖:

2F4D2190781A35ADAC9188C5FC48F8CD.jpg

我要告訴大家,上面的結果是錯的,正確的結果如圖:
179A2A60E6C0637971FEB28BF5E1F50D.jpg

實際上,圖片只向右平移了50的一半,即25,這是為什么?

網上錯誤結論:

對于imageView:其imageEdgeInsets的top,left,bottom是相對button的contentRect而言,right是相對titleLabel而言;
對于titleLabel:其titleEdgeInsets的top,right,bottom是相對button的contentRect而言,left是相對imageView而言。

正確結論

imageEdgeInsetstitleEdgeInsets的top,left,right, bottom都是相對button的contentRect而言,當contentEdgeInsets為UIEdgeInsetsZero時,button、imageView、titleLabel的安全區域均為button的bounds。

根據這個正確結論,當設置了button.imageEdgeInsets = UIEdgeInsetsMake(0,50, 0,0)時,那么imageView的安全區域就是如下圖中的紅色區域

669CA397468CDFA3318832E6E46F654D.jpg

圖片的區域我們知道了,根據水平排列方式默認為UIControlContentHorizontalAlignmentCenter,圖片應當在紅色區域的中間位置,然而,我們要深刻明白:

重要的話說3遍

  • UIControlContentHorizontalAlignmentCenter的指的是內容(圖片+文字)整體居中
  • UIControlContentHorizontalAlignmentCenter的指的是內容(圖片+文字)整體居中
  • UIControlContentHorizontalAlignmentCenter的指的是內容(圖片+文字)整體居中
    其余枚舉值同理

因此,盡管titleLabel沒有設置titleEdgeInsets,但是我們在對imageView進行某種對齊時,不應該只考慮imageView,應該將imageView+titleLabel這個整體作為考慮對象; 如圖
F2C3BD086D79D226158CE915C5349A99.jpg

核心解釋

上圖中,imageView和藍色的titleLabel作為一個整體,在紅色區域內居中了,綠色的titleLabel只參與計算,由于我們沒有設置titleLabel的titleEdgeInsets,所以最終titleLabel的位置依然保持不變。藍色的titleLabel實際上是虛擬的,我只是告訴大家,系統進行對齊方式計算時,永遠是把imageView+titleLabel這個整體作為計算對象,我們來計算一下,圖片向右偏移25是怎么來的:
①紅色區域的寬度為:200 - 50 = 150;
②圖片+藍色label的總寬度:50 + 50 = 100;
③圖片的x值:(① - ②) / 2.0 =(150 - 100)/ 2.0 = 25;(除以2是因為居中對齊,如果是其余對齊就不用除以2)

我不知道我上面的表達夠不夠清楚,如果不清楚,那么我們來一次強化訓練

強化訓練

我們不再按照水平中心對齊,我們來一次左對齊

button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;

設置后如圖


280C608ED3BFFE70D6865E80E99AAB6D.jpg

再設置

 button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 50);

大家想想經過上面那行代碼之后,結果是什么呢?圖片會向左偏移50的距離嗎?如果按照網上的結論,圖片的right是相對titleLabel而言,那么設置right為50圖片必會向左偏移50。我要告訴大家,上面那行代碼設置之后,不會產生任何變化,為什么?

原因很簡單:上面那行代碼的意思是,圖片的安全區域為:在contentRect的基礎上,原區域右邊往左內縮50距離,即下圖中的紅色區域:
F3B155F5C3B22687C4C5AB809E993FC5.jpg

在這個紅色區域當中,將imageView+(虛擬)titleLabel這個整體進行左對齊,大家明顯能看到,現在就是左對齊的,所以設置right為50是不會有任何變化的,那么如果我們修改一下,設置
 button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 175);

上面那行代碼的意思是,圖片的安全區域為:在contentRect的基礎上,原區域右邊往左內縮175距離,即下圖中的紅色區域:
F6C4759589AC0ECDC7D607881F3B5A6E.jpg

在這個紅色區域內,要把imageView+(虛擬)titleLabel這個整體進行左對齊,但是我們發現,紅色區域的寬度容不下imageView+titleLabel這個整體,這個時候,系統先會把titleLabel的寬度壓縮,如果壓縮為0之后,發現連imageView都容不下,那么繼續壓縮imageView,直到寬度降為紅色區域寬為止,titleLabel保持不動, 最終顯示結果如圖
F3B3E996C3F16FB079910B6F7E635DFB.jpg

再次訓練

保持默認設置

button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

再設置

button.imageEdgeInsets = UIEdgeInsetsMake(50, 0, 0, 0);

*上面那行代碼的意思是,圖片的安全區域為:在contentRect的基礎上,原區域頂部向下內縮50距離,即下圖中的紅色區域:
F0893D4F8F8B780241E97EF8C9C8F541.jpg

在這個紅色區域當中,要依然保證imageView+(虛擬)titleLabel這個整體進行垂直居中, 因此最終結果如圖:
2102CCCB35A62B6562F0E927EB97BAA7.jpg

從這里我們可以萌生一個思想

imageEdgeInsetstitleEdgeInsets不要去理解為將imageView和titleLabel進行平移,應該理解為將imageView和titleLabel的安全區域的各邊進行偏移,偏移完成后,再聯合contentHorizontalAlignmentcontentVerticalAlignment屬性進行整體對齊

我所知道的秘密

我想大家在實現按鈕圖片位置在上、下、左、右的需求時,有不少人是通過重寫按鈕的imageRectForContentRect:titleRectForContentRect:的,我個人也很推薦這種做法,重寫layoutSubviews也可以,但我并不推薦,可以說重寫layoutSubviews可以實現你的需求,但是嚴重破壞了系統按鈕,因為,系統按鈕在layoutSubviews里面,當存在文字或者圖片時,會先調用imageRectForContentRect:titleRectForContentRect:這2個方法計算出imageRect和titleRect,然后將計算結果應用在imageView和titleLabel上,所以,如果你重寫layoutSubviews,先super , 然后進行一系列自己的布局,這就會導致你使用button時,通過imageRectForContentRect:titleRectForContentRect:這2個方法獲取到的rect并非你在layoutSubviews里計算的結果,仍然是系統計算的結果,這就是破壞了原始按鈕的方法

  • imageRectForContentRect:titleRectForContentRect:的調用時機:
  1. 在第一次調用titleLabel和imageView的getter方法(懶加載)時,alloc init之前會調用一次(無論有無圖片文字都會直接調),因此,在重寫這2個方法時,在方法里面不要使用self.imageView和self.titleLabel,因為這2個控件是懶加載,如果在重寫的這2個方法里是第一調用imageView和titleLabel的getter方法, 則會造成死循環
  2. 在layoutsSubviews中如果文字或圖片不為空時會調用, 測試方式:在重寫的這兩個方法里調用setNeedsLayout(layutSubviews),發現會造成死循環
  3. 按鈕的frame發生改變,設置文字圖片、改動文字和圖片、設置對齊方式,設置內容區域等時會調用,其實這些,系統是調用了layoutSubviews從而間接的去調用imageRectForContentRect:titleRectForContentRect:
    ......

建議

大家在實現按鈕的圖片在上、左、下、右的時候,最好要注意不要去破壞系統按鈕,什么叫破壞呢?比如你實現完之后,要保證按鈕的所有自帶屬性和方法依然生效,再比如:UIButton中的titleLabel和imageView是懶加載的,我們不要在實現自己需求的過程中去提前加載,這不符合按鈕的規則

demo地址: SPButton

demo效果圖

F728B222E090608891172DB207F7EF45.jpg

測試gif圖

演示圖.gif
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容