爬爬爬之路:UI(十一) UITableView(三) 自定义Cell 多Cell混合 自适应高度 及cell的状态控制

自定义Cell

引言

UITableView中的数据显示 必须通过UITableViewCell进行.

由于系统定义的UITableViewCell只有简单的几种样式, 当我们需要完成一些复杂的界面操作的时候, 系统定义的UITabeViewCell就不够用了.

此时的最佳解决方案就是我们自己定义一个cell.

自定义Cell的步骤

  1. 创建一个继承自UITableViewCell的子类
  2. 重写其初始化方法
  3. 把需要的控件添加到其显示内容区域上
  4. 在UITableViewDataSource对应的显示内容方法:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    

    中, 将返回的UITableViewCell对象替换成自定义Cell的对象.

这里需要介绍一个很重要的属性:
@property (nonatomic, readonly, retain) UIView *contentView;
我们知道ViewController中添加组件 是添加到ViewController自带的view上.
而AppDelegate 总添加组建 是添加到AppDelegate的window上

同样, 想要把组建添加到屏幕可以显示出来的地方, 在UITableViewCell中的组建就是contentView. 只有将组建添加在contentView中, 才可以被显示出来.

在创建自定义cell的时候, 注意不能添加UITableViewCell中已存在的同名控件(类型不同也不能重名). 比如imageView这个名字.


多Cell混合显示

一个UITableView中, 很可能出现两种甚至多种布局方式截然不同的复杂布局的cell. 这时候就需要我们创建多个cell来满足需求.

我们可以根据Model的某个用于区别cell的属性来判断当前行需要显示的cell样式是对应了哪一个自定义cell
比如: 现在已有一个需求是: 定义一个UITableView来显示学生信息, 当学生的性别是女的时候, 显示GirlCell这个自定义cell的样式, 当学生的性别是男的时候, 显示的是BoyCell这个自定义cell的样式.
假设所有的学生的信息是保存在self.dataArr这个数组中, self.dataArr中保存的形式是多个Student类(数据模型)对象. Student类中包含name, gender, age三个属性. 每个Student对象对应一个学生的信息. 当前UITableView中只有一个分区
代码如下:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Student *student = self.dataArr[indexPath.row];
    // 通过学生信息的性别信息进行判断应该生成的学生信息应该显示在哪个自定义cell上
    // 若是女
    if ([student.gender isEqualToString:@"女"]) {
        static NSString *girlIdentifier = @"girlCell";
        GirlCell *cell = [tableView dequeueReusableCellWithIdentifier:girlIdentifier];
        if (cell == nil) {
            cell = [[[GirlCell alloc] nitWithStyle:(UITableViewCellStyleSubtitle) reuseIdentifier:girlIdentifier] autorelease];
        }
        // 自定义cell中拥有一个Student对象的属性. 且重写了该属性的set方法, 自动对界面进行赋值.
        cell.student = student;
        return cell;
    } else {
        // 若不是女生, 就是男生
        static NSString *boyIdentifier = @"boyCell";
        BoyCell *cell = [tableView dequeueReusableCellWithIdentifier:boyIdentifier];
        if (cell == nil) {
            cell = [[[BoyCell alloc] nitWithStyle:(UITableViewCellStyleSubtitle) reuseIdentifier:girlIdentifier] autorelease];
        }
        // 自定义cell中拥有一个Student对象的属性. 且重写了该属性的set方法, 自动对界面进行赋值.
        cell.student = student;
        return cell;
    }
}

像这样, 可以通过Model的某个可用于判断可用于显示该Model的cell类型. 像这样就可以同时在一个UITableView上显示多种自定义cell


自适应高度

对于一些新闻类的界面而言(文本内容长度不一)的cell. 我们就需要通过计算文本高度, 来动态的改变该cell的高度, 达到每个cell都能正好显示出内容的目的.

这里介绍一个计算文本高度的方法:

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context;

这是一个NSString的对象方法.

计算这个方法需要两个不定参数, 一个是控件的宽度(通常是Label), 一个是字体的大小.
原理是, 系统会自动根据给定的宽度和字体大小来计算出本字符串在这两个条件下会需要的多高的高度.

参数1 size : 宽为需要计算文本高度的控件的宽度. 高度为一个最大高度(一般而言填一个当前字符串的长度达不到的高度, 比如CGFLOAT_MAX).
参数2 options: 用来控制计算高度的方式
有以下几种枚举:

typedef NS_OPTIONS(NSInteger, NSStringDrawingOptions) {
    NSStringDrawingTruncatesLastVisibleLine = 1 << 5, 计算文本尺寸时 将以每个字或字形为单位来计算
    NSStringDrawingUsesLineFragmentOrigin = 1 << 0, 文本将以每行组成的矩形为单位计算整个文本的尺寸
    NSStringDrawingUsesFontLeading = 1 << 1, 行距: 从一行文字的底部到另一行文字地步的间距
    NSStringDrawingUsesDeviceMetrics = 1 << 3,
}

常用的是第二个, 也就是NSStringDrawingUsesLineFragmentOrigin.

参数3 attributes : 用处是给定字体的大小
用法为:

NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:16], NSFontAttributeName, nil];
// value是字体大小, 要求和控件中设定的字体大小要一致, 否则会计算不准确
// key是UIFont 在系统中的属性的名字   固定写NSFontAttributeName即可.

参数4 是一个用于绘制属性字符串的对象, 这里我们暂时用不上, 填写nil即可.

在这里, 我们只是计算出该字符串在固定宽度和字体大小下所占的框架大小.

在计算出结果后, 需要根据结果修改label的高度, 再修改cell的高度.
这里涉及到了调用的地方的问题, 一个是在自定义cell内部调用, 另一个是在自定义cell外部调用. 为了调用方便, 这里将计算高度的方法写成自定义cell的类方法. 可以将Model对象当成该方法的参数.

记得在计算出内容本文所需高度的时候, 要记得修改对应控件的frame, 更别忘了要修改cell的高度.

使用示例:

// content为显示内容, font为字体
+ (CGFloat)cellHeightForModel:(NSString *)content fontSize:(CGFloat)font {

    // 创建字体大小的字典
    NSDictionary *fontDic = @{NSFontAttributeName : [UIFont systemFontOfSize:font]};
    // kWidth为控件的宽度
    CGRect titleFrame = [content boundingRectWithSize:CGSizeMake(kWidth, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin) attributes:fontDic context:nil];

    return titleFrame.size.height;
}

自定义cell的赋值

通常为了符合封装的特性, 我们会在自定义cell中声明一个Model的属性接口, 并且通过重写Model的setter方法, 对整个cell进行赋值.

比如 :

- (void)setModel:(Model *)model {
    if (_model != model) {
        [_model release];
        _model = [model retain];
    }
    // 为其他的控件赋值, 值来自model
    // ...
    // ...
    // ...
}

当我们需要记录某个cell的状态, 比如选中状态.
由于cell的重用机制, 当一个cell出现的时候, 都会运行一次

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

方法

在这个方法中, 我们会进行对特定标识符的自定义cell进行赋值操作, 只要一个cell离开屏幕显示范围 再次出现时均会进行重新赋值的过程.

所以用cell控制本身的状态是不稳定, 可能会导致其他cell在重用本cell的时候将它的状态一同重用了.
因为cell和Model是一一对应的, cell是不稳定的, Model是稳定. 显而易见最好的方法就是用cell对应的Model来控制cell的状态.

比如控制cell的选中状态.
在Model中添加一个属性用于控制cell的选中状态

// 对应的cell是否被选中
@property (nonatomic, assign) BOOL isSelected;
// 在Model的初始化方法中或者是在封装Model数据时给isSelected赋上初值.

在以上提到的重写的setter方法中再填入一句判断:
伪代码如下:

- (void)setModel:(Model *)model {
    if (_model != model) {
        [_model release];
        _model = [model retain];
    }
    // 为其他的控件赋值, 值来自model
    // ...
    // ...
    // ...
    if (model.isSelected) {
        让cell变成选中状态对应的样式
    } else {
        让cell变成未选中状态对应的样式
    }
}

这样就可以稳定的保证cell不会因为复用了其他cell而导致复用了不该复用的状态.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值