iOS Auto Layout學習筆記

一、What's Auto Layout

      Auto Layout是由蘋果公司UIKit框架提供的一個用於動態計算UIView及其子類的大小和位置的庫。html

      說到Auto Layout就不得不說Cassowary算法,由於Auto Layout是構建在Cassowary算法的基礎之上的。1997年,Auto Layout用到的佈局算法論文發表,被稱爲高效的線性方程求解算法。2011年蘋果利用Cassowary算法爲開發者提供了Auto Layout自動佈局庫中。因爲Cassowary算法的自己的優秀,不只是蘋果公司,許多開發者將其運用到各個不一樣的開發語言中,如JavaScript、ASP.NET、Java、C++等都有運用Cassowary算法的庫。從這裏也能夠看出Cassowary算法自身的優秀和先進性,否則不會被運用的如此普遍。前端

      蘋果公司在iOS 6系統時引入了Auto Layout,可是直到如今已經更新到iOS 12了,還有不少開發者仍是不肯使用Auto Layout。主要是對其反人類的語法以及對其性能問題的擔心。git

      針對Auto Layout的一些問題,在iOS 9發佈時,蘋果推出了更簡潔語法的NSLayoutAnchor。同時發佈了模仿前端Flexbox佈局思路的UIStackView,以此爲開發者在自動佈局上提供更好的選擇。github

在蘋果WWDC 2018 High Performance Auto Layout中蘋果工程師說: iOS 12將大幅度提高Auto Layout性能,使滑動屏幕時達到滿幀。 在WWDC 2018 What's New in Cocoa Touch蘋果的工程師說了iOS 12對Auto Layout優化後的表現。 算法

WWDC 2018 What's New in Cocoa Touch
從圖上能夠看出, iOS 11中視圖嵌套的數量的性能快成指數級別增加了,在 iOS 12中已經基本和手寫frame佈局的性能相似了。

iOS 6iOS 12,蘋果也在不斷的優化Auto Layout的性能,同時爲開發者提供更簡潔的API,若是你還在使用frame手寫佈局,不妨試試Auto Layout。下面我將介紹iOS中幾種經常使用的佈局方法。bash

二、Auto Layout各個版本不一樣用法

      如我要設置一個寬高爲120,居中顯示的View,效果以下圖: app

AutoLayoutdemo3.png

一、用frame手寫佈局
UIView *centerView = [[UIView alloc] init];
    centerView.backgroundColor = [UIColor redColor];
    [self.view addSubview:centerView];
    CGFloat width = self.view.frame.size.width;
    CGFloat height = self.view.frame.size.height;
    [centerView setFrame:CGRectMake(width / 2 - (60), height / 2 - (60), 120, 120)];
複製代碼
二、iOS 6提供的NSLayoutConstraint語法添加約束
centerView.translatesAutoresizingMaskIntoConstraints = NO;
    NSLayoutConstraint *consW = [NSLayoutConstraint constraintWithItem:centerView
                                                             attribute:NSLayoutAttributeWidth
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:self.view
                                                             attribute:NSLayoutAttributeWidth
                                                            multiplier:0
                                                              constant:120.0
                                 ];
    NSLayoutConstraint *consH = [NSLayoutConstraint constraintWithItem:centerView
                                                             attribute:NSLayoutAttributeHeight
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:self.view attribute:NSLayoutAttributeHeight
                                                            multiplier:0
                                                              constant:120.0
                                 ];
    NSLayoutConstraint *consX = [NSLayoutConstraint constraintWithItem:centerView
                                                             attribute:NSLayoutAttributeCenterX
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:self.view
                                                             attribute:NSLayoutAttributeCenterX
                                                            multiplier:1.0
                                                              constant:0.0
                                 ];
    NSLayoutConstraint *consY = [NSLayoutConstraint constraintWithItem:centerView
                                                             attribute:NSLayoutAttributeCenterY
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:self.view
                                                             attribute:NSLayoutAttributeCenterY
                                                            multiplier:1.0
                                                              constant:0.0
                                 ];
    [self.view addConstraints:@[consW,consH,consX,consY]];

複製代碼
三、用VFL語法
centerView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[centerView(120)]" options:0 metrics:nil views:views]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[centerView(120)]" options:0 metrics:nil views:views]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
複製代碼
四、使用第三方開源框架MasonrySnapKit
__weak typeof (self) weakSelf = self;
 [centerView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.size.mas_equalTo(CGSizeMake(120, 120));
     make.center.equalTo(weakSelf.view);
 }];

複製代碼
let centerView:UIView = UIView.init()
 view.addSubview(centerView)
 centerView.backgroundColor = UIColor.red
 centerView.snp.makeConstraints { (make) in
    make.width.equalTo(120)
    make.height.equalTo(120)
    make.center.equalTo(view)
 }
複製代碼
五、使用iOS 9以後Apple提供的NSLayoutAnchor
let centerView:UIView = UIView.init()
 view.addSubview(centerView)
 centerView.backgroundColor = UIColor.red
 centerView.translatesAutoresizingMaskIntoConstraints = false
 centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
 centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
 centerView.widthAnchor.constraint(equalToConstant: 120).isActive = true
 centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
複製代碼

      經過上面的代碼對比,使用frame手寫佈局只要幾行代碼就搞定了,使用NSLayoutConstraint語法和VFL語法是最複雜的,尤爲是NSLayoutConstraint語法要用30多行代碼才能是想一樣的效果,代碼行數越多出錯的機率也就成正比上升,因此這就是不少開發者不肯使用Auto Layout(或者說不肯意使用系統提供API來實現)的緣由之一吧。框架

      若是你的App要兼容iOS 9如下的各個版本,建議使用Masonry,若是隻兼容iOS 9以上的版本,建議使用SnapKit或者系統提供的NSLayoutAnchor API,畢竟Masonry這個庫已經2年沒有更新了。iview

在這裏我推薦優先使用NSLayoutAnchor,第三方的開源庫隨時都面臨着一些問題:ide

  • iOS 系統版本的更新形成的適配和兼容問題,若是是開源代碼要等到蘋果發佈新版本,代碼的做者再作兼容和適配
  • 代碼的做者中止更新這些代碼了,這對咱們開發者來講就很被動了,咱們要麼本身修改這些代碼,要麼選擇更新的開源代碼
  • 使用系統庫可在打包時能夠減小包大小

三、Auto Layout的生命週期

      前面說到蘋果的Auto Layout是基於Cassowary算法的,蘋果在此基礎上提供了一套Layout Engine引擎,由它來管理頁面的佈局,來完成建立、更新、銷燬等。

      在APP啓動後,會開啓一個常駐線程來監聽約束變化,當約束髮生變化後會出發Deffered Layout Pass(延遲佈局傳遞),在裏面作容錯處理(若有些視圖在更新約束時沒有肯定或缺失佈局申明),完成後進入約束監聽變化的狀態。

      當下一次刷新視圖(如調用layoutIfNeeded())時,Layout Engine會從上到下調用layoutSubviews(),而後經過Cassowary算法計算各個子視圖的大小和位置,算出來後將子視圖的framelayout Engine裏拷貝出來,在以後的處理就和手寫frame的繪製、渲染的過程同樣了。使用Auto Layout和手寫frame多的工做就在佈局計算上。

四、NSLayoutAnchor經常使用屬性

  • leadingAnchor
  • trailingAnchor
  • leftAnchor
  • rightAnchor
  • topAnchor
  • bottomAnchor
  • widthAnchor
  • heightAnchor
  • centerXAnchor
  • centerYAnchor
  • firstBaselineAnchor
  • lastBaselineAnchor

對於NSLayoutAnchor的一些經常使用屬性,經過其命名就能看出來其做用,這裏不作贅述,若是想了解更多請查閱Apple Developer NSLayoutAnchor

五、Auto Layout幾個更新約束的方法

  • setNeedsLayout: 告知頁面須要更新,可是不會馬上開始更新。執行後會馬上調用layoutSubviews

  • layoutIfNeeded: 告知頁面佈局馬上更新。因此通常都會和setNeedsLayout一塊兒使用。若是但願馬上生成新的frame須要調用此方法,利用這點通常佈局動畫能夠在更新佈局後直接使用這個方法讓動畫生效。

  • layoutSubviews: 更新子View約束

  • setNeedsUpdateConstraints:須要更新約束,可是不會馬上開始

  • updateConstraintsIfNeeded:馬上更新約束

  • updateConstraints:更新View約束

六、NSLayoutAnchor使用注意事項

一、在使用NSLayoutAnchor爲視圖添加約束時必定要先把translatesAutoresizingMaskIntoConstraints設置false
centerView.translatesAutoresizingMaskIntoConstraints = false
複製代碼
二、在使用safeAreaLayoutGuide適配iPhone X 等機型時要對iOS 11以前的系統作適配,不然會致使低版本系統上程序Crash
if #available(iOS 11.0, *) {
     tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
 } else {
     tableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
 }
複製代碼
三、設置約束後要將其激活,即設置isActivetrue
let centerX: NSLayoutConstraint = centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
centerX.isActive = true
複製代碼
四、leadingAnchor 不要和 leftAnchor混用
centerView.leadingAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
複製代碼
centerView.leftAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
複製代碼

以上2種寫法,在編譯時不會出現任何問題,可是在運行時就會報錯,並會致使程序Crash,官方的說法是:

While the NSLayoutAnchor class provides additional type checking, it is still possible to create 
invalid constraints. For example, the compiler allows you to constrain one view’s leadingAnchor
 with another view’s leftAnchor, since they are both NSLayoutXAxisAnchor instances. However, 
Auto Layout does not allow constraints that mix leading and trailing attributes with left or right 
attributes. As a result, this constraint crashes at runtime.
複製代碼

同理,trailingAnchorrightAnchor也不能混用。

五、如何刷新某個約束

      如我要修改一個UIView的寬度: 經過代碼添加約束,可把UIView的寬度設置類屬性,而後在須要的地方修改constant的參數,而後在刷新約束便可,代碼以下:

var centerView: UIView! 
 var centerWidth: NSLayoutConstraint! 

複製代碼
self.centerView = UIView.init()
view.addSubview(self.centerView)
self.centerView.backgroundColor = UIColor.red
self.centerView.translatesAutoresizingMaskIntoConstraints = false
self.centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
self.centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
self.centerWidth = self.centerView.widthAnchor.constraint(equalToConstant: 120)
self.centerWidth.isActive = true
self.centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
複製代碼
self.centerWidth.constant = 250
weak var weakSelf = self
UIView.animate(withDuration: 0.35, animations: {
   weakSelf?.centerView.superview?.layoutIfNeeded()
}) { (finished) in
            
}
複製代碼

效果以下:

layoutDemo5.gif

若是是xib或者storyboard,那就更簡單了,直接摁住鍵盤control鍵,拖到對應的類裏,而後在須要的地方修改約束並刷新便可。操做以下:

AutoLayoutdemo6.gif

六、設置寬高比

      在開發中,咱們會遇到一些需求要求根據UIView的寬高比來設置約束,如通常狀況下顯示視頻的寬高比是16:9,經過代碼設置寬高好比下:

centerView.heightAnchor.constraint(equalToConstant: 90).isActive = true
 centerView.widthAnchor.constraint(equalTo: centerView.heightAnchor, multiplier: 16 / 9).isActive = true
複製代碼

layoutDemo7.png

七、Auto Layout自適應UITableViewCell高度使用

一、 使用rowHeight設置高度

      通常狀況下,若是UITableView的每一個Cell高度是固定的咱們能夠直接指定一個值便可,若是沒有設置UITableView的高度,系統會默認設置rowHeight高度是44。

tableview.rowHeight = 44;
複製代碼

也能夠經過UITableViewDelegate的代理來設置UItableView的高度。

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 50
 }
複製代碼

若是經過手動計算每一個UItableViewCell的高度,也在這個代理中實現,經過計算返回每一個UItableViewCell的高度。

二、使用estimatedRowHeight設置高度

      UItableView繼承自UIScrollView,UIScrollView的滾動須要設置其contentSize後,而後根據自身的bounds、contentInset、contentOffset等屬性來計算出可滾動的長度。而UITableView在初始化時並不知道這些參數,只有在設置了delegatedataSource以後,根據建立的UITableViewCell的個數和加載的UITableViewCell的高度以後才能算出可滾動的長度。

在使用Auto Layout自適應UITableViewCell高度時應提早設置一個估算值,固然這個估算值越接近真實值越好。

tableView.rowHeight = UITableView.automaticDimension
 tableView.estimatedRowHeight = 200
複製代碼
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 200    
 }
複製代碼

Autolayoutdemo2.png

      如上圖所示:這個界面就是用Auto Layout + estimatedRowHeight完成自適應高度的,在添加約束時要按照從上到下的書訊設置每個UIView的頂部(top)到上一個的視圖底部的(bottom)距離,同時要計算UITableViewCell內部全部控件的高度。那麼問題來了,用戶發佈的內容詳情沒有獲得數據以前時沒辦法算出其高度的,此處能夠先給內容文字UILabel設置一個默認高度,而後讓其根據內容填充自動計算高度:

topicInfoLab.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true;
 topicInfoLab.font = UIFont.init(name: "Montserrat-SemiBold", size: 12)
topicInfoLab.numberOfLines = 0
複製代碼

若是用戶發佈內容沒有圖片,直接設置發佈內容UILabel距離UITableView距離底部的約束距離便可;

detailsLab.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true

複製代碼

若是用戶發佈的內容有圖片,那麼在計算出每張圖片的位置和大小以後,必定要給最後一張圖片設置距離UItableViewCell底部(bottom)的約束距離。

for(idx, obj) in imageArray.enumerated() {
//.....計算圖片的大小和位置
if idx == imageArray.count - 1 {
   //設置最後一張圖片距離底部的約束
   photo.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
 }
}
複製代碼

layoutDemo8.png

實現思路如上圖所示,具體實現的請看代碼

八、 Compression Resistance PriorityHugging Priority使用

Compression Resistance PriorityHugging Priority在實際使用中每每配合使用,分別處理在同義水平線上多個view之間內容過少和內容過多而形成的互相壓擠的狀況。

Hugging Priority的意思就是自包裹的優先級,優先級越高,則優先將尺寸按照控件的內容進行填充。

Compression Resistance Priority,意思是說當不夠顯示內容時,根據這個優先級進行切割。優先級越低,越容易被切掉。

ContentHuggingPriority 表示當前的UIView的內容不想被拉伸
ContentCompressionResistancePriority 表示當前的UIView的內容不想被收縮
默認狀況下: HuggingPriority = 250 默認狀況下: CompressionResistancePriority = 750

如設置2個UILabel的拉伸優先級可以使用代碼:

fristLab.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
secondLab.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
複製代碼

九、總結

      本文主要分享了蘋果Auto Layout的幾種實現方法和注意事項,對於Auto Layout在實際開發中的使用是採用純代碼、仍是xib + 代碼,仍是storyboard + 代碼,仍是xib + storyboard + 代碼的方式實現,主要看團隊的要求、我的的習慣,以及App的繁瑣程度。 對於Auto Layout在視圖上的使用,我的建議若是UI比較簡單或者單一的界面可以使用Auto Layout,若是UI的操做或刷新很複雜的界面,建議仍是frame + 手動佈局的方式。


本文demo,請戳這裏

友情連接:

深刻剖析Auto Layout,分析iOS各版本新增特性

Auto Layout 是怎麼進行自動佈局的,性能如何?

Apple Developer High Performance Auto Layout

Apple Develope NSLayoutConstraint

WWDC 2018 What's New in Cocoa Touch

相關文章
相關標籤/搜索