前言

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

最新文章

  1. Function.prototype.toString 的使用技巧
  2. 最简单的jsp+servlet的增删改查代码
  3. POJ 2481 Cows
  4. 设置TextView的密码效果以及跑马灯效果
  5. Swift - 设置程序的应用图标和启动界面
  6. 个人项目--“”小鱼企业级开发系统“”Svn地址分享
  7. PHP批量去除bom头代码的小工具
  8. AspNet Core 下利用普罗米修斯+Grafana构建Metrics和服务器性能的监控 (无心打造文字不喜勿喷谢谢!)
  9. 2019/2/23Scala学习开始(Scala简介)
  10. Angular 基本指令
  11. webp 图形文件操作工具包 win32 (编译 libwebp-20171228-664c21dd 版本)
  12. javascript篇-知道value值,返回对象中的key值
  13. const &amp; define &amp; inline
  14. react:路由登陆后才能访问的控制
  15. bing背单词交互流程 - Chongyang Bai
  16. 第三章基本的SQl查询语言
  17. 加锁并发算法 vs 无锁并发算法
  18. IP分片
  19. 利用NotePad++ 格式化代码(格式标准化) worldsing
  20. 安装和使用phpstorm

热门文章

  1. VIM键盘图
  2. Android应用程序项目结构
  3. 一段曲折的copy路程
  4. oc温习三:常用函数
  5. Python基础之 一 字符编码及转换
  6. 继续畅通工程--hdu1879(最小生成树 模板题)
  7. 洛谷—— P2802 回家
  8. mybatis批量更新两种方式:1.修改值全部一样 2.修改每条记录值不一样
  9. linux 中断机制浅析
  10. [转] ubuntu 下mongodb的安装-----这篇文章也不错