AutoLayout的那些事(一)

Mango's Blog

AutoLayout非常強大也非常易用,可讀性也很強,加上各種第三方AutoLayout庫,讓你布起局來猶如繃掉鏈子的狗!根本停不下來!以前的

label.frame.origin.y + label.frame.size.height + 10

如今只用:

button.snp_makeConstraints{
    $0.top.equalTo(label.snp_bottom).offset(10)
}

真是好用得不要不要。

可是,我在使用AutoLayout卻遇到不少坑,翻閱了不少博客網(wǎng)站才找到了我認為的比較不錯的解決方案。我把這些內(nèi)容貼出來,如果其中有誤,可以在下方留言指出,希望大家能夠多多交流共同進步。

本文主要分四部分:

  • updateViewConstraints與updateConstraints篇
  • AutoLayout與Frame篇
  • AutoLayout動畫篇
  • AutoLayout比例設(shè)置

其中‘篇’字體現(xiàn)了本文作者對逼格的追求。

updateViewConstraints與updateConstraints篇

基本用法

updateViewConstraintsupdateConstraintsAutoLayout出現(xiàn)后新增的api,updateConstraints主要功能是更新view的約束,并會調(diào)用其所有子視圖的該方法去更新約束。

updateViewConstraints的出現(xiàn)方便了viewController,不用專門去重寫controllerview,當viewupdateConstraints被調(diào)用時,該view若有controller,該controllerupdateViewConstraints便會被調(diào)用。

兩個方法都需要在方法實現(xiàn)的最后調(diào)用父類的該方法。并且這兩個方法不建議直接調(diào)用。

在使用過程中我發(fā)現(xiàn)這兩個方法有時候不會被系統(tǒng)調(diào)用。后來我看到public class func requiresConstraintBasedLayout() -> Bool方法的描述:

constraint-based layout engages lazily when someone tries to use it (e.g., adds a constraint to a view). If you do all of your constraint set up in -updateConstraints, you might never even receive updateConstraints if no one makes a constraint. To fix this chicken and egg problem, override this method to return YES if your view needs the window to use constraint-based layout.

大意是說,視圖并不是主動采用constraint-based的。在非constraint-based的情況下-updateConstraints,可能一次都不會被調(diào)用,解決這個問題需要重寫該類方法并返回true。

這里要注意,如果一個viewcontroller是由interface builder初始化的,那么這個實例的updateViewConstraintsupdateConstraints方法便會被系統(tǒng)自動調(diào)用,起原因應(yīng)該就是對應(yīng)的requiresConstraintBasedLayout方法返回true。而純代碼初始化的視圖requiresConstraintBasedLayout方法默認返回false。

所以在純代碼自定義一個view時,想把約束寫在updateConstraints方法中,就一定要重寫requiresConstraintBasedLayout方法,返回true。

至于純代碼寫的viewController如何讓其updateViewConstraints方法被調(diào)用。我自己的解決辦法是手動調(diào)用其viewsetNeedsUpdateConstraints方法。

How to use updateConstraints?

文檔中對于這兩個方法提的最多的就是,重寫這兩個方法,在里面設(shè)置約束。所以一開始我認為這兩個方法是蘋果提供給我們專門寫約束的。于是便開始嘗試使用。

直到后來在UIView中看到這樣一句話:

You should only override this method when changing constraints in place is too slow, or when a view is producing a number of redundant changes.

“你只因該在添加約束過于慢的時候,或者一次要修改大量約束的情況下重寫次方法?!?/p>

簡直是讓人覺得又迷茫又坑爹。updateConstraints方法到底應(yīng)該何時使用

后來看到how to use updateConstraints這篇文章。給出了一個合理的解釋:

  • 盡量將約束的添加寫到類似于viewDidLoad的方法中。

  • updateConstraints并不應(yīng)該用來給視圖添加約束,它更適合用于周期性地更新視圖的約束,或者在添加約束過于消耗性能的情況下將約束寫到該方法中。

  • 當我們在響應(yīng)事件時(例如點擊按鈕時)對約束的修改如果寫到updateConstraints中,會讓代碼的可讀性非常差。

關(guān)于性能,我也做了一個簡單的測試:

class MMView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.grayColor()
        initManyButton()
        //初始化時添加約束
        test() //每次只有一個test()不被注釋就好
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        //響應(yīng)事件時添加約束
        //test()
    }
    
    override func updateConstraints() {
        //updateConstraints中添加約束
        //test()
        super.updateConstraints()
    }
    
    func test(){
        let then = CFAbsoluteTimeGetCurrent()
        addConstraintsToButton()
        let now = CFAbsoluteTimeGetCurrent()
        print(now - then)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    let buttonTag = 200
    func initManyButton(){
        for index in 0...1000{
            let button = UIButton(type: .System)
            button.tag = buttonTag + index
            self.addSubview(button)
        }
    }
    func addConstraintsToButton(){
        for index in 0...1000{
            if let button = self.viewWithTag(index+buttonTag){
                button.snp_makeConstraints{ make in
                    make.center.equalTo(self)
                    make.size.equalTo(self)
                }
            }
        }
    }
}

分別對 將 設(shè)置約束 寫在init中、寫在updateConstraints中、寫在事件響應(yīng)方法中 的時間消耗進行測試,對1000個button添加約束,每個添加4個約束。

  • init中,時間消耗約為0.37秒
  • 寫在updateconstraints中,時間消耗約為0.52秒
  • 寫在事件響應(yīng)方法中,時間消耗約為0.77秒

所以,結(jié)論,還是將約束的設(shè)置寫在viewDidLoad中或者init中。沒事兒盡量不去碰updateConstraints。除非對性能有要求。

關(guān)于UIView的translatesAutoresizingMaskIntoConstraints屬性

最近在對AutoLayout的學習中發(fā)現(xiàn),很多人似乎對translatesAutoresizingMaskIntoConstraints的誤解非常大,很多時候遇到問題總有人會在下面回答到:把translatesAutoresizingMaskIntoConstraints設(shè)置成false就可以解決問題。。。實際上并沒有什么用。

那么這個屬性到底是做什么的呢?

其實這個屬性的命名已經(jīng)把這個屬性的功能解釋的非常清楚了。

除了AutoLayout,AutoresizingMask也是一種布局方式。這個想必大家都有了解。默認情況下,translatesAutoresizingMaskIntoConstraints = true , 此時視圖的AutoresizingMask會被轉(zhuǎn)換成對應(yīng)效果的約束。這樣很可能就會和我們手動添加的其它約束有沖突。此屬性設(shè)置成false時,AutoresizingMask就不會變成約束。也就是說 當前 視圖的 AutoresizingMask失效了。

那我們什么時候需要設(shè)置這個屬性呢?

當我們用代碼添加視圖時,視圖的translatesAutoresizingMaskIntoConstraints屬性默認為true,可是AutoresizingMask屬性默認會被設(shè)置成.None。也就是說如果我們不去動AutoresizingMask,那么AutoresizingMask就不會對約束產(chǎn)生影響。

當我們使用interface builder添加視圖時,AutoresizingMask雖然會被設(shè)置成非.None,但是translatesAutoresizingMaskIntoConstraints默認被設(shè)置成了false。所以也不會有沖突。

反而有的視圖是靠AutoresizingMask布局的,當我們修改了translatesAutoresizingMaskIntoConstraints后會讓視圖失去約束,走投無路。例如我自定義轉(zhuǎn)場時就遇到了這樣的問題,轉(zhuǎn)場后的視圖并不在視圖的正中間。

所以,這個屬性,基本上我們也不用設(shè)置它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容