[譯]Auto Layout的最佳實踐

jopen 8年前發布 | 6K 次閱讀 iOS開發 移動開發 Auto Layout

原文鏈接:https://medium.com/@NSomar/auto-layout-best-practices-for-minimum-pain-c130b2b1a0f6#.tqby1u8l4


Auto Layout是個很棒的工具,作為開發者,它可以讓我們保持神志清醒,還能讓我們這些懶人們在設置frame的時候遠離“神奇數字”。

但是任何技術都不是完美無缺的,我必須得說我花了太多的時間來debug那些缺失的約束條件,或者對于一些藏在層級結構深處的視圖,添加一個沖突的約束條件就會把整個布局毀掉,當這些事情發生的時候簡直是天崩地裂!

在debug了無數個小時的auto layout的問題后,我發現每次造成問題的都是我自己(或者是你自己!),而問題的解決辦法總是相同的:遵從auto layout的文檔和規則!

我會在這里把正確使用auto layout的最佳實踐說給你聽,這樣你就可以免除一些痛苦了。

UIView的子類應該實現intrinsicContentSize方法

每個UIView的子類都應該實現intrinsicContentSize,并且返回它認為合適的大小。

假設我們新建了一個AwesomeView,而且我們知道這個view的默認尺寸是300x20,我們會這么寫:

</span>
-(CGSize)intrinsicContentSize {
 return CGSizeMake(300, 20);
}

如果我們不知道view的寬度,我們會用UIViewNoIntrinsicMetric來代替:
- (CGSize)intrinsicContentSize {
 return CGSizeMake(UIViewNoIntrinsicMetric, 20);
}

UIView基類的updateConstraints實現會調用intrinsicContentSize,它會使用返回的尺寸來給AwesomeView添加約束條件。

根據上面例子中的(300,20)尺寸,會添加下面的約束條件:

</span>
<NSContentSizeLayoutConstraint:0x7fef48d52580 H:[AwesomeView:0x7fef48ead7f0(300)] Hug:250 CompressionResistance:750>,
<NSContentSizeLayoutConstraint:0x7fef48d4d110 V:[AwesomeView:0x7fef48ead7f0(20)] Hug:250 CompressionResistance:750>

添加的約束條件比較特殊,它們是NSContentSizeLayoutConstraint類型的,這個類是個私有類。這些約束條件的優先級范圍是0-1000,“包住限制”(譯者注:hug consistance,使其在“內容大小”的基礎上不能繼續變大)的優先級是250,“撐住限制”(compression resistance,撐住使其在在其“內容大小”的基礎上不能繼續變小)的優先級是750,使用的常量等于通過intrinsicContentSize返回的值。

請注意,UIView基類實現updateConstraints只有在它第一次執行的時候才會添加intrinsicContentSize約束。

UIView的子類絕不應該給自身的尺寸添加約束

每個view都會負責給它的superview設置約束,但是view絕不應該設置它自己的約束條件,不管是對于自身的約束(比如說 NSLayoutAttributeWidth NSLayoutAttributeHeight),還是相對于superview的約束。

如果一個view想指定自己的高度或者寬度,它應該通過實現 intrinsicContentSize來達到目的。

這是個糟糕的例子:

</span>
- (instancetype)init
{
     self = [super init];
     if (self) {
         [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100]];
         [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100]];
     }
         return self;
} 

這個view在通過給自身添加約束來設置自己的寬度和高度,那么如果現在它的superview也在試圖指定這些數值會發生什么呢?
//Some place in the superview
[awesome addConstraint:[NSLayoutConstraint constraintWithItem:awesome attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:200]];
[awesome addConstraint:[NSLayoutConstraint constraintWithItem:awesome attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:200]];

嘭!
Unable to simultaneously satisfy constraints.
…
property translatesAutoresizingMaskIntoConstraints) 
(
 “<NSLayoutConstraint:0x7ff3b16c2ae0 H:[AwesomeView:0x7ff3b16bfa00(100)]>”,
 “<NSLayoutConstraint:0x7ff3b16c2330 H:[AwesomeView:0x7ff3b16bfa00(200)]>”
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7ff3b16c2330 H:[AwesomeView:0x7ff3b16bfa00(200)]>
…

AwesomeView添加的寬度/高度是100/100,而它的superview也添加了寬度/高度,但是是200/200,這樣autoLayout就不知道該選擇哪個約束條件了,因為它們的優先級都一樣。

一種解決辦法是這樣的,將AwesomeView自身的優先級降低一點。

</span>
[self addConstraint:({
 NSLayoutConstraint *constraint;
 constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100];
 constraint.priority = 800;
 constraint;
 })];

這樣autoLayout就可以做出選擇了,因為它自身添加的優先級比較低,它就可以選擇superview添加的約束了。

然而,盡管這樣可以解決問題,但正確的方式是通過 intrinsicContentSize來指定它的高度。

UIView的子類絕不應該給它的superview添加約束

和上面的原因一樣,子視圖絕不應該給他的父視圖添加約束。子視圖的位置是由父視圖決定的。

像這么做很糟糕:

</span>
- (void)didMoveToSuperview {
     [super didMoveToSuperview];
     [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
}

太糟糕了,它不能離開相對于它的父視圖的位置。所以,當父視圖想把Awesome放到另一個位置上會怎么樣?是的,就會拋出來另一個Unable to simultaneously satisfy constraints的問題。

updateConstraints是用來更新約束條件的

顧名思義,updateConstraints只是被用來更新需要的約束的。一個正確的實現 updateConstraints的方式應該長這個樣子:

</span>
- (instancetype)init
{
 … 
 init stuff
 …
 _labelCenterYConstraints = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
 [self addConstraint:_labelCenterYConstraints];
 label.text = @”I Am truly awesome!”;
 …
}

  • (void)updateConstraints { self.labelCenterYConstraints.constant = self.labelVerticalDisplacement; [super updateConstraints]; }</pre>
    然后在需要的地方:

    awesome.labelVerticalDisplacement = 40;
    [awesome setNeedsUpdateConstraints];

    調用 setNeedsUpdateConstraints會使autoLayout重新計算布局,因此會調用 updateConstraints,從而讀取新的label狀態并更新約束。

    在上面的例子中,你本可以只更新 _labelCenterYConstraints這個約束,如果你的視圖暴露出約束,或者如果你可以簡單獲得一個約束,那就直接設置約束的常量好了,不必使用updateConstraints。所以,上面的代碼也可以這么實現:

    awesome.labelCenterYConstraints.constant = 40;</pre> 

    一個非常糟糕 updateConstraints的實現會長這個樣子:

    </span>
    - (void)updateConstraints {
    [self removeConstraints:self.constraints];
    /*
    create the constraint here
    */
    [super updateConstraints]; 
    }

    這么做是非常錯誤的,因為:

    • 系統調用 updateConstraints很多次,因此移除或者重新創建可能會校驗約束的合理性。
    • [self removeConstraints:self.constraints]; 會移除包括xib或storyboard創建的所有約束條件,你該怎么重新創建這些約束?(趕緊說你不能!)
    • 上面的updateConstraints實現會覆蓋掉intrinsicContentSize的效果,因為你在調用[super updateConstraints];后移除了系統添加的約束條件。
    • updateConstraints應該被用來創建約束條件一次,然后僅僅移除掉失效的約束。它絕不該是一個移除所有約束再把每個傳過來的布局添加上的地方。(感謝Alexis提供以下補充。)

    正確的實現方式是這樣:

    - (void)updateConstraints {
    if (!didSetConstraints) {
    didSetConstraints = YES;
    //create the constraint here
    }

    //Update the constraints if needed [super updateConstraints]; }</pre>

    在上面的代碼中,創建約束的動作只會執行一次,然后在接下來的updateConstraints調用中,只會對這些已創建的約束的常量進行修改。

    我一直在追尋真理!所以如果你有很多更好的實踐經驗,請在推ter上和我分享。

    來自:http://www.calios.gq/2015/12/14/[譯]Auto-Layout的最佳實踐-——-止疼片/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!