地址: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 申請加入
有意思啊