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 文件中,例如 UIImageViewUILabel

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 的使用效率和灵活性非常重要。

posted @   Mr.陳  阅读(502)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示