iOS布局方式總結(jié)

1. frame布局。

性能相對比較好,但當(dāng)views比較多,view依賴關(guān)系比較復(fù)雜或適配不同機型時,處理起來會比較繁瑣,代碼可讀性低。特別在數(shù)據(jù)變化或橫豎屏切換導(dǎo)致界面布局變化,通常要重新計算每個視圖的frame,工作量巨大。

2. autoresizing布局。通過設(shè)置UIView的autoresizingMask屬性來設(shè)置布局方式組合。

缺點:描述界面變化規(guī)則不夠靈活,很多變化規(guī)則根本無法精確描述。
變化規(guī)則只能基于父視圖與子視圖之間,無法建立同級視圖或者跨級視圖之間的關(guān)系。

3. Auto Layout(NSLayoutConstraint)。

Cassowary的布局算法,通過將布局問題抽象成線性不等式,并分解成多個位置間的約束, Apple 在iOS 6推出的 Auto Layout(NSLayoutConstraint),內(nèi)部使用的是該算法。
NSLayoutConstraint包含firstItem(約束視圖),secondItem(參照視圖),firstAttribute(約束視圖的屬性),secondAttribute(參照視圖的屬性),relation(關(guān)系,包括>=,=,<=), multiplier(比例系數(shù)),constant(常量),priority(優(yōu)先級)。
約束屬性中NSLayoutAttributeBaseline代表相對基線對齊。比如在UILabel中,基線是文字底部的位置,相對bottom略高。在大部分view中,基線和底部是一致的。
iOS 8對約束屬性增加了一系列帶上Margin的布局屬性,類似CSS里的padding,比如NSLayoutAttributeLeftMargin。相對于NSLayoutAttributeLeft的左對齊,NSLayoutAttributeLeftMargin一般會在左邊留出8個距離作為margin,可通過layoutMargins屬性修改。
priority優(yōu)先級只有在兩個約束有沖突的時候才起作用,優(yōu)先級高的會覆蓋優(yōu)先級低的,最高的優(yōu)先級為1000。

3.1 translatesAutoresizingMaskIntoConstraints。

UIView有個translatesAutoresizingMaskIntoConstraints屬性,對于用代碼創(chuàng)建的view,默認值是true。translatesAutoresizingMaskIntoConstraints會將 frame/autoresizing布局 自動轉(zhuǎn)化為 auto layout布局,轉(zhuǎn)化的結(jié)果是為這個視圖自動添加所有需要的約束,如果我們這時給視圖添加自己創(chuàng)建的約束就一定會約束沖突。為了避免約束沖突,需要設(shè)置translatesAutoresizingMaskIntoConstraints = false。

3.2 UILayoutGuide。

如果要實現(xiàn)布局 對多個view之間的magin動態(tài)約束(margin的值不是固定,值受到布局約束),或者實現(xiàn)多控件共同居中,一種常見的實現(xiàn)方式是使用一個或多個輔助view,專門用于實現(xiàn)它們的約束關(guān)系。但這種輔助view會增加view視圖復(fù)雜度,并會加入到事件響應(yīng)路由中。iOS 9 便推出了UILayoutGuide來代替這種輔助view,UILayoutGuide直接繼承自NSObject,并沒有真正的創(chuàng)建一個View,只是創(chuàng)建了一個矩形空間,只在進行auto layout時參與進來計算。

3.2 safeAreaLayoutGuide(繼承自UILayoutGuide)。

iOS 11 增加了safeAreaLayoutGuide 和 safeAreaInsets作為UIView的安全區(qū)屬性。safeAreaLayoutGuide用于自動布局下對子視圖建立與安全區(qū)域的約束,safeAreaInsets用于frame布局,返回view四個方向與安全區(qū)域的偏移量。safeAreaInsets在viewDidLoad獲取不到真實的值,可以在viewSafeAreaInsetsDidChange獲取。

4. NSLayoutAnchor。iOS 9 推出的自動布局類,通過設(shè)置view的不同錨來實現(xiàn)自動布局約束,內(nèi)部可以理解成也是NSLayoutConstraint實現(xiàn)。NSLayoutAnchor相對NSLayoutConstraint,代碼更加整潔,優(yōu)雅,易讀。
4. VFL。Visual Format Language 可視化格式語言是蘋果公司為了簡化Autolayout的編碼而推出的抽象語言。通過一個抽象后的字符串描述視圖的自動布局約束,簡化了代碼,增加了可讀性。
5. 自動布局SnapKit/Masonry。主流使用的自動布局框架,它們使用鏈式編程的方式對NSLayoutConstraint進行了二次封裝。舉個例子:
make.bottom.lessThanOrEqualTo(contentView.snp.bottom).multipliedBy(0.5).offset(-10). priority(.low)
可以理解成NSLayoutConstraint的如下偽代碼。
firstItem.firstAttribute.relation(secondItem. secondAttribute). multiplier. constant.priority

從snapKit源碼可以得知,SnapKit會自動將view的translatesAutoresizingMaskIntoConstraints設(shè)置為false。對于使用了snapKit的view,關(guān)閉布局向auto layout隱式轉(zhuǎn)換。

extension LayoutConstraintItem {
    
    internal func prepare() {
        if let view = self as? ConstraintView {
            view.translatesAutoresizingMaskIntoConstraints = false
        }
    }
}
5.1高級用法匯總:

5.1.1 對單個約束進行操作。

    var labelConstraint: Constraint?

    label.snp.makeConstraints { (make) in
        make.top.equalToSuperview()
        make.right.lessThanOrEqualToSuperview()
        labelConstraint = make.right.lessThanOrEqualTo(button.snp.left).constraint
    }

    // 關(guān)閉約束
    labelConstraint?.deactivate()
    // 開啟約束
    labelConstraint?.activate()
    // 更新約束
    labelConstraint?.update(offset: -10)
    // 更改優(yōu)先級
    labelConstraint?.update(priority: .low)

5.1.2 contentHuggingPriority 和 ContentCompressionResistancePriority。
UILabel、UIImageView、UIButton 在沒有設(shè)置size約束的時候,會使用數(shù)據(jù)填充計算后的intrinsicContentSize作為視圖的size約束。contentHuggingPriority(拒絕放大優(yōu)先級) 和 ContentCompressionResistancePriority(拒絕壓縮優(yōu)先級)常用于多個使用intrinsicContentSize作為自身size約束的視圖,在相互存在水平或垂直方向關(guān)聯(lián)約束,導(dǎo)致視圖需要壓縮或放大的拒絕優(yōu)先級,拒絕優(yōu)先級低的視圖優(yōu)先放大/壓縮。
在使用拒絕壓縮優(yōu)先級時,若要指定視圖滿足最小寬度,此時在極限情況,所有視圖都會出現(xiàn)壓縮,因此需要將寬度優(yōu)先級設(shè)置最高(大于所有的縮小優(yōu)先級)

        let label1 = UILabel()
        label1.text = "111111111111111111111111111111111111111"
        view.addSubview(label1)
        let label2 = UILabel()
        label2.text = "222222222222222222222222222222222222222222"
        view.addSubview(label2)
        label1.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        label2.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
        label1.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.top.equalTo(50)
            // label1寬度優(yōu)先級大于label2的壓縮優(yōu)先級
            make.width.greaterThanOrEqualTo(50).priority(.required)
        }
        label2.snp.makeConstraints { (make) in
            make.right.equalToSuperview()
            make.top.equalTo(50)
            make.left.equalTo(label1.snp.right)
        }

5.1.3 UILayoutGuide。
使用 UILayoutGuide 作為虛擬占位布局對象,可以實現(xiàn)多控件居中,動態(tài)margin等約束效果,同3.2。
使用UILayoutGuide實現(xiàn)動態(tài)margin,三等分間距效果:

    func test() {
        let blueView = UIView()
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
        let redView = UIView()
        redView.backgroundColor = .red
        view.addSubview(redView)
        
        let leftLayoutGuide = UILayoutGuide()
        let middleLayoutGuide = UILayoutGuide()
        let rightLayoutGuide = UILayoutGuide()
        view.addLayoutGuide(leftLayoutGuide)
        view.addLayoutGuide(middleLayoutGuide)
        view.addLayoutGuide(rightLayoutGuide)
        
        blueView.snp.makeConstraints { (make) in
            make.height.width.equalTo(50)
            make.top.equalTo(100)
        }
        redView.snp.makeConstraints { (make) in
            make.height.width.equalTo(50)
            make.top.equalTo(100)
        }
        
        leftLayoutGuide.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalTo(blueView.snp.left)
        }
        middleLayoutGuide.snp.makeConstraints { (make) in
            make.left.equalTo(blueView.snp.right)
            make.right.equalTo(redView.snp.left)
            make.width.equalTo(leftLayoutGuide)
        }
        rightLayoutGuide.snp.makeConstraints { (make) in
            make.right.equalToSuperview()
            make.left.equalTo(redView.snp.right)
            make.width.equalTo(leftLayoutGuide)
        }
    }
UILayoutGuide實現(xiàn)動態(tài)margin

使用UILayoutGuide實現(xiàn)多控件居中:

func test() {
        let blueView = UIView()
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
        let redView = UIView()
        redView.backgroundColor = .red
        view.addSubview(redView)
        
        let layoutGuide = UILayoutGuide()
        view.addLayoutGuide(layoutGuide)
        
        blueView.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(100)
            make.top.equalTo(100)
        }
        redView.snp.makeConstraints { (make) in
            make.height.width.equalTo(50)
            make.top.equalTo(100)
            make.left.equalTo(blueView.snp.right).offset(20)
        }
        
        layoutGuide.snp.makeConstraints { (make) in
            make.centerX.equalToSuperview()
            make.left.equalTo(blueView.snp.left)
            make.right.equalTo(redView.snp.right)
        }
    }
UILayoutGuide實現(xiàn)多控件居中

5.1.4 在父視圖高度不確定,受數(shù)據(jù)填充和多個子視圖布局影響。可以通過對多個可能的底部視圖分別設(shè)定make.bottom.lessThanOrEqualTo/make.bottom.lessThanOrEqualToSuperview(),實現(xiàn)父視圖動態(tài)高度。

5.1.5 對父視圖調(diào)用layoutIfNeeded()使約束立即生效(自身調(diào)用只有size生效),可在動畫中使用產(chǎn)生約束動畫。

        label1.superview?.setNeedsLayout()
        UIView.animate(withDuration: 2) {
            label1.snp.updateConstraints { (make) in
                make.top.equalTo(200)
            }
            label1.superview?.layoutIfNeeded()
        }

5.1.6 使用safeAreaLayoutGuide屬性,將視圖放在安全區(qū)域內(nèi)。

    func test() {
        let redView = UIView()
        redView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
        view.addSubview(redView)
        redView.snp.makeConstraints { (make) in
            make.edges.equalTo(self.view)
        }
        
        let blueView = UIView()
        blueView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
        view.addSubview(blueView)
        blueView.snp.makeConstraints { (make) in
            make.edges.equalTo(self.view.safeAreaLayoutGuide)
        }
    }
紫色區(qū)域為安全區(qū)域

即使不是VC的視圖,獲取的safeAreaLayoutGuide也是在安全區(qū)域中。

    func test() {
        let testView = TestView()
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.left.right.equalTo(self.view.safeAreaLayoutGuide)
            make.top.equalToSuperview()
            make.height.equalTo(120)
        }
    }

class TestView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupUI() {
        backgroundColor = .gray
        let label = UILabel.init()
        label.numberOfLines = 0
        label.text = "123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123"
        addSubview(label)
        label.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
//            make.edges.equalTo(self.safeAreaLayoutGuide)
        }
    }
}
未使用safeAreaLayoutGuide,label范圍超出安全區(qū)域

將 make.edges.equalToSuperview() 改成 make.edges.equalTo(self.safeAreaLayoutGuide)

//            make.edges.equalToSuperview()
            make.edges.equalTo(self.safeAreaLayoutGuide)
label范圍在安全區(qū)域以內(nèi)

5.1.7 可通過additionalSafeAreaInsets 修改VC的安全區(qū)域范圍。

self.additionalSafeAreaInsets = UIEdgeInsets(top: 20.0, left: 50.0, bottom: 50.0, right: 50.0)
通過dditionalSafeAreaInsets縮小VC安全區(qū)域范圍

UIView的insetsLayoutMarginsFromSafeArea屬性默認為true,代表layoutMargin屬性會加上safeArea,設(shè)為false,則不會加上safeArea。

5.1.8 UIScrollView 中的 safe area。
在iOS 11以前,當(dāng)automaticallyAdjustsScrollViewInsets屬性為true,導(dǎo)航欄為半透明,VC的加入的第一個scrollView會自動調(diào)整其contentInset,以保證滑動視圖里的內(nèi)容不被UINavigationBar與UITabBar遮擋。contentInset是實際的inset。
在iOS 11或以后,取代成UIScrollView的contentInsetAdjustmentBehavior屬性,當(dāng)scrollView超出安全區(qū)域,會調(diào)整inset以防止scrollView的內(nèi)容超出安全區(qū)域。contentInset 是用戶自定義的inset,adjustedContentInset是實際的inset,并且是只讀屬性。可以理解成 contentInset + contentInsetAdjustmentBehavior調(diào)整的inset = adjustedContentInset(實際inset)。

func test() {
        scrollView.backgroundColor = .purple
        scrollView.contentInsetAdjustmentBehavior = .always
        view.addSubview(scrollView)
        scrollView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
        
        let label = UILabel()
        label.text = "123123123123"
        label.textColor = .white
        scrollView.addSubview(label)
        label.snp.makeConstraints { (make) in
            make.left.top.equalToSuperview()
        }
    }
label顯示在安全區(qū)域以內(nèi)

UITableView 有個insetsContentViewsToSafeArea屬性,會調(diào)整自動調(diào)整顯示內(nèi)容在安全區(qū)域以內(nèi),默認為true。

6. UIStackview
7. SDAutoLayout
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容