iOS开发基础13-深入理解 UITableView(二)
在 iOS 开发中,自定义 UITableViewCell
和模型转换是两项非常重要的技能。这篇文章将详细介绍如何通过纯代码和 Xib 文件自定义等高的 Cell,如何使用第三方框架进行自动布局和字典转模型,以及如何在同一个 UITableView
中同时使用不同类型的 Cell。
一、纯代码自定义等高 Cell
首先,我们要自定义一个 UITableViewCell
。我们以一个团购项目为例,创建一个继承自 UITableViewCell
的类 CHGTgCell
。
1. 添加子控件
在 CHGTgCell
类中,我们将需要的子控件如图片视图和标签视图进行创建和添加。
@interface CHGTgCell : UITableViewCell
@property (nonatomic, strong) UIImageView *iconImageView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *priceLabel;
@property (nonatomic, strong) UILabel *buyCountLabel;
@end
@implementation CHGTgCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// 初始化子控件
self.iconImageView = [[UIImageView alloc] init];
self.titleLabel = [[UILabel alloc] init];
self.priceLabel = [[UILabel alloc] init];
self.buyCountLabel = [[UILabel alloc] init];
[self.contentView addSubview:self.iconImageView];
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.priceLabel];
[self.contentView addSubview:self.buyCountLabel];
}
return self;
}
2. 布局子控件
我们可以在 layoutSubviews
方法中计算所有子控件的 frame
值。需要注意的是,在 initWithStyle:
方法中创建的 Cell,不会调用 initWithFrame:
方法。
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat margin = 10;
CGFloat imageX = margin;
CGFloat imageY = margin;
CGFloat imageWidth = 60;
CGFloat imageHeight = 60;
self.iconImageView.frame = CGRectMake(imageX, imageY, imageWidth, imageHeight);
CGFloat titleX = CGRectGetMaxX(self.iconImageView.frame) + margin;
CGFloat titleY = imageY;
CGFloat titleWidth = self.contentView.frame.size.width - titleX - margin;
CGFloat titleHeight = 20;
self.titleLabel.frame = CGRectMake(titleX, titleY, titleWidth, titleHeight);
CGFloat priceY = CGRectGetMaxY(self.titleLabel.frame) + margin;
CGFloat priceHeight = 20;
self.priceLabel.frame = CGRectMake(titleX, priceY, titleWidth, priceHeight);
CGFloat buyCountY = CGRectGetMaxY(self.priceLabel.frame) + margin;
CGFloat buyCountHeight = 20;
self.buyCountLabel.frame = CGRectMake(titleX, buyCountY, titleWidth, buyCountHeight);
}
3. 设置子控件数据
我们可以创建一个 TG
类,用于保存每一个 Cell 的数据。
@interface CHGTg : NSObject
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *price;
@property (nonatomic, copy) NSString *buyCount;
@end
- (void)setTg:(CHGTg *)tg {
_tg = tg;
self.iconImageView.image = [UIImage imageNamed:tg.icon];
self.titleLabel.text = tg.title;
self.priceLabel.text = [NSString stringWithFormat:@"¥%@", tg.price];
self.buyCountLabel.text = [NSString stringWithFormat:@"%@人已购买", tg.buyCount];
}
二、自动布局与 Masonry
在布局子控件时,我们可以使用第三方框架 Masonry 来简化代码。
使用 Masonry 布局子控件
#import <Masonry/Masonry.h>
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// 初始化子控件
self.iconImageView = [[UIImageView alloc] init];
self.titleLabel = [[UILabel alloc] init];
self.priceLabel = [[UILabel alloc] init];
self.buyCountLabel = [[UILabel alloc] init];
[self.contentView addSubview:self.iconImageView];
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.priceLabel];
[self.contentView addSubview:self.buyCountLabel];
// 使用 Masonry 布局
[self.iconImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(self.contentView).with.offset(10);
make.size.mas_equalTo(CGSizeMake(60, 60));
}];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.iconImageView);
make.left.equalTo(self.iconImageView.mas_right).with.offset(10);
make.right.equalTo(self.contentView).with.offset(-10);
}];
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.titleLabel.mas_bottom).with.offset(10);
make.left.right.equalTo(self.titleLabel);
}];
[self.buyCountLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.priceLabel.mas_bottom).with.offset(10);
make.left.right.equalTo(self.titleLabel);
}];
}
return self;
}
三、字典转模型的几种方式
1. 遍历数组
最传统的方法是手动遍历字典数组,通过字典构造模型对象然后添加到一个模型数组中。
- (NSArray *)tgs {
if (!_tgs) {
NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"tgs" ofType:@"plist"]];
NSMutableArray *tgArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
CHGTg *tg = [CHGTg tgWithDict:dict];
[tgArray addObject:tg];
}
_tgs = tgArray;
}
return _tgs;
}
2. 使用 MJExtension
MJExtension
是一个流行的第三方框架,用于将 JSON 或字典转化为模型。使用它可以简化数据解析流程。
#import "MJExtension.h"
_tgs = [CHGTg objectArrayWithFilename:@"tgs.plist"];
// 或者
_tgs = [CHGTg objectArrayWithFile:[[NSBundle mainBundle] pathForResource:@"tgs" ofType:@"plist"]];
// 或者
_tgs = [CHGTg objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"tgs" ofType:@"plist"]]];
在处理嵌套数据时,可以提前告诉 CarGroup
类其中的 cars
数组要解析成 Car
模型。
- (NSArray *)cargroups {
if (!_cargroups) {
[CarGroup setupObjectClassInArray:^NSDictionary *{
return @{@"cars": @"Car"};
}];
_cargroups = [CarGroup objectArrayWithFilename:@"cars.plist"];
}
return _cargroups;
}
四、Xib 自定义等高 Cell
我们可以通过 Xib 文件来更加直观地设计和布局自定义的 Cell。
1. 创建 Xib 文件
在 Xcode 中创建一个新的 Xib 文件,并确保它的 Custom Class
设置为 CHGTgCell
。
2. 配置 Xib 文件中的子控件
通过 Interface Builder 拖拽各种子控件到 Xib 文件中,例如 UIImageView
和 UILabel
。
3. 在 CHGTgCell 类中连接这些子控件
@interface CHGTgCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
@property (weak, nonatomic) IBOutlet UILabel *buyCountLabel;
@end
@implementation CHGTgCell
- (void)setTg:(CHGTg *)tg {
_tg = tg;
self.iconImageView.image = [UIImage imageNamed:tg.icon];
self.titleLabel.text = tg.title;
self.priceLabel.text = [NSString stringWithFormat:@"¥%@", tg.price];
self.buyCountLabel.text = [NSString stringWithFormat:@"%@人已购买", tg.buyCount];
}
@end
五、不同类型的 Cell 共存
我们可以在同一个 UITableView
中使用不同类型的 Cell。首先,在 cellForRowAtIndexPath
方法中返回不同类型的 Cell。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 == 0) {
CHGTgCell *cell = [tableView dequeueReusableCellWithIdentifier:tgID];
cell.tg = self.tgs[indexPath.row];
return cell;
} else {
CHGNewsCell *cell = [tableView dequeueReusableCellWithIdentifier:newsID];
return cell;
}
}
在 viewDidLoad
方法中注册不同类型的 Cell。
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = 70;
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([CHGTgCell class]) bundle:nil] forCellReuseIdentifier:tgID];
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([CHGNewsCell class]) bundle:nil] forCellReuseIdentifier:newsID];
}
六、Storyboard 自定义 Cell
在 Storyboard 中自定义 Cell,对不同的 Cell 绑定不同的 Identifier
并在代码中进行区分。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 == 0) {
static NSString *ID = @"tg";
CHGTgCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.tg = self.tgs[indexPath.row];
return cell;
} else {
return [tableView dequeueReusableCellWithIdentifier:@"test"];
}
}
七、分割线与静态 Cell
1. 分割线
通过自定义分割线,我们可以实现更灵活的 Cell 分隔效果。
- (void)setupSeparator {
UIView *separator = [[UIView alloc] init];
separator.backgroundColor = [UIColor grayColor];
[self.contentView addSubview:separator];
// 添加约束
[separator mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(1);
make.left.bottom.right.equalTo(self.contentView);
}];
}
2. 静态 Cell
静态 Cell 一般用于设置界面,通过 Storyboard 轻松实现。每个静态 Cell 的配置都可以在 Interface Builder 中完成,代码中直接使用即可。
总结
本文详细介绍了如何通过纯代码和 Xib 文件自定义等高 Cell,如何使用 Masonry 进行自动布局,如何利用 MJExtension 进行字典转模型,以及如何在同一个 UITableView
中同时使用不同类型的 Cell。这些知识点对于提高 UITableView
的使用效率和灵活性非常重要。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!