Masonry复杂ScrollView布局

前言

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

}];

}

时间: 2024-08-01 11:15:08

Masonry复杂ScrollView布局的相关文章

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

之前只有比较浅的接触过Masonry.项目中大多数的布局还是用xib中的AutoLayout与手码的frame计算相结合,相信也会有很多项目和我一样是这两种布局的组合.其实xib各方面用的感觉都挺好,以前是性能问题,冲突问题饱受人诟病,但随着苹果的更新换代这些问题也逐渐趋向最小化. 我们团队整改的主要原因是为了更细粒度的组件化.因为将一块代码复用到另一个页面远比从xib中拖几个控件到别的页面来的要快,并且使用Masonry写出来的代码在控件间关系上非常清晰便于理解. 普通的布局就略过不说了,这里

ScrollView布局注意事项

android 采用ScrollView布局时出现异常:ScrollView can host only one direct child. 主要是ScrollView内部只能有一个子元素,即不能并列两个子元素,所以需要把所有的子元素放到一个LinearLayout内部或RelativeLayout等其他布局方式.

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

在项目开发中,可能经常遇到嵌套ListView.ScrollView的问题,就是重写onMeasure方法.解决如下 public class ExpandListView extends ListView { public ExpandListView(Context context) { super(context); } public ExpandListView(Context context, AttributeSet attrs) { super(context, attrs); }

ios7导航栏+scrollview布局注意

ios7以后苹果新增加了半透明效果,在这里强调一点:在nav+scrollview是,scrollview控件会自动想下缩进64个像素点,以实现穿透效果:iOS7在Conttoller中新增了这个属性:automaticallyAdjustsScrollViewInsets,(默认YES)(如果将automaticallyAdjustsScrollViewInsets设为no,那么scrollview的y为0时,则不会被导航栏挡住.谢谢!

Masonry 布局 scrollView

原理 scrollView的高度(纵向滑动时)时靠内部的子控件撑起来的.我们直接给ScrollView布局会发现失败.用层级检查器发现,ScrollVIiw的高度有问题,我们可以选择添加一个UIView容器,约束容器的edge.equalTo(self.scrollView),和 containerView.height.mas_equalTo(self.scrollView); 往这个容器中添加控件,所有子空间相对于容器布局. 最后一个控件不要忘记 添加一个 和 容器底部的约束.否则 容器的高

IOS控件布局之Masonry布局框架

前言: 回想起2013年做iOS开发的时候,那时候并没有采用手写布局代码的方式,而是采用xib文件来编写,如果使用纯代码方式是基于window的size(320,480)计算出一个相对位置进行布局,那个时候windows的size是固定不变的,随着iphone5的发布,windows的size(320,568)也发生了变化,而采用autoresizingMask的方式进行适配,到后来iphone 6之后windows size的宽度也随之变化,开始抛弃autoresizingMask改用auto

Coding源码学习第四部分(Masonry介绍与使用(三))

接上篇继续进行Masonry 的学习. (12)tableViewCell 布局 1 #import "TableViewController.h" 2 #import "TestTableViewCell.h" 3 4 @interface TableViewController ()<UITableViewDelegate, UITableViewDataSource> 5 6 @property(nonatomic, strong) UITable

android学习ScrollView的使用

ScrollView 的使用相对来讲比较简单,通过包含更多的布局文件,使得上下滑动可以浏览到更多内容. 关于ScrollView有几个点需要注意的地方 1,ScrollView的滚动方式 ScrollView有两种滚动方式,横向的和纵向的,一般横向的用的比较少.ScrollView控件默认就是纵向滚动的,如果需要横向滚动只需要更改标签 HorizontalScrollView,即可满足要求 2,ScrollView默认是在滚动的过程中显示滚动条的,所以如果想隐藏滚动条有两种方式: 1,通过标签设

iOS6 及其以上版本自动旋转、手动强制旋转方案及布局适配

1.布局适配方式 本文不讨论哪种布局适配方式最好,此处使用的是 Masonry 纯代码布局适配.(Masonry 底层就是 AutoLayout 的 NSLayoutConstraint) 2.iOS 方向枚举类 // 三维设备方向 typedef NS_ENUM(NSInteger, UIDeviceOrientation) { UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // Device oriented vertica