引子
苹果发布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
能让代码更简洁明晰,也更加易读,另外也提供了额外的类型检查,可以避免创建非法无效的约束。例如:
// Creating constraints using NSLayoutConstraint
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
// Creating the same constraints using Layout Anchors
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
中,可以看到以下内容,很明显,都是具体的子类:
@class NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension;
@interface UIView (UIViewLayoutConstraintCreation)
/* Constraint creation conveniences. See NSLayoutAnchor.h for details.
*/
@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
完全一模一样。
不过,得使用下面的这些锚点:
@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;// same as `dg_lastBaselineAnchor`
@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
使用示例:
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大厦的地基之中,早已被开发者所唾弃、抛弃。