via:http://mp.weixin.qq.com/s/3xWoaNcTQKnJDZoG1KrcAg
1瘟栖、需求
不知道大家是否常有這樣的需求:一個界面中,有多個 view蜡豹,每個 view 的大小由其內(nèi)容決定麸粮。當(dāng)一個 view 有內(nèi)容時,下一個 view 與它之間會一個間隔镜廉。如果沒有內(nèi)容的話弄诲,下一個 view 就會緊挨著它。如下圖所示:
[圖片上傳中娇唯。齐遵。。(1)]
圖1 中塔插,四個 label 的大小是自適應(yīng)的梗摇,且每個 label 相隔 10px。這種情況下想许,視圖看起來是很正常的伶授。但如果其中某些 label 沒有文字呢?看下圖:
[圖2] label2.text = nil
[圖3] label2.text = nil; label3.text= nil;
圖2 是 label3.text 為 nil流纹,中間有一個明顯過大的間隔糜烹,這是由于 label3 的高度雖然為 0,但由于它與 label2 的間隔為 10px漱凝,而 label4 與它的間隔又是 10px疮蹦,所以造成了圖中 label2 與 label4 的過大的間隔。
圖3 則更為慘不忍睹碉哑。
這時挚币,咱們的需求就出來了:label 與 label 之間的間隔能隨著它們自身內(nèi)容的變化而變化。當(dāng)有文本時扣典,間隔存在妆毕;當(dāng)沒有文本時,則緊挨在一起贮尖。當(dāng)然笛粘,這里的間隔希望不僅是垂直的,水平方向也應(yīng)該是一樣的湿硝。
2薪前、解決方案
來看看各種解決方法。
2.1 動態(tài)更新約束
這是最直觀关斜、容易想到的辦法示括,就是在 label2 等內(nèi)容有變化時,去調(diào)整相關(guān)的間隔約束:更改 constants 或 優(yōu)先級 等痢畜。
這種方法存在的問題是維護成本太高垛膝,這里只有四個 label ,但是要維護這種間隔約束關(guān)系主就已經(jīng)很累了丁稀。所以這種方法是比較初級的吼拥,不靈活。
2.2 -(CGSize)intrinsicContentSize
自定義-(CGSize)intrinsicContentSize
視圖的 內(nèi)容 在 auto layout 中线衫,其與約束是 同樣重要的凿可。視圖有一個方法:– (CGSize)intrinsicContentSize,用來返回展示完整視力內(nèi)容的最小 size授账。
比如 UILabel 就是根據(jù)它的 text枯跑、attributedText 和 preferredMaxLayoutWidth 等來計算出它的內(nèi)容 size。
當(dāng)視圖內(nèi)容改變時矗积,可以調(diào)用 – (void)invalidateIntrinsicContentSize 方法來讓 Auto Layout 在下次布局時重新計算全肮。
咱們可以將間隔當(dāng)作 內(nèi)容 的一部分,將其計算在內(nèi):
<pre>
@interface NLLabelIntervalView : UIView
@property (nonatomic, strong) UILabel *label;
@property (nonatomic, assign) CGSize intervalSize;
@end
@implementation NLLabelIntervalView
- (CGSize)intrinsicContentSize {
CGSize size = [self.label intrinsicContentSize];
if ([self.label.text length] > 0) {
size.width += self.intervalSize.width;
size.height += self.intervalSize.height;
}
return size;
}
@end
</pre>
可以看到棘捣,這種方法在一定程度上可以解決間隔問題辜腺,但它有很大的不足:它將間隔 侵入 到內(nèi)容中;需要 包裝 目標(biāo)視圖乍恐,這個代價卻實在有點大评疗,雖然利用 繼承 可以一部分 包裝 問題,但類似于這里的 UILabel茵烈,由于它內(nèi)容的繪制方法(文字垂直居中)百匆,繼承 是無法做到 間隔 的。不過在自定義視圖時呜投,如果就間隔考慮進去的話加匈,問題倒是不大存璃。
所以,這種方案適用于自定義視圖中雕拼,對系統(tǒng)定義的視圖幫助有限纵东。
2.3 利用對齊矩形(alignment rect)
你可能會直觀的認(rèn)為 Auto Layout 中,約束是使用 frame 來確定視圖的大小和位置的啥寇,但實際上偎球,它使用的是 對齊矩形(alignment rect) 這個幾何元素。不過在大多數(shù)情況下辑甜,frame 和 alignment rect 是相等的衰絮,所以你這么理解也沒什么不對。
系統(tǒng)有 frame 不用磷醋,為啥要用 alignment rect 呢猫牡?
有時候,咱們在創(chuàng)建復(fù)雜的視圖時邓线,可能會添加各種裝飾元素镊掖,如陰影、外邊框褂痰、角標(biāo)等等亩进。但考慮到開發(fā)這樣的視圖所需時間成本,或者為了避免離屏渲染等原因缩歪,會找設(shè)計師直接切相應(yīng)的成品圖給咱們归薛。如下圖:
alignment rect
[圖片上傳中。匪蝙。主籍。(4)](圖片來源:iOS Auto Layout Demystified)
上圖中,(a) 是咱們拿到的圖逛球,(c) 是這個圖的 frame千元。顯然,咱們在布局的時候颤绕,不想將陰影和角標(biāo)考慮進去(視圖的 center 和 底邊幸海、右邊都發(fā)生了偏移),而是只考慮中間的核心部分奥务,如圖 (b) 中框出的矩形所示物独。
對齊矩形就是用來處理這種情況的。
UIView 提供了方法氯葬,由 frame 得到 alignment rect:
// The alignment rectangle for the specified frame.
- (CGRect)alignmentRectForFrame:(CGRect)frame;
它得可逆挡篓,也就是說得能從 alignment rect 反過來得到 frame:
// The frame for the specified alignment rectangle.
- (CGRect)frameForAlignmentRect:(CGRect)alignmentRect;
考慮到每次重寫這兩個方法比較煩,系統(tǒng)也提供了一個簡便方法帚称,由 inset 來指定 frame 與 aligment rect 的關(guān)系:
// The insets from the view’s frame that define its alignment rectangle.
- (UIEdgeInsets)alignmentRectInsets;
回到間隔問題官研。咱們可以將間隔當(dāng)作上面提到的裝飾秽澳,讓 UILabel 的 alignment rect 比 frame 多個 10 point 間隔就好了:
<pre>
@interface NLLabel : UILabel
@end
@implementation NLLabel
- (UIEdgeInsets)alignmentRectInsets {
return UIEdgeInsetsMake(.0, .0, -10.0, .0);
}
</pre>
不過讓人感覺迷惑是的,在 iOS 中戏羽,frameForAlignmentRect: 和 alignmentRectForFrame: 重寫之后肝集,并沒有起到預(yù)期的作用,OS X 中倒是正常蛛壳。所以在 iOS 中,還是使用 alignmentRectInsets 的好所刀。對于這個現(xiàn)象衙荐,還希望有了解的同學(xué)幫忙解釋一下。
當(dāng)然浮创,每次都得要繼承才能使用對齊矩形忧吟,畢竟不太方便,也許 關(guān)聯(lián)對象 和 method swizzled 組合起來是個可行方案:
<pre>
import <objc/runtime.h>
@interface UIView (nl_aligmentRectInsets)
@property (nonatomic, copy) UIEdgeInsets (^nl_alignmentRectInsets)(UIEdgeInsets originInsets);
@end
...
</pre>
3 總結(jié)
對齊矩形可是個好玩意呢~
參考:
1斩披、Apple Developerhttps://developer.apple.com/reference/uikit/uiview?language=objc
2溜族、Advanced Auto Layout Toolboxhttp://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
3、iOS Auto Layout Demystified