demo地址: SPButton
前言
最近我竟花了幾天的時間去深入研究button哈误,研究的過程當中隐轩,被imageEdgeInsets
和titleEdgeInsets
兩個屬性困惑甚久的烁,我為此徹夜不眠,網(wǎng)上也查閱各種資料辩尊,可以說只锻,對于這兩個屬性的解釋,網(wǎng)上的答案滿天飛钳垮,但是惑淳,沒有一個人真正說出了它們的原理。
重要關(guān)聯(lián)屬性contentHorizontalAlignment和contentVerticalAlignment
這是兩個枚舉饺窿,即整個內(nèi)容的水平對齊方式和垂直對齊方式
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新增歧焦,在我們大中華地區(qū),Leading就是Left肚医,Trailing就是Right 绢馍,對于部分國家,他們的語言是從右往左寫肠套,這時Leading就是Right舰涌,Trailing就Left
正文
創(chuàng)建一個按鈕,設(shè)置文字和圖片你稚,按鈕的內(nèi)容默認排布如圖:為了便于理解瓷耙,我給的titleLabel和imageView是等寬的
截圖中:
- 黑色邊框為
按鈕
矩形區(qū)域,其bounds為:(0刁赖,0搁痛,200,100)
宇弛,為了便于研究鸡典,contentEdgeInset默認UIEdgeInsetsZero,即按鈕的內(nèi)容區(qū)域就是按鈕的bounds枪芒; -
imageView
的frame為(50彻况,25谁尸,50,50)
纽甘; -
titleLabel
的frame為(100良蛮,37.5,50悍赢,50)
背镇;
現(xiàn)在,我設(shè)置
button.imageEdgeInsets = UIEdgeInsetsMake(0泽裳,50, 0破婆,0);
經(jīng)過上面的設(shè)置后涮总,請大家猜想一下,圖片的位置會在什么地方祷舀?
思考 1s瀑梗、2s、3s裳扯、.......
大家心中差不多有想法了抛丽,圖片的原x值為50,現(xiàn)在設(shè)置UIEdgeInsetsMake(0饰豺,50亿鲜, 0,0)冤吨,相當于整個圖片向右平移50蒿柳,那么現(xiàn)在圖片的x值應該為100,大家想象的結(jié)果是不是這樣的漩蟆,如圖:
我要告訴大家垒探,上面的結(jié)果是錯的,
正確的結(jié)果
如圖:實際上怠李,圖片只向右平移了50的一半圾叼,即25,這是為什么捺癞?
網(wǎng)上錯誤結(jié)論:
對于imageView:其
imageEdgeInsets
的top夷蚊,left,bottom是相對button的contentRect
而言翘簇,right是相對titleLabel而言撬码;
對于titleLabel:其titleEdgeInsets
的top,right版保,bottom是相對button的contentRect
而言呜笑,left是相對imageView而言夫否。
正確結(jié)論
imageEdgeInsets
和titleEdgeInsets
的top,left叫胁,right凰慈, bottom都是相對button的contentRect
而言,當contentEdgeInsets為UIEdgeInsetsZero時驼鹅,button微谓、imageView、titleLabel的安全區(qū)域均為button的bounds输钩。
根據(jù)這個正確結(jié)論瞧挤,當設(shè)置了button.imageEdgeInsets = UIEdgeInsetsMake(0沙热,50, 0,0)
時衡奥,那么imageView的安全區(qū)域就是如下圖中的紅色區(qū)域
圖片的區(qū)域我們知道了你弦,根據(jù)水平排列方式默認為UIControlContentHorizontalAlignmentCenter
待牵,圖片應當在紅色區(qū)域的中間位置吃型,然而,我們要深刻明白:
因此功戚,盡管titleLabel沒有設(shè)置titleEdgeInsets娶眷,但是我們在對imageView進行某種對齊時,不應該只考慮imageView啸臀,應該將imageView+titleLabel這個整體作為考慮對象; 如圖重要的話說3遍
- UIControlContentHorizontalAlignmentCenter的指的是內(nèi)容(圖片+文字)整體居中
- UIControlContentHorizontalAlignmentCenter的指的是內(nèi)容(圖片+文字)整體居中
- UIControlContentHorizontalAlignmentCenter的指的是內(nèi)容(圖片+文字)整體居中
其余枚舉值同理
核心解釋
上圖中届宠,imageView和藍色的titleLabel作為一個整體,在紅色區(qū)域內(nèi)居中了壳咕,綠色的titleLabel只參與計算席揽,由于我們沒有設(shè)置titleLabel的titleEdgeInsets,所以最終titleLabel的位置依然保持不變谓厘。藍色的titleLabel實際上是虛擬的幌羞,我只是告訴大家,系統(tǒng)進行對齊方式計算時竟稳,永遠是把imageView+titleLabel這個整體作為計算對象属桦,我們來計算一下,圖片向右偏移25是怎么來的:
①紅色區(qū)域的寬度為:200 - 50 = 150他爸;
②圖片+藍色label的總寬度:50 + 50 = 100聂宾;
③圖片的x值:(① - ②) / 2.0 =(150 - 100)/ 2.0 = 25;(除以2是因為居中對齊诊笤,如果是其余對齊就不用除以2)
我不知道我上面的表達夠不夠清楚系谐,如果不清楚,那么我們來一次強化訓練
強化訓練
我們不再按照水平中心對齊,我們來一次左對齊
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
設(shè)置后如圖
再設(shè)置
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 50);
大家想想經(jīng)過上面那行代碼之后纪他,結(jié)果是什么呢鄙煤?圖片會向左偏移50的距離嗎?如果按照網(wǎng)上的結(jié)論茶袒,圖片的right是相對titleLabel而言梯刚,那么設(shè)置right為50圖片必會向左偏移50。我要告訴大家薪寓,上面那行代碼設(shè)置之后亡资,不會產(chǎn)生任何變化,為什么向叉?
在這個紅色區(qū)域當中旷太,將imageView+(虛擬)titleLabel這個整體進行左對齊,大家明顯能看到销睁,現(xiàn)在就是左對齊的,所以設(shè)置right為50是不會有任何變化的存崖,那么如果我們修改一下冻记,設(shè)置
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 175);
上面那行代碼的意思是,圖片的安全區(qū)域為:在contentRect的基礎(chǔ)上来惧,原區(qū)域右邊往左內(nèi)縮175距離冗栗,即下圖中的紅色區(qū)域:在這個紅色區(qū)域內(nèi),要把imageView+(虛擬)titleLabel這個整體進行左對齊供搀,但是我們發(fā)現(xiàn)隅居,紅色區(qū)域的寬度容不下imageView+titleLabel這個整體,這個時候葛虐,系統(tǒng)先會把titleLabel的寬度壓縮胎源,如果壓縮為0之后,發(fā)現(xiàn)連imageView都容不下屿脐,那么繼續(xù)壓縮imageView涕蚤,直到寬度降為紅色區(qū)域?qū)挒橹梗瑃itleLabel保持不動的诵, 最終顯示結(jié)果如圖
再次訓練
保持默認設(shè)置
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
再設(shè)置
button.imageEdgeInsets = UIEdgeInsetsMake(50, 0, 0, 0);
*上面那行代碼的意思是万栅,圖片的安全區(qū)域為:在contentRect的基礎(chǔ)上,原區(qū)域頂部向下內(nèi)縮50距離西疤,即下圖中的紅色區(qū)域:在這個紅色區(qū)域當中烦粒,要依然保證imageView+(虛擬)titleLabel這個整體進行垂直居中, 因此最終結(jié)果如圖:
從這里我們可以萌生一個思想
imageEdgeInsets
和titleEdgeInsets
不要去理解為將imageView和titleLabel進行平移代赁,應該理解為將imageView和titleLabel的安全區(qū)域的各邊進行偏移扰她,偏移完成后兽掰,再聯(lián)合contentHorizontalAlignment
和contentVerticalAlignment
屬性進行整體對齊
我所知道的秘密
我想大家在實現(xiàn)按鈕圖片位置在上、下义黎、左禾进、右的需求時,有不少人是通過重寫按鈕的imageRectForContentRect:
和titleRectForContentRect:
的廉涕,我個人也很推薦這種做法泻云,重寫layoutSubviews
也可以,但我并不推薦狐蜕,可以說重寫layoutSubviews可以實現(xiàn)你的需求宠纯,但是嚴重破壞了系統(tǒng)按鈕,因為层释,系統(tǒng)按鈕在layoutSubviews里面婆瓜,當存在文字或者圖片時,會先調(diào)用imageRectForContentRect:
和titleRectForContentRect:
這2個方法計算出imageRect和titleRect贡羔,然后將計算結(jié)果應用在imageView和titleLabel上廉白,所以,如果你重寫layoutSubviews乖寒,先super , 然后進行一系列自己的布局猴蹂,這就會導致你使用button時,通過imageRectForContentRect:
和titleRectForContentRect:
這2個方法獲取到的rect并非你在layoutSubviews里計算的結(jié)果楣嘁,仍然是系統(tǒng)計算的結(jié)果磅轻,這就是破壞了原始按鈕的方法
-
imageRectForContentRect:
和titleRectForContentRect:
的調(diào)用時機:
- 在第一次調(diào)用titleLabel和imageView的getter方法(懶加載)時,alloc init之前會調(diào)用一次(無論有無圖片文字都會直接調(diào)),因此逐虚,在重寫這2個方法時聋溜,在方法里面不要使用self.imageView和self.titleLabel,因為這2個控件是懶加載叭爱,如果在重寫的這2個方法里是第一調(diào)用imageView和titleLabel的getter方法, 則會造成死循環(huán)
- 在layoutsSubviews中如果文字或圖片不為空時會調(diào)用, 測試方式:在重寫的這兩個方法里調(diào)用setNeedsLayout(layutSubviews)撮躁,發(fā)現(xiàn)會造成死循環(huán)
- 按鈕的frame發(fā)生改變,設(shè)置文字圖片买雾、改動文字和圖片馒胆、設(shè)置對齊方式,設(shè)置內(nèi)容區(qū)域等時會調(diào)用凝果,其實這些祝迂,系統(tǒng)是調(diào)用了layoutSubviews從而間接的去調(diào)用
imageRectForContentRect:
和titleRectForContentRect:
......
建議
大家在實現(xiàn)按鈕的圖片在上、左器净、下型雳、右的時候,最好要注意不要去破壞系統(tǒng)按鈕,什么叫破壞呢纠俭?比如你實現(xiàn)完之后沿量,要保證按鈕的所有自帶屬性和方法依然生效,再比如:UIButton中的titleLabel和imageView是懶加載的冤荆,我們不要在實現(xiàn)自己需求的過程中去提前加載朴则,這不符合按鈕的規(guī)則
demo地址: SPButton