前言
说到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); }]; } |