美文网首页
iOS 自定义tableView Cell、高度自适应

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

作者: WeiHing | 来源:发表于2016-09-13 11:36 被阅读3542次

    1.xib方式创建

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

    加载数据

    有plist文件数据结构如下


    创建数据模型

    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中拖子控件。


    创建一个继承自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、高度自适应

          本文链接:https://www.haomeiwen.com/subject/celedttx.html