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

2022-01-01 二進位小鎮

地址:http://www.starming.com/index.php?v=index&view=84

作者:Starming星光社

先前寫到的一篇Masonry心得文章裡已經提到了很多AutoLayout相關的知識,這篇我會更加詳細的對其知識要點進行分析和整理。

一般大家都會認為Auto Layout這個東西是蘋果自己搞出來的,其實不然,早在1997年Alan Borning, Kim Marriott, Peter Stuckey等人就發布了《Solving Linear Arithmetic Constraints for User Interface Applications》論文(論文地址:http://constraints.cs.washington.edu/solvers/uist97.html)提出了在解決布局問題的Cassowary constraint-solving算法實現,並且將代碼發布在他們搭建的Cassowary網站上http://constraints.cs.washington.edu/cassowary/。後來更多開發者用各種語言來寫Cassowary,比如說pybee用python寫的https://github.com/pybee/cassowary。自從它發布以來JavaScript,.NET,JAVA,Smalltall和C++都有相應的庫。2011年蘋果將這個算法運用到了自家的布局引擎中,美其名曰Auto Layout。

Cassowary是個解析工具包,能夠有效解析線性等式系統和線性不等式系統,用戶的界面中總是會出現不等關係和相等關係,Cassowary開發了一種規則系統可以通過約束來描述視圖間關係。約束就是規則,能夠表示出一個視圖相對於另一個視圖的位置。

進入下面主題前可以先介紹下加入Auto Layout的生命周期。在得到自己的layout之前Layout Engine會將Views,約束,Priorities(優先級),instrinsicContentSize(主要是UILabel,UIImageView等)通過計算轉換成最終的效果。在Layout Engine裡會有約束變化到Deferred Layout Pass再到應用Run Loop再回到約束變化這樣的循環機制。

觸發約束變化包括

Activating或Deactivating

設置constant或priority

添加和刪除視圖

這個Engine遇到約束變化會重新計算layout,獲取新值後會call它的superview.setNeedsLayout()

在這個時候主要是做些容錯處理,更新約束有些沒有確定或者缺失布局聲明的視圖會在這裡處理。接著從上而下調用layoutSubviews()來確定視圖各個子視圖的位置,這個過程實際上就是將subview的frame從layout engine裡拷貝出來。這裡要注意重寫layoutSubviews()或者執行類似layoutIfNeeded這樣可能會立刻喚起layoutSubviews()的方法,如果要這樣做需要注意手動處理的這個地方自己的子視圖布局的樹狀關係是否合理。

Auto Layout你的視圖層級裡所有視圖通過放置在它們裡面的約束來動態計算的它們的大小和位置。一般控制項需要四個約束決定位置大小,如果定義了intrinsicContentSize的比如UILabel只需要兩個約束即可。

view1.attribute1 = mutiplier * view2.attribute2 + constantredButton.left = 1.0 * yellowLabel.right + 10.0 //紅色按鈕的左側距離黃色label有10個point

使用NSLayoutConstraint類(最低支持iOS6)添加約束。NSLayoutConstraint官方參考:https://developer.apple.com/library/prerelease/ios/documentation/AppKit/Reference/NSLayoutConstraint_Class/index.html

[NSLayoutContraint constraintWithItem:view1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-5]

把約束用約束中兩個view的共同父視圖或者兩視圖中層次高視圖的- (void)addConstraint:(NSLayoutConstraint *)constraint方法將約束添加進去。

先舉個簡單的例子並排兩個view添加約束

[NSLayoutConstraint constraintWithVisualFormat:@「[view1]-[view2]" options:0 metrics:nil views:viewsDictionary;

viewDictionary可以通過NSDictionaryOfVariableBindings方法得到

UIView *view1 = [[UIView alloc] init];

UIView *view2 = [[UIView alloc] init];

viewsDictionary = NSDictionaryOfVariableBindings(view1,view2);

可以給這個位掩碼傳入NSLayoutFormatAlignAllTop使它們頂部對齊,這個值的默認值是NSLayoutFormatDirectionLeadingToTrailing從左到右。可以使用NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom 表示兩個視圖的頂部和底部約束相同。

這個參數作用是替換VFL語句中對應的值

CGRect viewFrame = CGRectMake(50, 50, 100, 100);

NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2);

NSDictionary *metrics = @{@"left":

@(CGRectGetMinX(viewFrame)),

@"top": @(CGRectGetMinY(viewFrame)),

@"width": @(CGRectGetWidth(viewFrame)),

@"height": @(CGRectGetHeight(viewFrame))};

[view1 addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];

使用NSDictionaryOfVariableBindings(...)快速創建

NSNumber *left = @50;NSNumber *top = @50;

NSNumber *width = @100;NSNumber *height = @100;

NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2);NSDictionary *metrics = NSDictionaryOfVariableBindings(left, top, width, height);

[view1 addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];

[view1(50)]-10-[view2(100)] 表示view1寬50,view2寬100,間隔10

[view1(>=50@750)] 表示view1寬度大於50,約束條件優先級為750(優先級越大優先執行該約束,最大1000)

V:[view1][view2(==view1)] 表示按照豎直排,上面是view1下面是一個和它一樣大的view2

H:|-[view1]-[view2]-[view3(>=20)]-| 表示按照水平排列,|表示父視圖,各個視圖之間按照默認寬度來排列

無論使用哪種方法創建約束都是NSLayoutConstraint類的成員,每個約束都會在一個Objective-C對象中存儲y = mx + b規則,然後通過Auto Layout引擎來表達該規則,VFL也不例外。VFL由一個描述布局的文字字符串組成,文本會指出間隔,不等量和優先級。官方對其的介紹:Visual Format Language https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html

標準間隔:[button]-[textField]

寬約束:[button(>=50)]

與父視圖的關係:|-50-[purpleBox]-50-|

垂直布局:V:[topField]-10-[bottomField]

Flush Views:[maroonView][buleView]

權重:[button(100@20)]

等寬:[button(==button2)]

Multiple Predicates:[flexibleButton(>=70,<=100)]

注意事項

創建這種字符串時需要注意一下幾點:

H:和V:每次都使用一個。

視圖變量名出現在方括號中,例如[view]。

字符串中順序是按照從頂到底,從左到右

視圖間隔以數字常量出現,例如-10-。

|表示父視圖

圓括號將

表達布局約束的規則可以使用一些簡單的數學術語,如下表 | 類型 | 描述 | 值 | | :-- |:| :| | 屬性 | 視圖位置 | NSLayoutAttributeLeft, NSLayoutAttributeRight, NSLayoutAttributeTop, NSLayoutAttributeBottom | | 屬性 | 視圖前面後面 | NSLayoutAttributeLeading, NSLayoutAttributeTrailing | | 屬性 | 視圖的寬度和高度 | NSLayoutAttributeWidth, NSLayoutAttributeHeight | | 屬性 | 視圖中心 | NSLayoutAttributeCenterX, NSLayoutAttributeCenterY | | 屬性 | 視圖的基線,在視圖底部上方放置文字的地方 | NSLayoutAttributeBaseline | | 屬性 | 佔位符,在與另一個約束的關係中沒有用到某個屬性時可以使用佔位符 | NSLayoutAttributeNotAnAttribute | | 關係 | 允許將屬性通過等式和不等式相互關聯 | NSLayoutRelationLessThanOrEqual, NSLayoutRelationEqual, NSLayoutRelationGreaterThanOrEqual | | 數學運算 | 每個約束的乘數和相加性常數 | CGFloat值 |

約束引用兩視圖時,這兩個視圖需要屬於同一個視圖層次結構,對於引用兩個視圖的約束只有兩個情況是允許的。第一種是一個視圖是另一個視圖的父視圖,第二個情況是兩個視圖在一個窗口下有一個非nil的共同父視圖。

哪個約束優先級高會先滿足其約束,系統內置優先級枚舉值UILayoutPriority

enum {

UILayoutPriorityRequired = 1000, //默認的優先級,意味著默認約束一旦衝突就會crash

UILayoutPriorityDefaultHigh = 750,

UILayoutPriorityDefaultLow = 250,

UILayoutPriorityFittingSizeLevel = 50,

};typedef float UILayoutPriority;

updateConstraints -> layoutSubViews -> drawRect

viewDidLayoutSubviews,-layoutSubviews

使用Auto Layout的view會在viewDidLayoutSubviews或-layoutSubview調用super轉換成具有正確顯示的frame值。

View的改變會調用哪些方法

改變frame.origin不會掉用layoutSubviews

改變frame.size會使 superVIew的layoutSubviews調用

改變bounds.origin和bounds.size都會調用superView和自己view的layoutSubviews方法

Auto Layout的Debug

Auto Layout以下幾種情況會出錯

Unsatisfiable Layouts:約束衝突,同一時刻約束沒法同時滿足。系統發現時會先檢測那些衝突的約束,然後會一直拆掉衝突的約束再檢查布局直到找到合適的布局,最後日誌會將衝突的約束和拆掉的約束列印在控制臺上。

Ambiguous Layouts:約束有缺失,比如說位置或者大小沒有全指定到。還有種情況就是兩個衝突的約束的權重是一樣的就會崩。

Logical Errors:布局中的邏輯錯誤。 ## Debugger

po [[UIWindow keyWindow] _autolayoutTrace] ## 參考 參考官方文檔:https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/TypesofErrors.html

Masonry

Github地址:https://github.com/SnapKit/Masonry

Cartography

Github地址:https://github.com/robb/Cartography

Masonry

可以參看我上篇文章《AutoLayout框架Masonry使用心得》:http://www.starming.com/index.php?v=index&view=81

完整記錄可以到官方網站進行核對和查找:What’s New in iOS https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Introduction/Introduction.html

iOS6

蘋果在這個版本引入Auto Layout,具備了所有核心功能。

iOS7

NavigationBar,TabBar和ToolBar的translucent屬性默認為YES,當前ViewController的高度是整個屏幕的高度,為了確保不被這些Bar覆蓋可以在布局中使用topLayoutGuide和bottomLayoutGuide屬性。

[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-[view1]" options:0 metrics:nil views:view2];

iOS8

Self Sizing Cells http://www.appcoda.com/self-sizing-cells/

UIViewController新增兩個方法,用來處理UITraitEnvironment協議,UIKit裡有UIScreen,UIViewController,UIView和UIPresentationController支持這個協議,當視圖traitCollection改變時UIViewController時可以捕獲到這個消息進行處理的。

- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);

- (void)systemLayoutFittingSizeDidChangeForChildContentContainer:(id )container NS_AVAILABLE_IOS(8_0);

- (CGSize)sizeForChildContentContainer:(id )container withParentContainerSize:(CGSize)parentSize NS_AVAILABLE_IOS(8_0);

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator NS_AVAILABLE_IOS(8_0);

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id )coordinator NS_AVAILABLE_IOS(8_0);

UIView的Margin新增了3個API,NSLayoutMargins可以定義view之間的距離,這個只對Auto Layout有效,並且默認值為{8,8,8,8}。NSLayoutAttribute的枚舉值也有相應的更新

//UIView的3個Margin相關API@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);

@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0);

- (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);//NSLayoutAttribute的枚舉值更新NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

iOS9

UIStackView

蘋果一直希望能夠讓更多的人來用Auto Layout,除了弄出一個VFL現在又弄出一個不需要約束的方法,使用Stack view使大家使用Auto Layout時不用觸碰到約束,官方口號是「Start with Stack View, use constraints as needed」。 更多細節可以查看官方介紹:UIKit Framework Reference UIStackView Class Referencehttps://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutWithoutConstraints.html

Stack Views :https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html

Stack View提供了更加簡便的自動布局方法比如Alignment的Fill,Leading,Center,Trailing。Distribution的Fill,Fill Equally,Fill Proportionally,Equal Spacing。

如果希望在iOS9之前的系統也能夠使用Stack view可以用sunnyxx的FDStackViewhttps://github.com/forkingdog/FDStackView,利用運行時替換元素的方法來支持iOS6+系統。

NSLayoutAnchorAPI

新增這個API能夠讓約束的聲明更加清晰,還能夠通過靜態類型檢查確保約束的正常工作。具體可以查看官方文檔https://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutAnchor_ClassReference/

NSLayoutConstraint *constraint = [view1.leadingAnchor constraintEqualToAnchor:view2.topAnchor];

無共同父視圖的視圖之間相互添加約束會有問題。

調用了setNeedsLayout後不能通過frame改變視圖和控制項

官方文檔

Auto Layout Guide

https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/index.html

UIScrollView And Autolayout

https://developer.apple.com/library/ios/technotes/tn2154/_index.html

IB中使用Auto Layout:Auto Layout Help

https://developer.apple.com/library/ios/recipes/xcode_help-IB_auto_layout/_index.html

What’s New in iOS

https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Introduction/Introduction.html

WWDC視頻

WWDC 2012: Introduction to Auto Layout for iOS and OS X
https://developer.apple.com/videos/wwdc/2012/?id=202

WWDC 2012: Best Practices for Mastering Auto Layout
https://developer.apple.com/videos/wwdc/2012/?id=228

WWDC 2012: Auto Layout by Example
https://developer.apple.com/videos/wwdc/2012/?id=232

WWDC 2013: Taking Control of Auto Layout in Xcode 5
https://developer.apple.com/videos/wwdc/2013/?id=406

WWDC 2015: Mysteries of Auto Layout, Part 1 內容包含了Auto Layout更高封裝stack view的介紹

https://developer.apple.com/videos/play/wwdc2015-218/

WWDC 2015: Mysteries of Auto Layout, Part 2 包含Auto Layout生命周期和調試Auto Layout的一些方法介紹

https://developer.apple.com/videos/play/wwdc2015-219/

youyisiaApp

1. 最新iOS開發視頻教程共享;

2. iOS開發技術大牛文章推送;

3. 移動開發最新行業資訊推送;

4. App產品運營知識推送;

iOS 開發Vip交流群

加微信:yanzy310 申請加入

有意思啊

相關焦點

  • 使用 Auto Layout 的典型痛點和技巧
    官方文檔:Auto Layout Guide 文檔地址:https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853-CH7-SW1
  • Masonry介紹與使用實踐:快速上手Autolayout
    提示:點擊上方"CocoaChina"↑免費訂閱前言MagicNumber -> autoresizingMask -> autolayout
  • Auto Layout In iOS Developing Part 2
    」3.Adding a LabelNow that you have some ideas about auto layout and the preview feature, let's add a label to the lower-right part of the view and
  • IOS開發之Autolayout自動布局
    經常編寫大量的坐標計算代碼為了保證在3.5 inch和4.0 inch屏幕上都能有完美的UI界面效果,有時還需要分別為2種屏幕編寫不同的坐標計算代碼(即傳說中的「屏幕適配」)2、什麼是AutolayoutAutolayout是一種「自動布局」技術,專門用來布局UI界面的
  • 深入剖析三種SVC雙數據中心虛擬化存儲解決方案的特性(三):SVC Local VDM+SVC PPRC
    具有什麼特性?前兩天剖析了SVC Stretch Cluster(點擊回顧)、SVC HyperSwap(點擊回顧)。SVC Local VDM即本地SVC VDISK MIRROR,跟Stretched Cluster方案採用了類似的存儲複製技術,只不過是本地非跨站點的VDM,又跟SVC HyperSwap方案採用了相同的SVC PPRC遠程複製和Consistency一致性組技術,而且對SVC的版本沒有很高的要求,SVC 6.4版本就可以實現,可以說是另外兩種技術的爸爸,另外兩種技術均是由它演變升級而來
  • iOS進階指南之Autolayout實用技巧
    顧名思義,Autolayout = 自動+布局,也就是當你設置好一定的約束之後,系統會幫你處理布局的細節。那麼,在不那麼自動的年代,我們用的是什麼?我們用的是Frame布局。那麼,先來討論一下Frame布局有哪些問題?舉個簡單的例子好了。如圖:代碼如下。
  • 一文徹底搞懂 Design 設計的 CoordinatorLayout 和 AppbarLayout 聯動
    現在,讓我們更進一步地挖掘下 CoordinatorLayout, 深入了解一下隱藏在表象背後的神秘細節。        實際上, CoordinatorLayout 本身並沒有做過多工作,實現交互行為的主要幕後推手是 CoordinatorLayout 的內部類—— Behavior。
  • 從 Auto Layout 的布局算法談性能
    ▲點擊上方「CocoaChina」關注即可免費學習iOS開發原文連結:http://draveness.me/layout-performance/這篇文章主要從 iOS 中影響性能的另一大殺手,萬惡之源 Auto Layout(自動布局)來分析如何對
  • 【Android築基】深入理解LayoutInflater.inflate()方法
    先按照一般的情況來分析,有助於解決普遍的問題。和 layout_gravity 參數。1.2 根節點不是 merge 時,第二組取值情況分析根節點是否是 merge取值組合ViewGroup rootboolean attachToRoot否第二組notNulltrue我們直接從第 24 行開始,因為之前的代碼流程和第一組取值情況是一模一樣的。
  • Hulu大數據團隊帶你深入學習Hadoop
    本課程以目前主流的,最新Hadoop穩定版2.7.x為基礎,同時兼介紹3.0版本新增特性及使用,深入淺出地介紹Hadoop大數據技術體系的原理、內幕及案例實踐, 內容包括大數據收集、存儲、分布式資源管理以及各類主要計算引擎,具體包括數據收集組件Flume、分布式文件系統HDFS,分布式資源管理系統YARN以及分布式計算引擎MapReduce、Hive和Presto
  • Android:FlexboxLayout你值得擁有的流布局助力
    作為一個有好奇心的工程獅,當然第一時間就去試了試手,效果非常贊,因此這篇文章就介紹一下它的用法和最新版添加的一些特性(支持集成RecyclerView),Github地址:https://github.com/google/flexbox-layout 。
  • 前端周報:W3C 收到 CSS 工作組提案;深入探索 iOS 漏洞利用鏈;Angular 9 的新特性;
    【深入探索 iOS 漏洞利用鏈】:Google 的 Project Zero 團隊查找出 iOS 的漏洞關係鏈,本次測試涵蓋 iOS 10 到最新版本 iOS 12 的所有版本。測試發現 14 個漏洞,包括 0-days 漏洞。
  • 從 Chrome 源碼看瀏覽器如何 layout 布局
    width的大小是50,類型是百分比,而margin值是0,類型是auto,這兩種都不能直接用來畫的。所以需要通過layout計算出具體的數字。blink會檢測兩邊是不是都為auto,如果是的話就認為是居中:// CSS 2.1: "If both 'margin-left' and 'margin-right' are 'auto', their used// values are equal.
  • Android中LayoutAnimation的分析(三)
    ps:源碼是基於 android api 27 來分析的。我們繼續Android中LayoutAnimation的分析(二)這篇文章分析,在Android中LayoutAnimation的分析(二)這篇文章中,我們還漏分析了一些內容,那就是屬性動畫的解析,我們看 LayoutAnimation-Controller 中 LayoutAnimationController(Context context, AttributeSet attrs)
  • 一步步分析知乎中CoordinatorLayout的實現
    完整的拆包分析知乎和仿寫知乎包括 MainActivity, 首頁回答的 ListFragment, 和點擊首頁回答打開的回答詳情的 DetailFragment (gif演示的實現)控制項效果圖如下所示:
  • ConstraintLayout2.0進階之路-歡迎新同學
    ConstraintLayout2.0針對布局來說,主要新增了兩類東西,一個是新增了VirtualLayouts,用於將一組View進行關聯管理,並賦予定義的能力,另一個是放開了ConstraintHelper的限制。國際慣例,官網鎮樓。
  • Android FlexboxLayout 聰明的UI布局
    二者之間的重要差異在於 FlexboxLayout 具有 「換行」 的特性。同時 FlexboxLayout 還為 RecycleView 提供了管理器 FlexboxLayoutManager,使得 FlexboxLayout 更加強大了。
  • ConstraintLayout的崛起之路
    為了防止約束失效,在 1.1.0 版本中新增了app:layout_constrainedWidth="true"屬性。       app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf="parent"/>新增了如下屬性:    app:layout_constraintDimensionRatio="H,16:9"
  • MotionLayout系列之配合布局CoordinatorLayout, DrawerLayout, ViewPager使用
    ><android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http
  • ConstraintLayout和MotionLayout使用進階
    app:layout_constraintEnd_toEndOf="parent"      app:layout_constraintStart_toStartOf="parent"      />2、這時候TextView會水平居中,我們需要添加app:layout_constraintHorizontal_bias="0"