前言

说到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. sql语句中嵌套2层循环

    declare @year intdeclare @month intset @year=2008 while(@year<=2011)beginset @month=1while(@month ...

  2. LR性能测试问题解决方法

    一.Error -27727: Step download timeout (120 seconds)has expired when downloading resource(s). Set the ...

  3. windows 环境下.Net使用Redis缓存

    Redis简介 Redis是一个开源的,使用C语言编写,面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景.Redis纯粹为应用而产生,它是一个高性能的 ...

  4. Go 在游戏行业中的工程实践

    在今年 1 月由七牛云主办的 ECUG Con 十周年盛会上,真有趣技术总监陈明达带来了题为< Go 在游戏行业中的工程实践>的精彩分享,深入讲解了 Go 的工程经验,错误和异常处理,in ...

  5. bzoj 1701 [Usaco2007 Jan]Cow School牛学校

    [Usaco2007 Jan]Cow School牛学校 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 175  Solved: 83[Submit][S ...

  6. bzoj3875 【Ahoi2014】骑士游戏 spfa处理后效性动规

    骑士游戏 [故事背景] 长期的宅男生活中,JYY又挖掘出了一款RPG游戏.在这个游戏中JYY会 扮演一个英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽. [问题描述] 在这个游戏中,JYY一共有两种攻 ...

  7. 把disable maven nature后的项目,恢复菜单呈现出来(Convert to Maven Project)

    把disable maven nature后的项目,恢复菜单呈现出来(Convert to Maven Project) 有的时候需求把disable maven nature后的项目,再转换为mav ...

  8. hihoCoder #1055 : 刷油漆 [ 树形dp ]

    传送门 结果:Accepted     提交时间:2015-05-11 10:36:08 #1055 : 刷油漆 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上回说到 ...

  9. POJ 2391 多源多汇拆点最大流 +flody+二分答案

    题意:在一图中,每个点有俩个属性:现在牛的数量和雨棚大小(下雨时能容纳牛的数量),每个点之间有距离, 给出牛(速度一样)在顶点之间移动所需时间,问最少时间内所有牛都能避雨. 模型分析:多源点去多汇点( ...

  10. PAT (Advanced Level) 1037. Magic Coupon (25)

    简单题. #include<iostream> #include<cstring> #include<cmath> #include<algorithm> ...