CHCR讓佈局更婀娜

高家二少爺發表於2019-03-02

傳統方式佈局的弊端

兩個label同一行佈局的時候,傳統做法是將其中的一個label的寬度固定;另外一個label設定左右約束,讓它跟隨螢幕寬度拉伸壓縮。如下的例子,我給商品數量Label加了一個60寬度約束。然後,我們先來看看這樣做的問題

  • 極端情況,當商品數量很大時就顯示不下了。
image
  • 商品名稱很長時,商品數量的剩餘寬度顯得有些浪費空間(浪費的程度要看具體的商品數量最大與最小的差值)
image

如何解決

要解決這兩個問題就需要讓兩個Label的寬度在一定程度上自動調整。那這個調整到底是怎樣一個程度呢?

首先,肯定跟業務有關。在兩個文字的寬度與間距之和大於螢幕的時候至少是有一個無法完全顯示的。一般情況下不會讓兩個文字都顯示不全(這樣也太秀了),那麼如何做取捨就看業務了。在本文的例子中,商品名稱與數量,肯定是要先保證數量可以看得到。

在兩個文字寬度與間距之和小於螢幕的時候,毫無懸念,就是兩個文字都顯示。都顯示的時候,是拉伸其中一個Label還是不拉伸任何Label?這個不重要,重要的是加約束方便就好。

把商品數量Label的寬度去掉就出現了一個錯誤(注意此時總體寬度不足螢幕寬度),因為此時系統不知道如何拉伸或者壓縮以滿足整體約束。

image

檢視約束錯誤的快捷修復資訊,大概意思是把 商品數量Label的 horizontal hugging 從251降低到250以達到比其它View低的目的。

image

那麼 horizontal hugging 又是什麼鬼?我們回頭看一下AutoLayout文件基礎的東西

[AutoLayout Guide:Anatomy of a Constraint](developer.apple.com/library/arc…)

用到的基礎知識

在 Intrinsic Content Size 這一節有這樣一張圖:

image

Content huggingContent compression Resistance 簡稱為CHCR

對應的解釋比較冗長,直接看等價的約束來的實在點

// Compression Resistance
View.height >= 0.0 * NotAnAttribute + IntrinsicHeight
View.width >= 0.0 * NotAnAttribute + IntrinsicWidth
 
// Content Hugging
View.height <= 0.0 * NotAnAttribute + IntrinsicHeight
View.width <= 0.0 * NotAnAttribute + IntrinsicWidth
複製程式碼

在每一個檢視中這兩種約束時同時存在的,為了避免衝突,AutoLayout設計者給了一個預設的
優先順序。By default, views use a 250 priority for their content hugging, and a 750 priority for their compression resistance.
這個預設值在Interface Builder 的 size inspector 中能看得到。

Constaints

Compression Resistance Priority 高於 Content Hugging Priority 就導致了一個控制元件更容易被拉伸而不是被壓縮。因為壓縮可能導致顯示不全,所以更容易拉伸是比較合理的。

瞭解完這兩個屬性,我們回頭看一下這個錯誤。應該就是商品名稱和商品數量兩個Label的Compression Resistance PriorityContent Hugging 都相同,AutoLayout 不知道拉伸或者壓縮哪一個Label造成的。

那麼,為何Interface Builder提示我們修改Content Hugging而不是Compression Resistance Priority呢?文件中沒有找到相關的說明與解釋。不過我試了一下,當文字長度與間距之和超過螢幕寬度的時候,Interface Builder的提示就變成了修改Compression Resistance Priority。那麼對應的情況下生效的是哪一個屬性,大家用腳趾頭想一下就知道了。

結論

最後我們回到需求:商品數量維持全部顯示,商品名稱最大化顯示。
結論就是把商品數量Label的Compression Resistance PriorityContent Hugging 都設定成比商品名稱高。效果如圖:

效果

本文以橫向的兩個Label為例,縱向、多個或者過個其它有內容不定寬高的控制元件都可以通過這樣的方式去做約束。具體如何,就留給大家自己推導。

補充一個意外情況,怎樣的情況可以給商品數量加一個寬度上限。

意外情況

####完了?
###程式碼呢?
##程式碼呢?
#程式碼呢?

Show the code

有一句話是怎麼說的:
nib能實現的,程式碼都能實現
如果不是,儘管過來找我。
我~

求教

程式碼實現也很簡單,UIView的幾個方法

- (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);
複製程式碼

以及引數對應的兩個列舉

typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
    UILayoutConstraintAxisHorizontal = 0,
    UILayoutConstraintAxisVertical = 1
};
複製程式碼
typedef float UILayoutPriority NS_TYPED_EXTENSIBLE_ENUM;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint.  Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50; // 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.
複製程式碼

相關文章