前言
我們在日常開發中經常用到 UIButton
,有時候需要 icon 和文字一起展示,所以需要做一些定制,比如以下幾種情況:
- 圖片在上,文字在下;
- 圖片在下,文字在上;
- 圖片在左,文字在右;
- 圖片在右,文字在左;
其實就是改變 UIButton
內部的 imageView
和 titleLabel
的位置而已,默認的位置是:imageView
在左 titleLabel
在右且緊密相連,在水平方向: imageView
和 titleLabel
整體居中,在垂直方向:垂直居中,如下圖:
這種布局很多時候并不是我們想要的,可能我們還需要imageView
和 titleLabel
之間加點間距,再或者改變一下兩者的位置等等,這時候就用到了 UIButton
的 imageEdgeInsets
和 titleEdgeInsets
兩個屬性了,但是經過一番折騰,很多小伙伴還是不明白這里面的原理和套路,盡管網上有關這倆屬性的資料很多,都是教大家如何設置,但很多資料并沒有說清楚真正的本質,今天就給大家講講imageEdgeInsets
和 titleEdgeInsets
兩個屬性的本質,希望大家看完這篇文章能有所收獲。
關鍵知識點
-
UIEdgeInsetsMake
所對應的4個參數分別是上、左、下、右
UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
-
UIButton
的屬性titleEdgeInsets
和imageEdgeInsets
顧名思義其實就是titleLabel
和imageView
的內邊距,可以理解為向內壓縮,當然還少了一個前提:如果是正值則為向內壓縮,如果是負值,則為向外擴張。
(為了便于理解,大家也可以可參考 CSS 的padding
。) 看圖說話:
edgeInsets說明.png
控件的有效活動區域(名字自己起的):此區域就是
titleLabel或
imageView所能展示的區域,它們各自有各自的區域,是互不干擾的,這點要注意一下。如果區域容納不了真實的內容,就會被壓縮,具體如何壓縮后面會具體講解
默認情況下:titleEdgeInsets
和imageEdgeInsets
各個邊的值都是0,此時,黃色區域覆蓋藍色區域; UIControl
的屬性contentVerticalAlignment
和contentHorizontalAlignment
,這兩個屬性都是枚舉類型的,定義了content
在UIControl
內部是如何定位布局的,分為垂直方向和水平方向,此處的定位就是相對于上面??所說的控件的有效活動區域,即在有效活動區域內,控件的垂直方向和水平方向對齊方式。(此處還是要注意,這個對齊也是指每個控件在自己單獨的有效活動區域的對齊,并非是整體。舉個例子:titleLabel
在自己的有效活動區域內居中對齊,imageView
在自己的有效活動區域內居中對齊,兩個區域可能有交集,也可能無交集,互不干擾,并不是titleLabel
和imageView
整體在UIButton
內居中對齊)具體枚舉值如下:
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
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,
};
初始默認值:中心對齊(垂直方向居中,水平方向居中)
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
找出
titleLabel
或imageView
各自的初始有效活動區域,即button.imageEdgeInsets = UIEdgeInsetsZero
和button.titleEdgeInsets = UIEdgeInsetsZero
時它們各自的有效活動區域,為什么要找到它們的初始有效活動區域呢,因為我們設置的imageEdgeInsets
和titleEdgeInsets
都是針對初始有效活動區域做的操作。AutoLayout
的Content Hugging Priority
(抗拉伸優先級) 和Content Compression Resistance Priority
(抗壓縮優先級),從字面意思可以看出Content Hugging Priority
是內容擁抱優先級(Hugging:擁抱,抱緊),可以理解為內容與承載內容的視圖緊湊的抱緊,比如說label
里的文字內容是充滿整個label
的;Content Compression Resistance Priority
是內容壓縮阻力優先級。這兩個屬性的值都是越大,越抗拉伸和抗壓縮,屬于float
類型的,取值范圍是1--1000。不過還有4種系統類型的值如下:
typedef float UILayoutPriority NS_TYPED_EXTENSIBLE_ENUM;
// A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000;
// This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750;
// This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250;
// When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
獲取和設置
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
以上這些知識點是大家需要了解的,弄懂這些,對后面的操作會有很大的幫助。接下來我們研究 UIButton
的 imageEdgeInsets
和 titleEdgeInsets
是如何向內壓縮和向外擴展的。
如何找出titleLabel
和 imageView
各自的初始有效活動區域
提示:利用 UIControl
的屬性 contentVerticalAlignment
和 contentHorizontalAlignment
。
UIButton
繼承自 UIControl
,所以我們可以通過設置UIButton
的這兩個屬性,得到titleLabel
和 imageView
各自的初始有效活動區域,前面我們說過,這兩個屬性是針對它們各自的有效活動區域而言的,并不是UIButton
本身,所以我們在不改變imageEdgeInsets
和 titleEdgeInsets
時,分別設置它們在有效區域內左上對齊和右下對齊,就可以知道它們的有效區域范圍了,如果理解不了,可以單獨設置4次,分別是居上對齊、居左對齊、居下對齊、居右對齊,也可以得到相同的結果;還是數形結合更直觀一點,我請大家看圖,你們自備茶??吧!??????
通過上圖,大家有沒有發現,不管是什么對齊方式, imageView
的活動區域始終是在 {{0, 0}, {43, 40}} ({{x, y}, {width, height}})
這個區域內進行的,同時也是對齊方式為 Fill
模式下各自的 Frame
(由于精度問題,會有小于0.5的誤差在內,屬于正常情況,不影響結果)。而 titleLabel
的活動區域始終是在 {{30, 0}, {70, 40}} 這個區域內進行的,也就是 button
的寬度減去了 imageView
的寬度的范圍,但是在對齊方式為 Fill
模式下, titleLabel
的活動區域并非是上圖中綠色背景區域那些,真實值應該是再加上右邊與 button
的間距。
仔細的小伙伴們可能也發現了,imageView
和 titleLabel
的初始活動區域的上邊界和下邊界正好是 button
的上下邊界,但是左右邊界好像確定不下來啊,再看上面的圖,前兩個對齊方式:
左上對齊的時候, imageView
的左邊界是 button
左邊界, titleLabel
的左邊界是 imageView
的寬度;
右下對齊的時候,titleLabel
的右邊界是 button
右邊界, imageView
的右邊界是 button
的寬度減掉 titleLabel
的寬度;
小結(劃重點)
// 有效活動區域各邊界的初始位置
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
imageView :top:0 left:0 bottom:0 right:buttonWidth-labelWidth
titleLabel:top:0 left:imageWidth bottom:0 right:buttonWidth
下面我用圖給大家講解下找imageView
的右邊界和titleLabel
的左邊界,其它邊界上面已經說得很清楚了,這里就不再贅述了。
圖解:
以上圖應該可以讓大家完全明白了吧!
如何設置正確 imageEdgeInsets 和 titleEdgeInsets
上面我們已經找到了 imageView 和 titleLabel 有效活動區域的初始邊界,接下來我們就通過設置 imageEdgeInsets 和 titleEdgeInsets 來滿足我們的需求。
- 圖片在上,文字在下;
- 圖片在下,文字在上;
- 圖片在左,文字在右;(默認情況)
- 圖片在右,文字在左;
imageEdgeInsets
和 titleEdgeInsets
是 UIEdgeInsets
類型的,上面關鍵知識點里面已經說了,
UIEdgeInsetsMake
所對應的4個參數分別是上、左、下、右UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
imageEdgeInsets
和titleEdgeInsets
的各個參數值的正負決定是向內壓縮還是向外擴展(如果是正值則為向內壓縮,如果是負值,則為向外擴張。
)
就拿 imageEdgeInsets
舉例說明一下:
對于 imageEdgeInsets.top
- 為 0 時,
imageView
有效活動區域的上邊界不動; - 為正值時,
imageView
有效活動區域的上邊界向內收縮,即下移; - 為負值時,
imageView
有效活動區域的上邊界向外擴張,即上移;
對于 imageEdgeInsets.left
- 為 0 時,
imageView
有效活動區域的左邊界不動; - 為正值時,
imageView
有效活動區域的左邊界向內收縮,即右移; - 為負值時,
imageView
有效活動區域的左邊界向外擴張,即左移;
對于 imageEdgeInsets.bottom
- 為 0 時,
imageView
有效活動區域的下邊界不動; - 為正值時,
imageView
有效活動區域的下邊界向內收縮,即上移; - 為負值時,
imageView
有效活動區域的下邊界向外擴張,即下移;
對于 imageEdgeInsets.right
- 為 0 時,
imageView
有效活動區域的右邊界不動; - 為正值時,
imageView
有效活動區域的右邊界向內收縮,即左移; - 為負值時,
imageView
有效活動區域的右邊界向外擴張,即右移;
titleEdgeInsets
同理
舉例說明
例1:圖片在左,文字在右,且間距為6;
分析:默認情況就是圖片在左,文字在右,所以我們只需要加間距就行,在對齊方式(水平和垂直方向)都為居中對齊時,我們只需要讓 imageView
有效活動區域保持大小不變,整體往左移間距的二分之一,也就是3, titleLabel
有效活動區域保持大小不變,整體往右移間距的二分之一即可實現。
// imageView 與 titleLabel 的間距
CGFloat spacing = 6.f;
button.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing/2, 0, spacing/2);
button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing/2, 0, -spacing/2);
這個圖片是用電腦在模擬器上直接截的圖,截下來是正常居中的,但是傳到這里,貌似看起來 imageView
的頂部間距比下面底部間距大一點點似的,不過大家也可以自己跑個 demo 看看,數據都是沒問題的。可以直接通過 Xcode 的 Debug View Hierarchy
功能看視圖層次結構和它們的 frame。
問題:上面的方案雖然實現了我們的需求,但是你們有沒有發現,對 imageView
和 titleLabel
的有效活動區域保持大小不變,整體進行了左移和右移,導致了它們各自的有效活動區域跑出了 button
的外面,可以先后設置水平方向居左和居右對齊,看看會出現什么效果。
盡管實際使用的時候不會這么操作,但是對于追求完美的人來說實在是不能忍啊!
思考:除了上面的方案,還有沒有其他方案?
方案一:只左移 imageView
有效活動區域的右邊界,和右移 titleLabel
有效活動區域的左邊界,間距多少就移多少(在水平對齊方式為居中對齊的時候, imageView
有效活動區域的右邊界左移 spacing , imageView
就會整體左移 spacing/2,同理,titleLabel
會右移 spacing/2,整體而言間距就是 spacing)。
// imageView 與 titleLabel 的間距
CGFloat spacing = 6.f;
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
此方案在水平對齊方式為居左或居右的時候 imageView
和 titleLabel
不會超出 button
的邊界。
方案二:既然我們知道了各自初始的有效活動區域,通過加間距,我們也可以算出來最終 imageView
和 titleLabel
的位置,那我們直接把 imageView
和 titleLabel
各自有效活動區域壓縮或擴展到與最終 imageView
和 titleLabel
的位置(即frame)一致不就行了,針對各自有效活動區域的每條邊界做移動操作就可滿足,聽起來是要做很多的計算啊,不過此方案有一個好處就是,最終結果不會受 button
的對齊屬性 contentHorizontalAlignment
和 contentVerticalAlignment
影響,因為有效活動區域與它們各自本身的 boundSize 一致了,不論居左,居右,居上,居下,居中還是填充, imageView
和 titleLabel
的位置都只有一種情況。
CGFloat buttonHeight = button.bounds.size.height;
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageHeight = CGRectGetHeight(button.imageView.frame);
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
CGFloat labelHeight = CGRectGetHeight(button.titleLabel.frame);
// imageView 與 titleLabel 的間距
CGFloat spacing = 6.f;
// 垂直居中對齊時,imageView 頂部和底部距離 button 的間距
// 也是 imageView 有效活動區域上邊界下移的距離和下邊界上移的距離
CGFloat imageSpaceVertical = (buttonHeight-imageHeight) * 0.5f;
// 垂直居中對齊時,titleLabel 頂部和底部距離 button 的間距
// 也是 titleLabel 有效活動區域上邊界下移的距離和下邊界上移的距離
CGFloat labelSpaceVertical = (buttonHeight-labelHeight) * 0.5f;
// 水平居中對齊時,imageView 和 titleLabel 加間距后,各自距離 button 的間距(兩者是等間距的)
// 也是 imageView 有效活動區域左邊界右移的距離 titleLabel 有效活動區域右邊界左移的距離
CGFloat spaceHorizontal = (buttonWidth-imageWidth-labelWidth-spacing) * 0.5f;
// imageView 最終的 frame
CGRect imageViewFrame = CGRectMake(spaceHorizontal, imageSpaceVertical, imageWidth, imageHeight);
CGFloat titleLabel_X = CGRectGetMaxX(imageViewFrame) + spacing;
// titleLabel 最終的 frame
CGRect titleLabelFrame = CGRectMake(titleLabel_X, labelSpaceVertical, labelWidth, labelHeight);
// imageView 有效活動區域右邊界左移的距離
CGFloat imageViewRightEdgeLeftSpace = (buttonWidth-labelWidth) - CGRectGetMaxX(imageViewFrame);
// titleLabel 有效活動區域左邊界右移的距離
CGFloat titleLabelLeftEdgeRightSpace = titleLabel_X - imageWidth;
button.imageEdgeInsets = UIEdgeInsetsMake(imageSpaceVertical, spaceHorizontal, imageSpaceVertical, imageViewRightEdgeLeftSpace);
button.titleEdgeInsets = UIEdgeInsetsMake(labelSpaceVertical, titleLabelLeftEdgeRightSpace, labelSpaceVertical, spaceHorizontal);
// 對齊方式:左上
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
// 對齊方式:右下
//button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentBottom;
// 對齊方式:填充
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
//button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
雖然看起來計算的偏移量有點多,但是如果理解了這個方案的邏輯,那么今天講的本質也就理解了,此方案就當一個測試案例吧,以上這幾種方案都是在水平和垂直居中對齊方式下進行計算的,大家也可以自己動手練習一下其它方式,更有助于理解。
例2:圖片在上,文字在下,緊密相連,不加間距;
分析:上圖下字,這種類似于疊羅漢,此時在水平方向,只有 imageView
和 titleLabel
,所以為了簡化計算,水平方向的對齊方式采用居中,設置各自水平方向的有效活動區域為整個button
的寬度即可,至于垂直方向,則只需計算出最終的 imageView
和 titleLabel
的 frame.origin.y
值即可。
// 垂直居中對齊時,imageView 和 titleLabel ,各自距離 button 的間距(兩者是等間距的)
// 也是 imageView 有效活動區域上邊界下移的距離 titleLabel 有效活動區域下邊界上移的距離
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最終的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最終的 frame.origin.y
// 也是 titleLabel 有效活動區域上邊界下移的距離
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活動區域下邊界上移的距離
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// imageView 有效活動區域右邊界右移的距離
CGFloat imageRightEdgeRightSpace = labelWidth;
// titleLabel 有效活動區域下邊界上移的距離
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
// titleLabel 有效活動區域左邊界左移的距離
CGFloat labelLeftEdgeLeftSpace = imageWidth;
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, 0, imageBottomEdgeTopSpace, -imageRightEdgeRightSpace);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -labelLeftEdgeLeftSpace, labelBottomEdgeTopSpace, 0);
// 對齊方式:水平方向為居中,垂直方向任意都行,不影響結果
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
//button.contentVerticalAlignment = UIControlContentVerticalAlignmentBottom;
//button.contentVerticalAlignment = UIControlContentHorizontalAlignmentFill;
此方案是固定住了 imageView
和 titleLabel
有效活動區域的上邊界和下邊界,也就是上邊界和它們各自最終的 frame.origin.y
一致,下邊界和 (frame.origin.y + frame.size.height)
,所以垂直方向的對齊方式不會影響到它們的位置,水平方向采用將有效活動區域放大至 button
的寬度,利用了水平居中對齊。
圖片在上,文字在下,很明顯,這種布局當前 button
的高度容納不下,相應的我們增加其高度即可。
思考:大家有沒有發現一個問題,我們以上所講的情況都是 button
的寬度大于 imageView
和 titleLabel
的之和的,也就是說,button
始終包含著 imageView
和 titleLabel
,大家有沒有想過 button
的寬度小于 imageView
和 titleLabel
之和的時候還會按我們的預期偏移嗎?
填坑
上面讓大家思考的問題其實是個坑,當button
的寬度小于 imageView
和 titleLabel
之和的時候,此時 titleLabel
會被壓縮,也就是文字顯示不全,甚至直接顯示“...”。如圖:
此時, button
的寬高都為60,無法完全顯示兩者,默認優先壓縮了 titleLabel
。接下來看看為什么會壓縮 titleLabel
而不是 imageView
。
在上面的關鍵知識點里的第5條說到了兩個優先級, Content Hugging Priority
(抗拉伸優先級) 和 Content Compression Resistance Priority
(抗壓縮優先級)正式因為 titleLabel
的 Content Compression Resistance Priority
(抗壓縮優先級)小于 imageView
的,所以才會在寬度不夠的時候會優先壓縮 titleLabel
。我們來看看它們的默認值各是多少,還是通過 Xcode 的 Debug View Hierarchy
功能。
代碼獲取:
// imageView 水平方向的抗壓縮阻力
UILayoutPriority layoutPriority_image = [button.imageView contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
// titleLabel 水平方向的抗壓縮阻力
UILayoutPriority layoutPriority_label = [button.titleLabel contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
上圖可以看出:
imageView 的
Content Hugging Priority
:
Horizontal:250(UILayoutPriorityDefaultLow)
Vertical:250(UILayoutPriorityDefaultLow)imageView 的
Content Compression Resistance Priority
:
Horizontal:750(UILayoutPriorityDefaultHigh)
Vertical:750(UILayoutPriorityDefaultHigh)titleLabel 的
Content Hugging Priority
:
Horizontal:250(UILayoutPriorityDefaultLow)
Vertical:250(UILayoutPriorityDefaultLow)titleLabel 的
Content Compression Resistance Priority
:
Horizontal:749.5
Vertical:749.5
titleLabel 的抗壓縮阻力小于 imageView 的抗壓縮阻力,所以是壓縮了 titleLabel 。
也許有小伙伴在想,如果把 titleLabel 的抗壓縮阻力設置為大于 imageView 的抗壓縮阻力,那會不會就會優先顯示 titleLabel 而壓縮 imageView 呢,那不好意思,讓你失望了,并不會出現你想要的結果。可能這是為了保護 imageView 不會被壓縮變形吧,當然這也只是我的猜測。??????
接下來我們把上面提到的問題做了吧,請看題。
例:當 button 的寬度小于 imageView 和 titleLabel 寬度之和時,使圖片在上,文字在下,不加間距。
分析:既然是疊羅漢放置,那就還是按水平居中的對齊方式,放大它們的有效活動區域至 button 的寬度,讓它們自動居中對齊就行,我們只需計算上下邊界偏移量就行。
// 垂直居中對齊時,imageView 和 titleLabel ,各自距離 button 的間距(兩者是等間距的)
// 也是 imageView 有效活動區域上邊界下移的距離 titleLabel 有效活動區域下邊界上移的距離
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最終的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最終的 frame.origin.y
// 也是 titleLabel 有效活動區域上邊界下移的距離
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活動區域下邊界上移的距離
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// titleLabel 有效活動區域下邊界上移的距離
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, 0, imageBottomEdgeTopSpace, -labelWidth);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -imageWidth, labelBottomEdgeTopSpace, 0);
What? 這是什么鬼??,imageView 為啥沒居中啊?怎么讓和我們想的不一樣呢? 結果卻讓人意想不到啊!
通過多次測試,我發現當 imageView 有效活動區域的右邊界恰好右移到接近 button 右邊界處的某個位置時,imageView 有效活動區域的右邊界會被壓縮至 button 寬度的一半,不知道這是什么機制,這種問題之前沒出現,就是因為 imageView 的寬度與 button 的寬度正好發生了什么不可告人的秘密秘?,也可能是精度丟失的問題導致的吧,暫且就這么安慰自己吧,哈哈...
那既然水平居中對于 imageView 是不安全的,那我們就采取絕對定位(也就是讓 imageView 的有效活動區域 frame 與它最終的 frame 一致),只不過就是需要多做點計算的事兒。
// 垂直居中對齊時,imageView 和 titleLabel ,各自距離 button 的間距(兩者是等間距的)
// 也是 imageView 有效活動區域上邊界下移的距離 titleLabel 有效活動區域下邊界上移的距離
CGFloat spaceVertical = (buttonHeight-imageHeight-labelHeight) * 0.5f;
// imageView 最終的 frame.origin.y
CGFloat imageView_Y = spaceVertical;
// titleLabel 最終的 frame.origin.y
// 也是 titleLabel 有效活動區域上邊界下移的距離
CGFloat titleLabel_Y = spaceVertical + imageHeight;
// imageView 有效活動區域下邊界上移的距離
CGFloat imageBottomEdgeTopSpace = buttonHeight-titleLabel_Y;
// imageView 有效活動區域左邊界右移的距離
CGFloat imageLeftEdgeRightSpace = (buttonWidth-imageWidth) * 0.5f;
// imageView 有效活動區域右邊界移動的距離(此處不確定是左移還是右移,因為 titleLabel 被壓縮后寬度未知)
// 所以還是通過算術表達式計算,正值就是左移,負值就是右移
CGFloat imageRightEdgeLeftSpace = (buttonWidth-labelWidth) - (buttonWidth-imageLeftEdgeRightSpace);
// 簡化一下
imageRightEdgeLeftSpace = imageLeftEdgeRightSpace-labelWidth;
// titleLabel 有效活動區域下邊界上移的距離
CGFloat labelBottomEdgeTopSpace = buttonHeight-(titleLabel_Y+labelHeight);
button.imageEdgeInsets = UIEdgeInsetsMake(imageView_Y, imageLeftEdgeRightSpace, imageBottomEdgeTopSpace, imageLeftEdgeRightSpace-labelWidth);
button.titleEdgeInsets = UIEdgeInsetsMake(titleLabel_Y, -imageWidth, labelBottomEdgeTopSpace, 0);
以上方案是把 imageView 的有效活動區域給絕對定位了,titleLabel 的有效活動區域是放大,采用水平居中對齊的方式,自動居中對齊的。大家可以試試 button 寬度小的時候,兩者的有效活動區域都采用絕對定位的方案,看看會有什么意外的收獲。??????答案是盡管居中了,但是 titleLabel 還是被壓縮了,沒有顯示出文字。此處給大家提示一下,在計算 titleLabel 有效活動區域左右邊界移動的時候,切忌,不要用 titleLabel 的寬度(即CGRectGetWidth(button.titleLabel.frame)
)去做計算,因為此時 titleLabel 的寬度是被壓縮了的,與真實的 titleString 寬度不符,是小于 titleString 寬度的,所以 titleLabel 最終移動了左右邊界后還是之前的那么寬,固然無法完全顯示文字。此時做計算的時候一定要去計算 titleLabel.text
的文字寬度。但是 imageView 有效活動區域右邊界移動的計算還是使用 titleLabel 的寬度(即CGRectGetWidth(button.titleLabel.frame)
)去計算。自己試著理解一下,這里可能有點繞,自己最好動手練習一下,就明白了!
NSDictionary *dic = @{NSFontAttributeName:button.titleLabel.font};
CGFloat stringWidth = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, labelHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:dic
context:nil].size.width;
總結
說了這么多,最后也該做個大總結了,主要說下以下幾個重點以及需要注意的地方吧。
- imageView 和 titleLabel 有效活動區域的初始位置,所有的偏移都是在這個基礎上進行的;
// 有效活動區域各邊界的初始位置
CGFloat buttonWidth = button.bounds.size.width;
CGFloat imageWidth = CGRectGetWidth(button.imageView.frame);
CGFloat labelWidth = CGRectGetWidth(button.titleLabel.frame);
imageView :top:0 left:0 bottom:0 right:buttonWidth-labelWidth
titleLabel:top:0 left:imageWidth bottom:0 right:buttonWidth
-
button.contentHorizontalAlignment
和button.contentVerticalAlignment
兩個方向的對齊方式都是針對 imageView 和 titleLabel 在各自的有效區域內而言的,且互不影響,但是在填充模式下,即
// 對齊方式:填充
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
imageView 會拉伸充滿自己的有效活動區域,titleLabel 只會在垂直方向充滿自己的有效活動區域,水平方向是與自己的有效活動區域左對齊的,不會充滿整個 titleLabel 的有效活動區域。
當 button 的寬度不夠容納 imageView 和 titleLabel 時, imageView 不合適用水平方向擴大有效區域的右邊界至 button 的右邊界,然后居中對齊的方式,不安全,但是 titleLabel 不會有問題,可采用此方式。
當 button 的寬度不夠容納 imageView 和 titleLabel 時, titleLabel 有效活動區域如果采用絕對定位的話,切記分清 labelWidth 和 stringWidth 的區別,計算 imageView 有效活動區域右邊界移動時用 labelWidth ,計算 titleLabel 有效活動區域的左右邊界移動時用 stringWidth。
當 button 的寬度不夠容納 imageView 和 titleLabel 時, 始終優先壓縮 titleLabel。
設置
button.imageEdgeInsets
和button.titleEdgeInsets
時,button.contentHorizontalAlignment
和button.contentVerticalAlignment
有多種對齊方式可選,自己喜歡哪一種就選哪一種對齊方式,當然還是計算量少的最好,哈哈...以上講的所有邊界偏移量的計算有一個很重要的前提就是
button.contentEdgeInsets
為默認值UIEdgeInsetsZero
,否則在做偏移量計算的時候需要加上button.contentEdgeInsets
各邊界的值,當然一般很少會去設置button.contentEdgeInsets
。
今天的分享就到此結束??,希望大家能有所收獲,祝各位帥哥靚女周末愉快!!!