NSLayoutAnchor

引子

苹果发布iOS 6.0时,为开发者提供了视图布局的利器——Auto Layout,用于替代Frame-Based Layout,以轻松方便地达到不同尺寸屏幕上界面的兼容适配。

Auto Layout对应着一套constraint-based layout system(基于约束的布局系统),这套系统所使用的策略如下:

item1.attribute1 = multiplier × item2.attribute2 + constant

上面出现的=,表示的是等于,而非赋值。另外,其中的=,也可以是>=<=

对应地,有一个名为NSLayoutConstraint的类,用于构建相关的约束,其用于构建约束的方法如下:

  1. + constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
  2. + constraintsWithVisualFormat:options:metrics:views:

一眼看上去,就能发现,第一个方法正好与上面提到的策略是一致的。

第二个方法,使用了所谓的Visual Format Language,比起第一个方法,复杂难用程度直接上升了一个台阶。比如V:[topField]-10-[bottomField],其语法十分复杂,而且,改动视图对应的变量名称,也得修改这里面的内容,十分难用,因此,大多数开发者会选择使用第一种方法。

但很明显,如果视图中有很多约束,第一种方法用得多了,代码也是又臭又长,就显得不那么简洁明晰,因此也深受诟病,这就促使聪明的开发者创造出一些了不起的第三方类库,SnapKit出品的MasonrySnapKit就深受开发者的喜爱,已形成垄断,其它自动布局相关的第三方类库都在夹缝中生存。

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
// 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确实很大程度地减少了代码量,也更加明晰。

注意:
尽管提供了类型检查,但还是有可能创建出非法无效的约束。比如,由于leadingAnchorleftAnchor都是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)
/* 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完全一模一样。

不过,得使用下面的这些锚点:

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;// 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

使用示例:

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];

总述

比起MasonrySnapKitNSLayoutAnchor API还是有些小巫见大巫,在这两个优秀的第三方类库面前,苹果所提供的创建约束的API完全不能在开发者中流行起来,它们完全处在MasonrySnapKit大厦的地基之中,早已被开发者所唾弃、抛弃。


NSLayoutAnchor
https://daniate.github.io/2017/03/01/NSLayoutAnchor/
作者
Daniate
发布于
2017年3月1日
许可协议