前言
说到iOS
自动布局,有很多的解决办法。有的人使用xib/storyboard
自动布局,也有人使用frame
来适配。对于前者,笔者并不喜欢,也不支持。对于后者,更是麻烦,到处计算高度、宽度等,千万大量代码的冗余,对维护和开发的效率都很低。
笔者在这里介绍纯代码自动布局的第三方库:Masonry
。这个库使用率相当高,在全世界都有大量的开发者在使用,其star
数量也是相当高的。
效果图
本节详解Masonry
的循环创建视图,并且可以展开与收缩的用法,先看看效果图:
当我们点击某一行时,可以展开:
核心代码
看下代码:
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
@interface ScrollViewComplexController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableArray *expandStates;
@end
@implementation ScrollViewComplexController
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = NO;
[self.view addSubview:self.scrollView];
self.scrollView.backgroundColor = [UIColor lightGrayColor];
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
UILabel *lastLabel = nil;
for (NSUInteger i = 0; i < 10; ++i) {
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0;
label.layer.borderColor = [UIColor greenColor].CGColor;
label.layer.borderWidth = 2.0;
label.text = [self randomText];
label.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[label addGestureRecognizer:tap];
// We must preferredMaxLayoutWidth property for adapting to iOS6.0
label.preferredMaxLayoutWidth = screenWidth - 30;
label.textAlignment = NSTextAlignmentLeft;
label.textColor = [self randomColor];
[self.scrollView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.right.mas_equalTo(self.view).offset(-15);
if (lastLabel) {
make.top.mas_equalTo(lastLabel.mas_bottom).offset(20);
} else {
make.top.mas_equalTo(self.scrollView).offset(20);
}
make.height.mas_equalTo(40);
}];
lastLabel = label;
[self.expandStates addObject:[@[label, @(NO)] mutableCopy]];
}
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
// 让scrollview的contentSize随着内容的增多而变化
make.bottom.mas_equalTo(lastLabel.mas_bottom).offset(20);
}];
}
- (NSMutableArray *)expandStates {
if (_expandStates == nil) {
_expandStates = [[NSMutableArray alloc] init];
}
return _expandStates;
}
- (UIColor *)randomColor {
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
}
- (NSString *)randomText {
CGFloat length = arc4random() % 150 + 5;
NSMutableString *str = [[NSMutableString alloc] init];
for (NSUInteger i = 0; i < length; ++i) {
[str appendString:@"测试数据很长,"];
}
return str;
}
- (void)onTap:(UITapGestureRecognizer *)sender {
UIView *tapView = sender.view;
NSUInteger index = 0;
for (NSMutableArray *array in self.expandStates) {
UILabel *view = [array firstObject];
if (view == tapView) {
NSNumber *state = [array lastObject];
if ([state boolValue] == YES) {
[view mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(40);
}];
} else {
UIView *preView = nil;
UIView *nextView = nil;
if (index - 1 < self.expandStates.count && index >= 1) {
preView = [[self.expandStates objectAtIndex:index - 1] firstObject];
}
if (index + 1 < self.expandStates.count) {
nextView = [[self.expandStates objectAtIndex:index + 1] firstObject];
}
[view mas_remakeConstraints:^(MASConstraintMaker *make) {
if (preView) {
make.top.mas_equalTo(preView.mas_bottom).offset(20);
} else {
make.top.mas_equalTo(20);
}
make.left.mas_equalTo(15);
make.right.mas_equalTo(self.view).offset(-15);
}];
if (nextView) {
[nextView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(view.mas_bottom).offset(20);
}];
}
}
[array replaceObjectAtIndex:1 withObject:@(!state.boolValue)];
[self.view setNeedsUpdateConstraints];
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.35 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
break;
}
index++;
}
}
@end
|
讲解
当我们要收起的时候,只是简单地设置其高度的约束为40
,但是当我们要展开时,实现起来就相对麻烦了。因为我们需要重新添加约束,要重新给所点击的视图添加约束,就需要知道前一个依赖视图和后一个依赖视图的约束,以便将相关联的都更新约束。
当我们更新所点击的视图时,我们通过判断是否有前一个依赖视图来设置顶部约束:
1
2
3
4
5
6
7
|
if (preView) {
make.top.mas_equalTo(preView.mas_bottom).offset(20);
} else {
make.top.mas_equalTo(20);
}
|
除了这个之外,我们也需要更新后一个视图的约束,因为我们对所点击的视图调用了mas_remakeConstraints
方法,就会移除其之前所添加的所有约束,所以我们必须重新将后者对当前点击的视图的依赖重新添加上去:
1
2
3
4
5
6
7
|
if (nextView) {
[nextView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(view.mas_bottom).offset(20);
}];
}
|