Autolayout和Masrony的使用

#iOS界面布局

目前来说iOS目前几种主流的UI布局:

  • Frame布局
  • AutoLayout
    • 使用代码实现AutoLayout
    • 使用xib或者StoryBoard
      在代码可读性上 frame > 使用代码实现AutoLayout > 使用xib或者StoryBoard
      在开发效率上 使用xib或者StoryBoard > 使用代码实现AutoLayout > Frame布局

AutoLayout是通常是通过定义一系列的约束(constrains)来进行定的。和Frame定位一样,它同样需要你提供位置和尺寸信息,但是和Frame不同的是,它不是让你直接提供x,y,width,height,而是通过你给的约束来推断出相应的尺寸和位置。

AutoLayout本质

一次函数:

y = ax + b

item1.attribute1 = multiplier × item2.attribute2 + constant

看一段原生的代码实现AutoLayout(NSLayoutConstraint)

VFL是这么写的:

1
2
3
4
5
6
7
NSDictionary *viewsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:label1,@"label1",label2,@"label2",label3,@"label3",label4,@"label4", nil];
NSDictionary *metrics = [NSDictionary dictionaryWithObject:@(88) forKey:@"labelHeight"];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label1(labelHeight)]-[label2(labelHeight)]-[label3(labelHeight)]-[label4(labelHeight)]-(>=10)-|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:viewsDictionary];
[view addConstraints:constraints];

还有一种写法:

1
2
3
4
5
6
7
8
9
10
view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint * constraint = [NSLayoutConstraint
constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:10];
[view addConstraint:constraint];

苹果好像也发现这样写有点尴尬,于是在iOS8和iOS9中对autolayout的api进行改动:

1
2
3
4
5
6
7
8
9
//iOS8
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeCenterX multiplier:1 constant:10];
//iOS9
view.translatesAutoresizingMaskIntoConstraints = NO;
[view.widthAnchor constraintEqualToConstant:100];
[view.heightAnchor constraintEqualToConstant:100];
[view.centerXAnchor constraintEqualToAnchor:view1.centerXAnchor];
[view.centerYAnchor constraintEqualToAnchor:view1.centerYAnchor];

一个view的位置确定,正常情况下需要来自内部或者外部的4条约束才能确定其位置。所以使用原生代码实现起来太过繁琐,所以为了解决这个问题,将NSLayoutConstraint简化封装使用就非常有必要了,开源项目中有很多项目已经很成熟了,比如Masonry和purelayout等,下面简单介绍Masonry。

Masonry的介绍

Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束。

使用Masonry完成上面的代码

1
2
3
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(10));
}];

当然一条约束什么都干不了,完成一个View的布局,至少有4个来自内部或者外部的约束,比如:

1
2
3
4
5
6
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(10));
make.left.equalTo(@(10));
make.bottom.equalTo(@(-10));
make.right.equalTo(@(-10));
}];

Masonry最重要的4个方法

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
31
32
33
34
35
36
37
38
39
40
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(MASConstraintMakerConfigBlock)block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(MASConstraintMakerConfigBlock)block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* All constraints previously installed for the view will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(MASConstraintMakerConfigBlock)block;

MASViewConstraint的对象链式调用

如何实现链式调用

Masonry中大量的使用了链式调用的,比如

1
view.top.left.equalTo(superView).offset(10);

上面的这种方式就是链式调用,而且像equalTo(superView)这种形式也不是Objective-C中函数调用的方式,在Objective-C中是通过[]来调用函数的,而此处使用了()。接下来讲分析这种链式的调用是如何实现的。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ClassB;
interface ClassA : NSObject
// 1. 定义一些 block 属性
property(nonatomic, readonly) ClassA *(^aaa)(BOOL enable);
property(nonatomic, readonly) ClassA *(^bbb)(NSString* str);
@property(nonatomic, readonly) ClassB *(^ccc)(NSString* str);
@implement ClassA
// 2. 实现这些 block 方法,block 返回值类型很关键,影响着下一个链式
- (ClassA *(^)(BOOL))aaa
{
return ^(BOOL enable) {
//code
if (enable) {
NSLog(@"ClassA yes");
} else {
NSLog(@"ClassA no");
}
return self;
}
}
- (ClassA *(^)(NSString *))bbb
{
return ^(NSString *str)) {
//code
NSLog(@"%@", str);
return self;
}
}
// 这里返回了ClassB的一个实例,于是后面就可以继续链式 ClassB 的 block 方法
// 见下面例子 .ccc(@"Objective-C").ddd(NO)
- (ClassB * (^)(NSString *))ccc
{
return ^(NSString *str) {
//code
NSLog(@"%@", str);
ClassB* b = [[ClassB alloc] initWithString:ccc];
return b;
}
}
//------------------------------------------
@interface ClassB : NSObject
@property(nonatomic, readonly) ClassB *(^ddd)(BOOL enable);
- (id)initWithString:(NSString *)str;
@implement ClassB
- (ClassB *(^)(BOOL))ddd
{
return ^(BOOL enable) {
//code
if (enable) {
NSLog(@"ClassB yes");
} else {
NSLog(@"ClassB no");
}
return self;
}
}
// 最后我们可以这样做
id a = [ClassA new];
a.aaa(YES).bbb(@"HelloWorld!").ccc(@"Objective-C").ddd(NO)

offset(10)这种调用方式是如何实现的呢?

1
2
3
4
5
6
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}

不难发现,offset是一个getter方法的名,offset函数的返回值是一个匿名Block, 也就是offset后边的()。这个匿名闭包有一个CGFloat的参数,为了支持链式调用该匿名闭包返回一个MASConstraint的对象。

优化UITableViewCell高度计算的那些事:

https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/