iOS 学习之仿微信朋友圈九宫格布局

微信朋友圈一直以来都是 iOS 开发人员争相模仿的界面,主要是其包含了丰富的 iOS 所需知识点,以及常用的功能模块。当然各个功能模块实现过程中的细节处理以及用户体验的优化,这才是我们开发者在日常开发中需要关注和加强的地方。

本文将着重分析微信朋友圈中九宫格实现的具体过程以及细节优化,争取把里面的所有知识点,模块虽小,但五脏俱全,其中最主要分析的是朋友圈的界面布局的细节处理以及性能优化。希望为大家提供一点思路,少走一些弯路,填补一些细坑。文章仅供大家参考,若有不妥之处,还望不吝赐教,欢迎批评指正。

相信大家一定不陌生微信朋友圈的九宫格布局。
当图像为一张的时候,图片会自适应它本身的大小从而做出适配;
当图像为四张的时候,图像会呈现出 2x2 的布局;
当图像为其他数量的时候,图像依次会 3x3 的布局;

而最大的难点就在于九宫格处理这一块,九张图像依次排列,这种布局,可复用的 item 又按照一定的规律排版,我们首先第一考虑的是什么,没错 - UICollectionView,只有这个控价最符合这种需求,但是问题又来了,如果是 UICollectionView,那四张图像的时候又怎么来排版呢?一张呢?好吧,果段放弃这个念想,只能换其他的控件来尝试。那么如果用九个 UIImageView 来做,根据图像的数量来确定图像的数量,从而来进行增删控件,在进行 layout 更新来达到试图所需要的效果,咋一看,方法确实可行,说干就干,结果终于做出来了,可是发现界面异常卡顿,尤其是在图像变化很大的时候,卡顿最为明显。最终实在看不下去,我们换一种思路,假设把整个视图分文两部分部分,其中用户名,头像,发布内容为上半部分,九宫格单独里拿出来做为下半部分视图。我们可以把九张 UIImageView 一次性全部加载在一个 UIView 上,通过图像的数量来决定 UIView 的高度,宽度我们把它定死,UIView 的宽度为屏幕的宽度,加载的时候只需要通过数量来隐藏 UIImgaeView,并且更新对应的布局,那么就可以达到流畅加载的效果。

WYIconBrowserView.h

1
2
3
4
5
6
7
8
9
10
11
#import <UIKit/UIKit.h>

@interface WYIconBrowserView : UIView

/** 图像链接数组,最大支持9张,超出则不显示 */
@property (nonatomic, strong) NSArray <NSString *> *imagesUrl;

/** 第一张图像尺寸 */
@property (nonatomic, assign) CGSize firstImageSize;

@end

WYIconBrowserView.m

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
#import "WYIconBrowserView.h"

@interface WYIconBrowserView ()
@property (nonatomic, strong) NSMutableArray <UIImageView *> *iconArray;
@property (nonatomic, assign) CGFloat lineSpacing;
@property (nonatomic, assign) CGFloat interitemSpacing;
@property (nonatomic, assign) CGFloat leftW;
@property (nonatomic, assign) CGFloat itemWidth;
@end

@implementation WYIconBrowserView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupUI];
}
return self;
}

- (void)showInBigImage:(UITapGestureRecognizer *)tap {
UIImageView *iconImage = (UIImageView *)tap.view;
NSInteger idx = iconImage.tag - 99999;
NSLog(@"点中第%ld张图片",idx);
}

- (void)setupUI {
self.leftW = 0;
self.lineSpacing = 5;
self.interitemSpacing = 5;

CGFloat viewW = [UIScreen mainScreen].bounds.size.width / 10 * 7.0;
CGFloat itemW = (viewW - self.interitemSpacing * 2) / 3;
self.itemWidth = floor(itemW * 100) / 100;

for (int i = 0; i < 9; i++) {
NSInteger item = i % 3;
NSInteger line = i / 3;

CGFloat originX = self.leftW + self.interitemSpacing * item + self.itemWidth * item;
CGFloat originY = self.lineSpacing * line + self.itemWidth * line;

UIImageView *icon = [[UIImageView alloc] init];
icon.userInteractionEnabled = YES;
icon.frame = CGRectMake(originX, originY, self.itemWidth, self.itemWidth);
icon.hidden = YES;

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showInBigImage:)];
[icon addGestureRecognizer:tap];

[self.iconArray addObject:icon];
[self addSubview:icon];
}
}

- (void)setImagesUrl:(NSArray<NSString *> *)imagesUrl {
_imagesUrl = imagesUrl;

[self.iconArray enumerateObjectsUsingBlock:^(UIImageView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
if (imagesUrl.count == 4)
{
if (idx == 0 || idx == 1)
{
[obj setImageWithURLString:self.imagesUrl[idx]
placeholderImage:[UIImage imageNamed:@"placeholderImage_"]
];

obj.hidden = NO;
obj.tag = 99999 + idx;

} else if (idx == 3 || idx == 4)
{
[obj setImageWithURLString:self.imagesUrl[idx-1]
placeholderImage:[UIImage imageNamed:@"placeholderImage_"]
];

obj.hidden = NO;
obj.tag = 99999 + idx - 1;

} else
{
obj.tag = 88888 + idx;
obj.hidden = YES;
}

} else
{
if (idx < self.imagesUrl.count)
{
[obj setImageWithURLString:self.imagesUrl[idx]
placeholderImage:[UIImage imageNamed:@"placeholderImage_"]
];

obj.hidden = NO;
obj.tag = 99999 + idx;
}
else
{
obj.tag = 88888 + idx;
obj.hidden = YES;
}
}

}];


if (imagesUrl.count == 0)
{
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(0).priorityHigh();
make.width.equalTo(self.itemWidth);
}];
} else if (imagesUrl.count == 1)
{
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.firstImageSize.width);
make.height.equalTo(self.firstImageSize.height).priorityHigh();
}];
} else if (imagesUrl.count == 4)
{
CGFloat cellHeight = self.itemWidth * 2 + self.lineSpacing;
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(cellHeight).priorityHigh();
make.width.equalTo(self.itemWidth);
}];
} else
{
NSInteger itemCount = imagesUrl.count % 3 == 0 ? (imagesUrl.count / 3) : (imagesUrl.count / 3 + 1);
CGFloat cellHeight = self.itemWidth * itemCount + self.lineSpacing * itemCount;

[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(cellHeight).priorityHigh();
make.width.equalTo(self.itemWidth);
}];
}
}

#pragma mark - Getter
- (NSMutableArray <UIImageView *> *)iconArray {
if(!_iconArray){
_iconArray = [[NSMutableArray <UIImageView *> alloc] init];
}
return _iconArray;
}

@end