引子 苹果发布iOS 6.0
时,为开发者提供了视图布局的利器——Auto Layout
,用于替代Frame-Based Layout
,以轻松方便地达到不同尺寸屏幕上界面的兼容适配。
Auto Layout
对应着一套constraint-based layout system
(基于约束的布局系统),这套系统所使用的策略如下:
item1.attribute1 = multiplier × item2.attribute2 + constant
上面出现的=
,表示的是等于
,而非赋值
。另外,其中的=
,也可以是>=
、<=
。
对应地,有一个名为NSLayoutConstraint
的类,用于构建相关的约束,其用于构建约束的方法如下:
+ constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
+ constraintsWithVisualFormat:options:metrics:views:
一眼看上去,就能发现,第一个方法正好与上面提到的策略是一致的。
第二个方法,使用了所谓的Visual Format Language
,比起第一个方法,复杂难用程度直接上升了一个台阶。比如V:[topField]-10-[bottomField]
,其语法十分复杂,而且,改动视图对应的变量名称,也得修改这里面的内容,十分难用,因此,大多数开发者会选择使用第一种方法。
但很明显,如果视图中有很多约束,第一种方法用得多了,代码也是又臭又长,就显得不那么简洁明晰,因此也深受诟病,这就促使聪明的开发者创造出一些了不起的第三方类库,SnapKit
出品的Masonry 、SnapKit 就深受开发者的喜爱,已形成垄断,其它自动布局相关的第三方类库都在夹缝中生存。
NSLayoutAnchor
简述苹果应该是了解到了相应的弊端,在发布iOS 9.0
时,又提供了NSLayoutAnchor
这样一个用于创建NSLayoutConstraint
的工厂类。
使用NSLayoutAnchor API
能让代码更简洁明晰,也更加易读,另外也提供了额外的类型检查,可以避免创建非法无效的约束。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 NSLayoutConstraint (item : subview, attribute : .Leading, relatedBy : .Equal, toItem : view, attribute : .LeadingMargin, multiplier : 1.0 , constant : 0.0 ).active = true NSLayoutConstraint (item : subview, attribute : .Trailing, relatedBy : .Equal, toItem : view, attribute : .TrailingMargin, multiplier : 1.0 , constant : 0.0 ).active = true let margins = view.layoutMarginsGuide subview.leadingAnchor.constraintEqualToAnchor (margins.leadingAnchor).active = true subview.trailingAnchor.constraintEqualToAnchor (margins.trailingAnchor).active = true
很明显,使用NSLayoutAnchor API
确实很大程度地减少了代码量,也更加明晰。
注意: 尽管提供了类型检查,但还是有可能创建出非法无效的约束。比如,由于leadingAnchor
和leftAnchor
都是NSLayoutXAxisAnchor
,因此编译器允许在它们之间形成约束,但Auto Layout
并不允许出现这样的混淆,因此,在运行时会出现崩溃。
在使用NSLayoutAnchor
时,并不是直接使用这个类,而是使用其具体的子类:
NSLayoutXAxisAnchor
用于创建水平约束
NSLayoutYAxisAnchor
用于创建竖直约束
NSLayoutDimension
用于创建与视图宽高相关的约束
在UIKit/UIView.h
中,可以看到以下内容,很明显,都是具体的子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @class NSLayoutXAxisAnchor ,NSLayoutYAxisAnchor ,NSLayoutDimension ;@interface UIView (UIViewLayoutConstraintCreation )@property (readonly , strong ) NSLayoutXAxisAnchor *leadingAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutXAxisAnchor *trailingAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutXAxisAnchor *leftAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutXAxisAnchor *rightAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutYAxisAnchor *topAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutYAxisAnchor *bottomAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutDimension *widthAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutDimension *heightAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutXAxisAnchor *centerXAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutYAxisAnchor *centerYAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutYAxisAnchor *firstBaselineAnchor NS_AVAILABLE_IOS (9 _0);@property (readonly , strong ) NSLayoutYAxisAnchor *lastBaselineAnchor NS_AVAILABLE_IOS (9 _0);@end
在低版本系统中使用NSLayoutAnchor
这样的API
我仿照NSLayoutAnchor API
写了DGLayoutAnchor ,可以在iOS 6.0
及更高版本中使用,其使用方式与NSLayoutAnchor
完全一模一样。
不过,得使用下面的这些锚点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @interface UIView (DGLayoutAnchor)@property (readonly, strong) DGLayoutXAxisAnchor *dg_leadingAnchor;@property (readonly, strong) DGLayoutXAxisAnchor *dg_trailingAnchor;@property (readonly, strong) DGLayoutXAxisAnchor *dg_leftAnchor;@property (readonly, strong) DGLayoutXAxisAnchor *dg_rightAnchor;@property (readonly, strong) DGLayoutYAxisAnchor *dg_topAnchor;@property (readonly, strong) DGLayoutYAxisAnchor *dg_bottomAnchor;@property (readonly, strong) DGLayoutDimension *dg_widthAnchor;@property (readonly, strong) DGLayoutDimension *dg_heightAnchor;@property (readonly, strong) DGLayoutXAxisAnchor *dg_centerXAnchor;@property (readonly, strong) DGLayoutYAxisAnchor *dg_centerYAnchor;@property (readonly, strong) DGLayoutYAxisAnchor *dg_firstBaselineAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutYAxisAnchor *dg_lastBaselineAnchor;@property (readonly, strong) DGLayoutYAxisAnchor *dg_baselineAnchor;@property (readonly, strong) DGLayoutXAxisAnchor *dg_leftMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutXAxisAnchor *dg_rightMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutYAxisAnchor *dg_topMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutYAxisAnchor *dg_bottomMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutXAxisAnchor *dg_leadingMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutXAxisAnchor *dg_trailingMarginAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutXAxisAnchor *dg_centerXWithinMarginsAnchor NS_AVAILABLE_IOS (8 _0);@property (readonly, strong) DGLayoutYAxisAnchor *dg_centerYWithinMarginsAnchor NS_AVAILABLE_IOS (8 _0);@end @interface UIViewController (DGLayoutAnchor)@property (readonly, strong) DGLayoutGuideAnchor *dg_topLayoutGuideTopAnchor;@property (readonly, strong) DGLayoutGuideAnchor *dg_topLayoutGuideBottomAnchor;@property (readonly, strong) DGLayoutGuideAnchor *dg_bottomLayoutGuideTopAnchor;@property (readonly, strong) DGLayoutGuideAnchor *dg_bottomLayoutGuideBottomAnchor;@end
使用示例:
1 2 3 4 NSLayoutConstraint *lc1 = [v.dg_topAnchor equalTo:self .dg_topLayoutGuideBottomAnchor constant:10 ];NSLayoutConstraint *lc2 = [v.dg_leadingAnchor equalTo:self .view.dg_leadingMarginAnchor];NSLayoutConstraint *lc3 = [self .dg_bottomLayoutGuideTopAnchor equalTo:v.dg_bottomAnchor constant:10 ];NSLayoutConstraint *lc4 = [v.dg_trailingAnchor equalTo:self .view.dg_trailingMarginAnchor];
总述 比起Masonry 、SnapKit ,NSLayoutAnchor API
还是有些小巫见大巫,在这两个优秀的第三方类库面前,苹果所提供的创建约束的API
完全不能在开发者中流行起来,它们完全处在Masonry 、SnapKit 大厦的地基之中,早已被开发者所唾弃、抛弃。