先前写到的一篇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再回到约束变化这样的循环机制。
触发约束变化包括
这个Engine遇到约束变化会重新计算layout,获取新值后会call它的superview.setNeedsLayout()
在这个时候主要是做些容错处理,更新约束有些没有确定或者缺失布局声明的视图会在这里处理。接着从上而下调用layoutSubviews()来确定视图各个子视图的位置,这个过程实际上就是将subview的frame从layout engine里拷贝出来。这里要注意重写layoutSubviews()或者执行类似layoutIfNeeded这样可能会立刻唤起layoutSubviews()的方法,如果要这样做需要注意手动处理的这个地方自己的子视图布局的树状关系是否合理。
Auto Layout你的视图层级里所有视图通过放置在它们里面的约束来动态计算的它们的大小和位置。一般控件需要四个约束决定位置大小,如果定义了intrinsicContentSize的比如UILabel只需要两个约束即可。
view1.attribute1 = mutiplier * view2.attribute2 + constant
redButton.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]];
无论使用哪种方法创建约束都是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
创建这种字符串时需要注意一下几点:
表达布局约束的规则可以使用一些简单的数学术语,如下表 | 类型 | 描述 | 值 | | :------------ |:---------------| :-----| | 属性 | 视图位置 | 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
使用Auto Layout的view会在viewDidLayoutSubviews或-layoutSubview调用super转换成具有正确显示的frame值。
Auto Layout以下几种情况会出错
Github地址: https://github.com/SnapKit/Masonry
Github地址: https://github.com/robb/Cartography
可以参看我上篇文章《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
苹果在这个版本引入Auto Layout,具备了所有核心功能。
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-[view1]" options:0 metrics:nil views:view2];
- (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的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),
苹果一直希望能够让更多的人来用Auto Layout,除了弄出一个VFL现在又弄出一个不需要约束的方法,使用Stack view使大家使用Auto Layout时不用触碰到约束,官方口号是“Start with Stack View, use constraints as needed”。 更多细节可以查看官方介绍:UIKit Framework Reference UIStackView Class Reference https://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的FDStackView https://github.com/forkingdog/FDStackView ,利用运行时替换元素的方法来支持iOS6+系统。
新增这个API能够让约束的声明更加清晰,还能够通过静态类型检查确保约束的正常工作。具体可以查看官方文档 https://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutAnchor_ClassReference/
NSLayoutConstraint *constraint = [view1.leadingAnchor constraintEqualToAnchor:view2.topAnchor];