您的位置:首页 > 移动开发 > IOS开发

欢迎使用CSDN-markdown编辑器

2017-03-07 22:49 155 查看
在没有AutoLayout之前,自定义一个高度不固定的cell是相当麻烦的。你需要写非常多计算尺寸的代码,在拿到数据后,需要计算cell里面每一个控件的尺寸才能最终确定cell的高度。如果你已经受够了各种计算尺寸的代码。那么本篇文章或许会对你有一些帮助,本文会说明如何利用AutoLayout优雅的实现具有动态高度的cell


在开始之前,先看下效果图,知道将要完成神马东西。



高度随着内容变化,内容越多,高度就越高

内容label,就是显示
天气真好
的label,最多显示3行


自定义Cell,继承自UITableViewCell

1
2
3
4
5
6
7

@interface GJCell : UITableViewCell

@property (nonatomic, weak) UIImageView *customImageView;
@property (nonatomic, weak) UILabel *title;
@property (nonatomic, weak) UILabel *subtitle;

@end

customImageView用于显示头像

title用于显示昵称

subtitle用于显示内容


创建UITableView并准备数据

1
2
3
4
5
6
78
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

#import "GJCell.h"
#import "Masonry.h"

static NSString * const GJCellIndentifier = @"GJCell";

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) NSArray *datas;
@property (nonatomic, weak) UITableView *tableView;

@end

@implementation ViewController

#pragma mark - Life Cycle

- (void)viewDidLoad {
[super viewDidLoad];

[self setupData];

[self setupView];
}

- (void)setupData {
NSDictionary *data1 = @{@"icon": @"myIcon",
@"name": @"GJBlog",
@"content": @"今天天气真好啊"};

NSDictionary *data2 = @{@"icon": @"myIcon",
@"name": @"GJBlogGJBlogGJBlog",
@"content": @"今天天气真好啊今天天气真好啊今天天气真好啊今天天气真好啊"};

NSDictionary *data3 = @{@"icon": @"myIcon",
@"name": @"GJBlogGJBlogGJBlogGJBlogGJBlog",
@"content": @"今天天气真好啊今天天气真好啊今天天气真好啊今天天气真好啊今天天气真好啊今天天气真好啊今天天气真好啊"};

self.datas = @[data1, data2, data3];
}

- (void)setupView {
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
[tableView registerClass:[GJCell class] forCellReuseIdentifier:GJCellIndentifier];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:tableView];
self.tableView = tableView;

[tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}

AutoLayout
代码采用Masonry,
系统的那套太繁琐了.

tableView
的大小等于
self.view
的大小.

self.datas
里面有三条数据,每条数据的
name
content
长度都是不一样的.尽量模拟真实情况.
icon
的值是一张本地图片的名称.


实现GJCell.m

1
2
3
4
5
6
78
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

#import "GJCell.h"
#import "Masonry.h"

@implementation GJCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupView];

[self setupConstraint];
}
return self;
}

- (void)setupView {

UIImageView *customImageView = [[UIImageView alloc] init];
customImageView.layer.cornerRadius = 15.0f;
customImageView.layer.masksToBounds = YES;
[self.contentView addSubview:customImageView];
_customImageView = customImageView;

// 重点1
CGFloat preferredWidth = [UIScreen mainScreen].bounds.size.width - 75;

UILabel *title = [[UILabel alloc] init];
title.numberOfLines = 0;
// 重点1
title.preferredMaxLayoutWidth = preferredWidth;
title.textColor = [UIColor grayColor];
[self.contentView addSubview:title];
_title = title;

UILabel *subtitle = [[UILabel alloc] init];
subtitle.numberOfLines = 3;
// 重点1
subtitle.preferredMaxLayoutWidth = preferredWidth;
[self.contentView addSubview:subtitle];
_subtitle = subtitle;
}

- (void)setupConstraint {
[self.customImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView).with.offset(15.0f);
make.right.equalTo(self.title.mas_left).with.offset(-15.0f);
make.left.equalTo(self.contentView).with.offset(15.0f);
make.size.mas_equalTo(CGSizeMake(30.0f, 30.0f));
}];

[self.title mas_makeConstraints:^(MASConstraintMaker *make) {
// 重点2
make.top.equalTo(self.contentView).with.offset(20.0f).with.priority(751);
make.right.equalTo(self.contentView).with.offset(-15.0f);
}];

[self.subtitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.title.mas_bottom).with.offset(15.0f);
make.right.equalTo(self.title);
make.bottom.equalTo(self.contentView).with.offset(-15.0f).with.priority(749);
make.left.equalTo(self.title);
}];
}

@end


1.在
setupView
方法里面添加自定义控件

设置
customImageView
为圆角,
title
的行数不限制,
subtitle
的行数最多为3行.


2.在
setupConstraint
方法里面添加自定义控件的约束

customImageView
顶部、左边距离父视图
15.0f
,
customImageView
的右边与
title
的左边相距
15.0f
,
customImageView
的高度和宽度都等于
30.0f
.

title
的顶部距离父视图
20.0f
,
title
的右边距离父视图
15.0f
.

subtitle
的顶部与
title
的底部相距
15.0f
,
subtitle
的左边等于
title
的左边,
subtitle
的右边等于
title
的右边,
subtitle
的底部距离父视图
15.0f
.


3.说下代码中的两个重点

重点1:告诉
AutoLayout系统``label
的最大宽度,便于计算高度。如果实在无法理解,等会实现
tableview
数据源
代理
之后,可以尝试注释掉
重点1
处的代码,然后看下效果。

重点2:注意力放到
with.priority(751)
这里,两个
label
相邻,
label
的高度都是暂时无法确定,需要告诉
AutoLayout系统
约束的优先级,遇到无法同时满足约束时的优先满足级别。如果实在无法理解,同重点1,代码完善后,删掉
with.priority(751)
,观察下控制台的输出。


在控制器中实现UItableViewDataSource

1
2
3
4
5
6
78
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 60;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
GJCell *cell = [tableView dequeueReusableCellWithIdentifier:GJCellIndentifier forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}

- (void)configureCell:(GJCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSInteger row = indexPath.row % 3;
NSDictionary *data = self.datas[row];

UIImage *image = [UIImage imageNamed:data[@"icon"]];
[cell.customImageView setImage:image];

[cell.title setText:data[@"name"]];

[cell.subtitle setText:data[@"content"]];
}


实现UITableViewDelegate

1
2
3
4
5
6
78
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForCellAtIndexPath:indexPath];
}

- (CGFloat)heightForCellAtIndexPath:(NSIndexPath *)indexPath {
static GJCell *cell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [self.tableView dequeueReusableCellWithIdentifier:GJCellIndentifier];
});
[self configureCell:cell atIndexPath:indexPath];
return [self calculateHeightForCell:cell];
}

- (CGFloat)calculateHeightForCell:(GJCell *)cell {
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f;
}


方法说明

heightForCellAtIndexPath:
拿出一个cell用于计算,使用
dispatch_once
保证只执行一次,方法
configureCell:atIndexPath:
在数据源那块已经实现了,直接调用即可。

调用
calculateHeightForCell:
计算cell的高度,先调用
setNeedsLayout
layoutIfNeeded
让cell去布局子视图,然后调用
systemLayoutSizeFittingSize:
AutoLayout系统
去计算大小,
参数
UILayoutFittingCompressedSize
的意思是告诉
AutoLayout系统
使用尽可能小的尺寸以满足约束,返回的结果里
+1.0f
是分割线的高度。


OK, 到这里基本结束了,可以编译运行了。


小小的优化一下

如果你的项目最低支持iOS7,那么你可以在
UITableViewDelegate
那块添加如下方法:

1
2
3
4
5

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 112.0f;
}

实现该方法后,tableview就不会一次性调用完所有cell的高度,有些不在可见范围的cell是不需要一开始就知道高度的。当然,
estimatedHeightForRowAtIndexPath
方法调用频率就会非常高,所以我们尽量返回一个比较接近实际结果的固定值以提高性能.

Done!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS Autolayout Cell