iOS开发基础19-深入理解和实现不等高的 UITableViewCell
在 iOS 开发中,UITableView
是一个非常常用的组件,用于展示大量数据。同时,很多时候我们希望每个 UITableViewCell
(简称 Cell)的高度能够根据内容自动调整。这篇文章将深入探讨如何使用纯代码自定义不等高的 Cell,并进行优化和性能分析。
主要步骤
- 创建自定义的
UITableViewCell
及其数据模型。 - 在控制器中加载数据,计算每个 Cell 的高度。
- 使用
heightForRowAtIndexPath:
方法返回每个 Cell 的实际高度。
实现代码
1. 创建自定义 UITableViewCell 和数据模型
首先,我们需要创建一个继承自 UITableViewCell
的类,例如 XMGStatusCell
,并在其初始化方法中添加所有需要的子控件。
自定义 UITableViewCell
#import <UIKit/UIKit.h>
@interface XMGStatusCell : UITableViewCell
@property (nonatomic, strong) UIImageView *iconImageView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong) UIImageView *pictureImageView;
@end
#import "XMGStatusCell.h"
@implementation XMGStatusCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupSubviews];
}
return self;
}
- (void)setupSubviews {
self.iconImageView = [[UIImageView alloc] init];
self.nameLabel = [[UILabel alloc] init];
self.textLabel = [[UILabel alloc] init];
self.pictureImageView = [[UIImageView alloc] init];
[self.contentView addSubview:self.iconImageView];
[self.contentView addSubview:self.nameLabel];
[self.contentView addSubview:self.textLabel];
[self.contentView addSubview:self.pictureImageView];
}
@end
自定义数据模型 XMGStatus
接下来,创建一个数据模型类 XMGStatus
,包含所有需要的属性,如名字、文本、头像和配图等,并计算每个字段的 frame 以及 Cell 的总高度。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface XMGStatus : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *picture;
@property (nonatomic, assign) BOOL vip;
// Frame 数据
@property (nonatomic, assign) CGRect iconFrame;
@property (nonatomic, assign) CGRect nameFrame;
@property (nonatomic, assign) CGRect textFrame;
@property (nonatomic, assign) CGRect pictureFrame;
@property (nonatomic, assign) CGFloat cellHeight;
@end
#import "XMGStatus.h"
@implementation XMGStatus
- (CGFloat)cellHeight {
if (_cellHeight == 0) {
CGFloat margin = 10;
// 头像
CGFloat iconX = margin;
CGFloat iconY = margin;
CGFloat iconWH = 30;
self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
// 昵称(姓名)
CGFloat nameX = CGRectGetMaxX(self.iconFrame) + margin;
CGFloat nameY = iconY;
NSDictionary *nameAttributes = @{NSFontAttributeName : [UIFont systemFontOfSize:17]};
CGSize nameSize = [self.name sizeWithAttributes:nameAttributes];
self.nameFrame = (CGRect){{nameX, nameY}, nameSize};
// 文字
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(self.iconFrame) + margin;
CGFloat textWidth = [UIScreen mainScreen].bounds.size.width - 2 * textX;
CGSize textMaxSize = CGSizeMake(textWidth, MAXFLOAT);
NSDictionary *textAttributes = @{NSFontAttributeName : [UIFont systemFontOfSize:14]};
CGFloat textHeight = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttributes context:nil].size.height;
self.textFrame = CGRectMake(textX, textY, textWidth, textHeight);
// 配图
if (self.picture) {
CGFloat pictureWH = 100;
CGFloat pictureX = textX;
CGFloat pictureY = CGRectGetMaxY(self.textFrame) + margin;
self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH);
_cellHeight = CGRectGetMaxY(self.pictureFrame) + margin;
} else {
_cellHeight = CGRectGetMaxY(self.textFrame) + margin;
}
}
return _cellHeight;
}
@end
2. 设置数据和布局子控件
为不等高的 UITableViewCell
设置布局和数据是非常关键的一步。确保所有子控件的位置和大小都是基于模型中的 frame 进行动态计算的。
@implementation XMGStatusCell
- (void)setStatus:(XMGStatus *)status {
_status = status;
self.iconImageView.image = [UIImage imageNamed:status.icon];
self.iconImageView.frame = status.iconFrame;
self.nameLabel.text = status.name;
self.nameLabel.frame = status.nameFrame;
self.textLabel.text = status.text;
self.textLabel.frame = status.textFrame;
if (status.picture) {
self.pictureImageView.hidden = NO;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
self.pictureImageView.frame = status.pictureFrame;
} else {
self.pictureImageView.hidden = YES;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
self.iconImageView.frame = self.status.iconFrame;
self.nameLabel.frame = self.status.nameFrame;
self.textLabel.frame = self.status.textFrame;
self.pictureImageView.frame = self.status.pictureFrame;
}
@end
3. 计算并返回 Cell 高度
在 UITableViewController
中,我们需要通过 heightForRowAtIndexPath:
方法来返回每个 Cell 的高度。在 Cell 的高度计算中,我们可以预先缓存高度。
#import "ViewController.h"
#import "XMGStatus.h"
#import "XMGStatusCell.h"
#import "MJExtension.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *statuses;
@end
@implementation ViewController
NSString *ID = @"status";
- (NSArray *)statuses {
if (!_statuses) {
_statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
}
return _statuses;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 告诉 tableView 所有 cell 的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
// 告诉 tableView 所有 cell 的估算高度
self.tableView.estimatedRowHeight = 200;
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.statuses.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.status = self.statuses[indexPath.row];
return cell;
}
#pragma mark - 代理方法
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
XMGStatus *status = self.statuses[indexPath.row];
return status.cellHeight;
}
@end
底层逻辑解析与优化
自适应高度计算的底层逻辑
在iOS中,UITableView
的高度缓存机制背后强大而复杂。当调用 heightForRowAtIndexPath:
方法时,系统会在内部缓存高度,以便后续快速访问。而自适应的 UITableViewCell
通过设置 tableView.rowHeight
为 UITableViewAutomaticDimension
和 estimatedRowHeight
的结合使用,使得系统能够自动计算并缓存高度。
优化方案
1. 使用预估高度减少计算
通过设置 tableView.estimatedRowHeight
,我们可以减少 heightForRowAtIndexPath:
方法的频繁调用,从而提升性能。
self.tableView.estimatedRowHeight = 200;
2. 缓存 Cell 高度
提前在数据模型中计算并缓存好 Cell 高度,避免在 heightForRowAtIndexPath:
中重复计算。
3. 批量更新表格
使用 beginUpdates
和 endUpdates
方法包裹需要批量更新的表格操作,可以提升性能。例如,当需要插入或者删除多行时:
[self.tableView beginUpdates];
// 插入或者删除行
[self.tableView endUpdates];
4. 异步加载数据
对于需要从网络等耗时操作中加载数据时,可以优先加载简单的部分,然后异步加载图片等数据,提升用户体验。
性能测试与调优
我们可以使用 Instruments 中的 Time Profiler 工具来检测我们应用的性能瓶颈,看看哪些方法占用了较多的 CPU 时间,以及通过优化代码来减少高频繁方法调用。
结论
通过深入分析和优化,我们成功实现了一个不等高的 UITableViewCell
。通过对子控件的动态布局、缓存 Cell 高度,以及性能优化等手段,可以在实际项目中更好地展示不等高的 Cell 效果。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步