前言

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

Masonry复杂ScrollView布局的更多相关文章

  1. 使用Masonry搭建特殊布局时与xib的对比

    之前只有比较浅的接触过Masonry.项目中大多数的布局还是用xib中的AutoLayout与手码的frame计算相结合,相信也会有很多项目和我一样是这两种布局的组合.其实xib各方面用的感觉都挺好, ...

  2. android中RelativeLayout无法填充ScrollView布局的问题

    ScrollView是解决布局过长的情况下使用,一遍其下面会有个顶部布局,我项目里面是RelativeLayout,但是RelativeLayout无论设置 android:layout_height ...

  3. 详解嵌套ListView、ScrollView布局显示不全的问题

    在项目开发中,可能经常遇到嵌套ListView.ScrollView的问题,就是重写onMeasure方法.解决如下 public class ExpandListView extends ListV ...

  4. iOS masonry 不规则tagView布局 并自适应高度

    在搜索页面经常会有不规则的tag出现,这种tagView要有点击事件,单个tagView可以设置文字颜色,宽度不固定根据内容自适应,高度固定,数量不固定.总高度就不固定.最近对于masonry的使用又 ...

  5. ScrollView不设置contentSize属性依然也可以作为底层滚动View(使用masonry设置scrollView的contentSize)

    第一步 //下层的scroolView self.baseScrollView = [[UIScrollView alloc] init]; self.baseScrollView.delegate ...

  6. Masonry 布局 scrollView

    原理 scrollView的高度(纵向滑动时)时靠内部的子控件撑起来的.我们直接给ScrollView布局会发现失败.用层级检查器发现,ScrollVIiw的高度有问题,我们可以选择添加一个UIVie ...

  7. IOS控件布局之Masonry布局框架

    前言: 回想起2013年做iOS开发的时候,那时候并没有采用手写布局代码的方式,而是采用xib文件来编写,如果使用纯代码方式是基于window的size(320,480)计算出一个相对位置进行布局,那 ...

  8. iOS学习——布局利器Masonry框架源码深度剖析

    iOS开发过程中很大一部分内容就是界面布局和跳转,iOS的布局方式也经历了 显式坐标定位方式 --> autoresizingMask --> iOS 6.0推出的自动布局(Auto La ...

  9. Scrollview包裹布局问题。

    输入框获取焦点,键盘弹出,背景图片上移: https://blog.csdn.net/wljian1/article/details/79962802 android:scrollbarThumbVe ...

随机推荐

  1. POJ-2386Lake Counting,搜索题。。

    Lake Counting Time Limit: 1000MS   Memory Limit: 65536K           Description Due to recent rains, w ...

  2. 是时候学习真正的 spark 技术了

     
 spark sql 可以说是 spark 中的精华部分了,我感觉整体复杂度是 spark streaming 的 5 倍以上,现在 spark 官方主推 structed streaming, ...

  3. UVA11090 Going in Cycle!! 【SPFA】

    题意:求一个无向图的边权平均值最小的环 思路:假设环中Σwi/t<ans 那变形一下就是Σwi<ans*t → Σ(wi-ans)< 0 这样就可以二分答案做了 #include & ...

  4. msp430项目编程07

    msp430中项目---简单计算器 1.扫描键盘工作原理 2.电路原理说明 3.代码(显示部分) 4.代码(功能实现) 5.项目总结 msp430项目编程 msp430入门学习

  5. python学习之-- subprocess模块

    subprocess 模块 功能:用来生成子进程,并可以通过管道连接它们的输入/输出/错误,以及获得它们的返回值.它用来代替多个旧模块和函数: os.system os.spawn* os.popen ...

  6. 【微信小程序】开发实战 之 「开发框架MINA构成」

    小程序开发框架的目标是通过尽可能简单.高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务. 微信团队为小程序提供的框架命名为MINA.MINA框架通过封装微信客户端提供的文件系统.网络通信 ...

  7. Ultra-QuickSort--POJ2299(归并排序求逆序数对)

    http://poj.org/problem?id=2299 归并排序:比如现在有一个序列[l,r),我们可以把这个序列分成两个序列[l,mid),[mid,r),利用递归按照上 述方法逐步缩小序列, ...

  8. 2017-10-04-afternoon

    注意完全平方数统计时的特判 #include <cstdio> inline void read(int &x) { x=; register char ch=getchar(); ...

  9. EXTJS中整合tinymce的富文本编辑器,添加上传图片功能

    提供部分代码.Ext.create('Ext.window.Window', { id: 'wind', title: 'CRUD窗口', modal: true, height: 800, widt ...

  10. 【APUE】wait与waitpid函数

    当一个进程终止时,内核就向其父进程发送SIGCHLD信号.因为子进程终止是个异步事件,所以这种信号也是内核向父进程发的异步通知.父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数.对 ...