UITableViewCell

UITableViewCell

  • cell内部的控件是懒加载的,用到时才创建并且只创建一次

UITableViewCell的常见设置

// 设置右边的指示样式
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

// 设置右边的指示控件
cell.accessoryView = [[UISwitch alloc] init];

// 设置cell的选中样式
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// backgroundView优先级 > backgroundColor

// 设置背景色
cell.backgroundColor = [UIColor redColor];

// 设置背景view
UIView *bg = [[UIView alloc] init];
bg.backgroundColor = [UIColor blueColor];
cell.backgroundView = bg;

// 设置选中的背景view
UIView *selectedBg = [[UIView alloc] init];
selectedBg.backgroundColor = [UIColor purpleColor];
cell.selectedBackgroundView = selectedBg;

cell的循环利用

  • cell的重用原理
重用原理:
1.当滚动列表时,部分Cell被会移出窗口,UITableView会将窗口外的Cell放入缓存池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看缓存池,如果池中有未使用的UITableViewCell,dataSource会用新的数据覆盖这个UITableViewCell原有的数值,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象

2.还有一个非常重要的问题:
缓存池中可能有不同类型的cell,为了防止取出的不是我们想要的cell,所以我们需要在初始化cell的时候给cell设置一个特定的字符串来标识,当tableView要求数据源返回cell时,会先通过该标识到缓存池中查找对应类型的cell,如果有就重用,如果没有,就用该标识来初始化一个新的cell
  1. 传统的写法
/**
 *  每当有一个cell要进入视野范围内,就会调用一次
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"wine";
    // 1.先去缓存池中查找可循环利用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    // 2.如果缓存池中没有可循环利用的cell
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    // 3.设置数据
    cell.textLabel.text = [NSString stringWithFormat:@"%zd行的数据", indexPath.row];
    return cell;
}
  1. 新的写法(注册cell)
- 该方式无法设置cell的样式
NSString *ID = @"wine";
- (void)viewDidLoad {
    [super viewDidLoad];
    // 注册某个重用标识 对应的 Cell类型
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1.先去缓存池中查找可循环利用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    // 2.设置数据
    cell.textLabel.text = [NSString stringWithFormat:@"%zd行的数据", indexPath.row];
    return cell;
}

自定义Cell

自定义等高cell
  • 代码自定义cell
1.新建一个继承自UITableViewCell的类
2.添加控件属性和模型属性
3.在layoutSubviews方法中布局子控件
  3.1通过frame布局
  3.2通过AutoLayout 布局
4.在传入模型的set方法中给子控件属性赋值
5.添加便利构造方法
  • xib自定义cell
1. 新建一个继承自UITableViewCell的类
2. 新建xib文件
   2.1 在xib文件中布局好子控件
   2.2 设置xib的class属性
   2.3 设置cell的重用标识
   2.4 连线子控件
3. 加载xib文件
4. 在模型属性的set方法中给子控件赋值
  • storyboard自定义cell
1. 新建一个继承自UITableViewCell的类
2. 在storyboard中布局好子控件
   2.1 设置cell的class属性
   2.2 设置cell的重用标识
   2.3 连线子控件
3. 通过继承自UITableViewCell的类设置cell里面每个控件的属性
// 3. 也可以直接通过viewWithTag,直接给子控件赋值(不推荐)

自定义非等高cell

  • 添加子控件的原则
1. 把有可能显示的控件,都先添加进去,然后根据数据的情况确定这些控件显示还是隐藏(hidden属性)
2.YES就要有与之对应的NO , 有if 就有与之对应的else(否则会引发循环引用问题)
  • UILabel真实宽高计算
// 单行时计算文本的真实尺寸,返回值CGSize
NSDictionary *atrriDict = @{NSFontAttributeName:[UIFont systemFontOfSize:17]}
[label.text sizeWithAttributes:atrriDict]

// 多行时计算文本的真实尺寸,返回值是CGRect(矩形框)
[label.text boundingRectWithSize:contentMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17]} context:nil].size
  • xib自定义cell
// 1. 在模型中增加一个cellHeight属性,用来存放对应cell的高度
// 2. 在cell的模型属性set方法中,调用layoutIfNeed方法强制布局,然后计算出cellHeight的值
// 3. 在控制器中实现tableView的代理方法估算行高(或者设置tableView.estimatedRowHeight)
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 100;
}
// 4. 在控制器中调用tableView的代理方法返回cell的真实高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    LJMessageModel *model = self.messages[indexPath.row];
    return model.cellHeight;
}
// 5注意点:如果cell中有自动换行的label需要设置label每一行的最大宽度(宽度有时候不等于中文字符宽度的整数倍,不设置会有小问题)
// 设置label的宽度等于屏幕宽度减去两倍的间距
label.preferrdMaxLayoutWidh = [UIScreen mainScreen].bounds.size.width - 2 * margin
  • storyboard自定义非等高cell
1.设置cell的重用标识
2.直接利用[tableView dequeueReusableCellWithIdentifier:@"wine"];
3.不需要判断cell是否为空,再创建cell,系统会自动帮我们完成这一步
4.如果有自动换行的label必须:
    设置numberOflines = 0;
    设置label的上下左右约束
5.在控制器的viewDidLoad方法
  // 设置cell的估算高度
  self.tableView.estimatedRowHeight = 100;
  // 告诉控制器cell的高度是自动计算的
  self.tableView.rowHeight = UITableViewAutomaticDimension;
  • 通过frame模型 + cell数据模型
    新建一个frame模型,定义属性保存每个控件的frame,以及一个cell数据模型属性
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class LJMessageModel;
@interface LJFrameModel : NSObject
// 模型属性
@property (nonatomic, strong) LJMessageModel *model;
@property(nonatomic,assign) CGRect timeFrame;
@property(nonatomic,assign)CGRect iconFrame;
@property(nonatomic,assign)CGRect textFrame;
// cell的真实高度
@property(nonatomic,assign)CGFloat cellHeight;

重写模型属性的set方法,再该方法中通过模型属性的数据计算子控件的frame

#import "LJFrameModel.h"
#import "LJMessageModel.h"
// 屏幕的宽高
#define kScreenSize ([UIScreen mainScreen].bounds.size)
#define Margin 10
@implementation LJFrameModel
- (void)setModel:(LJMessageModel *)model{
    _model = model;

    // 计算frame  timeLabel
    CGFloat timeX = 0;
    CGFloat timeY = 0;
    CGFloat timeW = kScreenSize.width;
    CGFloat timeH = 20;
    _timeFrame = CGRectMake(timeX, timeY, timeW, timeH);

    // 计算头像frame
    CGFloat iconX;
    CGFloat iconY = CGRectGetMaxY(_timeFrame) + Margin;
    CGFloat iconWH = 40;
    if (_model.type == MessageTypeMe) {// 自己
        iconX = kScreenSize.width - Margin - iconWH;
        _iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
    }else{// 其他
        iconX = Margin;
        _iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
    }

    // 计算textButton的frame
    // 屏幕宽度 - 2个头像的宽度 - 头像左右各2个间距的宽度
    CGFloat maxWidth = kScreenSize.width - 2 * iconWH - 4 * Margin;
    CGSize maxSize = CGSizeMake(maxWidth, MAXFLOAT);
    // 根据文字内容计算真实尺寸
    NSDictionary *attribute = @{NSFontAttributeName :[UIFont systemFontOfSize:17]};
    CGRect bounds = [_model.text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attribute context:nil];
    CGFloat textW = bounds.size.width;
    CGFloat textH = bounds.size.height;
    CGFloat textX;
    CGFloat textY = iconY;
    if (_model.type == MessageTypeMe) {
        textX = kScreenSize.width - textW - iconWH -2 * Margin - 40;
        _textFrame = CGRectMake(textX, textY, textW, textH);
    } else {
        textX = CGRectGetMaxX(_iconFrame) + Margin;
        _textFrame = CGRectMake(textX, textY, textW, textH);
    }

    // 增加button的宽高 内边距
    _textFrame.size.width += 30;
    _textFrame.size.height += 30;

    _cellHeight = MAX(CGRectGetMaxY(_textFrame), CGRectGetMaxY(_iconFrame));
}

初始化子控件

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        self.backgroundColor = [UIColor redColor];
        // 初始化子控件
        [self setupUI];
    }
    return self;
}
- (void)setupUI {
    // 时间
    UILabel *timeLable = [[UILabel alloc] init];
    self.timeLabel = timeLable;

    // 居中显示
    timeLable.textAlignment = NSTextAlignmentCenter;

    [self.contentView addSubview:timeLable];

    // 用户头像
    UIImageView *iconImageView = [[UIImageView alloc] init];
    self.iconImageView = iconImageView;

    [self.contentView addSubview:iconImageView];

    // 文本buton
    UIButton *textButton = [[UIButton alloc] init];
    self.textButton = textButton;
    textButton.backgroundColor = [UIColor blueColor];
    // 设置文本颜色
    [textButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

    // 设置文本自动换行
    textButton.titleLabel.numberOfLines = 0;

    // 设置文本的font
    textButton.titleLabel.font = [UIFont systemFontOfSize:17];

    // 设置button的内边距, 让内容的显示距离button边界有一段距离
//    textButton.contentEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
    textButton.titleEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
    textButton.imageEdgeInsets = UIEdgeInsetsMake(15, 0,0, 0);
    [self.contentView addSubview:textButton];
}

重写frame模型的set方法,在该方法中给子控件赋值,传递frame

- (void)setFrameModel:(LJFrameModel *)frameModel{
    _frameModel = frameModel;
    // 设置数据
    [self setupData];

    // 设置frame
    [self setupFrame];
}

#pragma mark -
#pragma mark -  设置数据
- (void)setupData {
    LJMessageModel *model = _frameModel.model;

    // 设置时间
    _timeLabel.text = model.time;
    // 设置头像
    // 判断是我, 还是其他人, 设置背景图片
    if (model.type == MessageTypeMe) { // 表示自己
        // 用户头像
        self.iconImageView.image = [UIImage imageNamed:@"me"];
        // 背景图片
        [self.textButton setBackgroundImage:[UIImage imageNamed:@"chat_send_nor"] forState:UIControlStateNormal];
    } else { // 表示别人
        // 用户头像
        self.iconImageView.image = [UIImage imageNamed:@"other"];
        // 背景图片
        [self.textButton setBackgroundImage:[UIImage imageNamed:@"chat_recive_press_pic"] forState:UIControlStateNormal];
    }
    // 设置文本
    [self.textButton setTitle:model.text forState:UIControlStateNormal];


}
#pragma mark -
#pragma mark -  设置frame
- (void)setupFrame {
    // 时间frame
    if (_frameModel.model.isHideTime) {//隐藏时间
        self.timeLabel.hidden = YES;
        // 更新frame
        _timeLabel.frame = CGRectZero;

        // 用户头像frame
        CGRect iconFrame = _frameModel.iconFrame;
        _iconImageView.frame = CGRectMake(iconFrame.origin.x, iconFrame.origin.y - 20, iconFrame.size.width, iconFrame.size.height);

        // 文本frame
        CGRect textFrame = _frameModel.textFrame;
        _textButton.frame = CGRectMake(textFrame.origin.x, textFrame.origin.y - 20, textFrame.size.width, textFrame.size.height);

        self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height - 20);
    }else{// 显示时间
        self.timeLabel.hidden = NO;
        _timeLabel.frame = _frameModel.timeFrame;
        // 用户头像frame
        _iconImageView.frame = _frameModel.iconFrame;

        // 文本frame
        _textButton.frame = _frameModel.textFrame;

        self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
        }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值