iOS 自定义tableView Cell、高度自适应

1.xib方式创建

每个cell的显示的内容都是固定的,也就是cell的高度都是相同的

加载数据

有plist文件数据结构如下


iOS 自定义tableView Cell、高度自适应_第1张图片

创建数据模型

Product.h
@interface Product : NSObject

@property (nonatomic , copy) NSString *name;
@property (nonatomic , copy) NSString *location;
@property (nonatomic , copy) NSString *count;
@property (nonatomic , copy) NSString *price;
@property (nonatomic , copy) NSString *icon;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)goodsWithDict:(NSDictionary *)dict;
@end

Product.m
@implementation Product
- (instancetype)initWithDict:(NSDictionary *)dict{
    if (self = [super init]) {
        self.name = dict[@"name"];
        self.location = dict[@"location"];
        self.count = dict[@"minproduct"];
        self.price = dict[@"price"];
        self.icon = dict[@"icon"];
//        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
+ (instancetype)goodsWithDict:(NSDictionary *)dict{
    return [[self alloc]initWithDict:dict];
}
@end

懒加载数据

PageFirstTableViewController.m
//用来存储所有团购商品的数据
@property (nonatomic , strong) NSMutableArray *goods;

#pragma mark -lazyload
- (NSMutableArray *)goods{
    if (_goods ==nil) {
        NSString *path = [[NSBundle mainBundle]pathForResource:@"dataSource.plist" ofType:nil];
        NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
       //字典转模型
        NSMutableArray *arrayModels = [NSMutableArray array];
        for (NSDictionary *dict in arrayDict) {
            Product *model = [Product goodsWithDict:dict];
            [arrayModels addObject:model];
        }
        _goods = arrayModels;
    }
    return _goods;
}

实现数据源协议

通过xib方式实现自定义cell

创建以一个.xib文件。在xib中拖一个UITableViewCell,设置高宽。向UITableViewCell中拖子控件。


iOS 自定义tableView Cell、高度自适应_第2张图片

创建一个继承自UITableViewCell的类ProductCell与xib文件的cell相关联。通过拖线的方式将cell的子控件拖线到ProductCell的属性上。

ProductCell.m
@property (weak, nonatomic) IBOutlet UILabel *name;
@property (weak, nonatomic) IBOutlet UILabel *price;
@property (weak, nonatomic) IBOutlet UIImageView *icon;
....略

实现数据源协议

PageFirstTableViewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.goods.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1.获取模型数据
    Product *model = self.goods[indexPath.row];
    //2.创建单元格
    //通过xib创建单元格
    //由于此方法调用十分频繁,cell的标示声明成静态变量有利于性能优化
    static NSString *ID = @"goods_cell"; //要在xib中设置这个id
    ProductCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        //加载xib文件,loadNibName:方法返回的是一个数组,因为xib中可能有不止一个控件
        cell = [[[NSBundle mainBundle]loadNibNamed:@"ProductCell" owner:nil options:nil] firstObject];//不能加xib后缀
    }
    //3.把模型数据设置给单元格
    cell.name.text = model.name;
    cell.price.text = [NSString stringWithFormat:@"¥%@", model.price];
    cell.icon.image = [UIImage imageNamed: model.icon];
   ...赋值,略
    //4.返回单元格
    return cell;
}

在控制器中直接为cell重的每个子控件赋值数据造成的问题:
1.控制器强依赖于cell,一旦cell内部子控件发生了变化,那么控制器中的代码也得改(紧耦合)。控制器完全依赖于单元格里面的属性。
2.cell的封装不够完整,凡是用到cell的地方,每次都要编写为cell的子控件依次赋值的语句,比如:cell.xxx = model.xxx。如果有10个控制器,每个控制器里都需要用到单元格进行赋值,如果一个单元格里有10个子控件,那么上面这样的代码就要写10次。

对自定义cell进行封装,把模型数据设置给单元格,形如:cell.goods = model;由cell对象内部自己来解析模型数据,并把数据设置到对应的子控件中。在cell中创建一个模型类型的属性,重写该属性的set方法,在set方法中将数据赋值给控件。

ProductCell.h
#import "Product.h"
@interface ProductCell : UITableViewCell
@property (nonatomic , strong) Product *goods;
//封装一个创建自定义cell的方法
+ (instancetype)productCellWithTableView:(UITableView *)tableView;
@end

ProductCell.m
//重写set方法
- (void)setGoods:(Product *)goods{
    _goods =goods;
    self.name.text = goods.name;
    self.price.text = [NSString stringWithFormat:@"¥%@",goods.price];
    self.icon.image = [UIImage imageNamed:goods.icon];
    self.location.text = goods.location;
    self.count.text = [NSString stringWithFormat:@"最低批发量:%@",goods.count];
}
+ (instancetype)productCellWithTableView:(UITableView *)tableView{
    static NSString *ID = @"goods_cell";
    ProductCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        cell = [[[NSBundle mainBundle]loadNibNamed:@"ProductCell" owner:nil options:nil] firstObject];//不能加xib后缀
    }
    return cell;
}

修改后的数据源方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Product *model = self.goods[indexPath.row];
    ProductCell *cell = [ProductCell productCellWithTableView:tableView];
    cell.goods = model;
    return cell;
}

注意:要设置tableView.rowHeight = xib中的cell的高度。不然会报警告

2.纯代码方式创建(frameLayout,自适应高度)

每个cell显示的内容不固定,cell的高度需要根据内容的多少自适应高度(例如微博,朋友圈):
iOS开发UI篇—使用纯代码自定义UItableviewcell实现一个简单的微博界面布局

使用纯代码自定义一个tableview的步骤
1.新建一个继承自UITableViewCell的类
2.重写initWithStyle:reuseIdentifier:方法
添加所有需要显示的子控件(不需要设置子控件的数据和frame, 子控件要添加到contentView中)
进行子控件一次性的属性设置(有些属性只需要设置一次, 比如字体\固定的图片)
3.提供2个模型
数据模型: 存放文字数据\图片数据
frame模型: 存放数据模型\所有子控件的frame\cell的高度
4.cell拥有一个frame模型(不要直接拥有数据模型)
5.重写frame模型属性的setter方法: 在这个方法中设置子控件的显示数据和frame
6.frame模型数据的初始化已经采取懒加载的方式(每一个cell对应的frame模型数据只加载一次)

原文例子里自定义cell自适应高度是通过加减乘除运算来计算出来的,没有使用autolayout

结合链接原文里面的例子,讲一下自己的个人理解。

  • 步骤2:NJWeiboCell.m文件,在重写的initWithStyle:reuseIdentifier:方法里创建并添加子控件到contentView上,注意创建的子控件属性要声明为weak。另外,像微博vip皇冠图标这种固定的内容的控件,进行一次性数据设置即可。

  • 步骤3、4:除了数据模型(NJWeibo.m)以外还需要frame模型,自定义cell持有frame模型,frame模型持有数据模型。
    为什么还需要frame模型?如果没有frame模型,那么自定义cell中直接持有数据模型,即- (void)setWeiboFrame:(NJWeiboFrame *)weiboFrame替换为- (void)setWeibo:(NJWeibo *)weibo,然后在- (void)settingFrame方法中详细设置控件frame,得出cell的高度。每个cell的高度是随子控件内容而变化的。
    但是在tableView中,- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath设置行高的代理方法要比- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法先调用。如果没有提供frame模型,那么要直到执行数据源方法,把模型数据设置给单元格(形如:cell.weibo = model;)时,才能获取得到cell的行高。
    如果独立出frame模型,就可以在frame模型中详细设置控件frame,得出cell高度,然后在设置行高的代理方法中取出对应的frame模型中的行高。

  • 获取文本lable的宽和高:- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context ;参数1是计算大小的指定范围;如果将来计算的文字的范围超出了指定的范围,返回的就是指定的范围;如果没有超出那么返回的就是真实的范围;不限制范围的话设置CGSizeMake(MAXFLOAT, MAXFLOAT)就可以了。要注意的是:如果是获取多行文本的话,在此之前需要设置文本控件的numberOfLines属性为0。

另外,如果是获取单行文本的size :可以用sizeWithAttributes:方法

更新:使用xib创建自适应高度的tableViewCell
UITableViewCell高度自适应探索这篇文章写得挺细致的,没有其他要补充,大致上和用代码创建自适应高度cell的实现原理上是一样的。比起使用frameLayout创建cell要简单一点,不需要另外设置frame模型来存放所有子控件的frame、cell的高度。

现在有个第三方框架可以很方便地创建高度自适应的tableView cell:
优化UITableViewCell高度计算的那些事
UITableView+FDTemplateLayoutCell 源码阅读

你可能感兴趣的:(iOS 自定义tableView Cell、高度自适应)