iOS小技能:cell的重用原理及自定义cell

3,182 阅读7分钟

「这是我参与2022首次更文挑战的第35天,活动详情查看:2022首次更文挑战」。

引言

iOS开发中,要实现表格数据展示,做常用的做法就是使用UITableView。

I UITableViewCell的简介

1.1 cell的结构

UITableView的每一行都是一个UITableViewCell

  1. cell的初始化: 通过datasource的tableView: cellForRowAtIndexPath: 方法来初始化每一行
  2. UITableViewCell内部有一个默认的自视图ContentView : ContentView是UITableViewCell所显示内容的父视图,可显示一些辅助视图(辅助视图的作用是显示一个表示动作的图标);contentView 默认有3个子视图(textLabel、detailTextLabel、UIImageView)
  3. UITableViewCell的UITabelCellStyle属性: 用于决定使用contentView的哪些子视图,以及这个视图在contentView中的位置
  4. cell的结构

这里写图片描述

1.2 cell的重用原理

  1. 重用原理 当滚动列表时,部分cell会移出窗口,UITableView会将窗口外的cell放入一个等待重用的对象池; 当UITableView要求datasource返回cell的时候,datasource会先查看这个对象池是否有未使用的cell,若有,datasource会用新的数据配置这个cell,并返回给UITableVIew重新显示到窗口中,从而避免创建新的对象。
  2. 解决一个TableView同时拥有不通类型的cell的问题 解决方案: 在初始化cell的时候,传入一个特定的“字符串标识”(通常使用cell的类名)来给cell的reuseIdentifier属性赋值; 当UITableView 要求datasource返回cell的时候,此时就利用reuseIdentifier属性到对象词中查找对应类型的cell对象,若找到就重用,否则利用这个reuseIdentifier属性来实例化一个cell对象
  3. cell重用的例子:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1.定义一个cell的标识
      static NSString *ID = @"mjcell";
     
    // 2.从缓存池中取出cell
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
     
    // 3.如果缓存池中没有cell
      if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
     
    // 4.设置cell的属性...
    return cell;
}
  1. 单元格循环引用概念

II 使用xib封装一个View的步骤

  1. new一个xib文件来描述一个View的内部结构
  2. new一个自定义的视图类(继承自xib根对象的class),类名通常与xib文件名保存一致
  3. 将xib中的控件与自定义视图类的.m文件建立连线 (建立连线之前,对根对象的class指定为刚刚新建的控件->在xi b属性面板指定可重用标识符
  4. 提供一个类方法,返回一个创建好的自定义视图类(屏蔽从xib加载的过程) 5, 提供一个模型属性让外界传递模型数据,重写模型属性的setter方法(将模型数据展示到对应的子控件上面)

III delegate

父控件(视图控制器)监听子控件的事件,当子控件发生某些事情的时候,通知父控件工作--备注:父控件通知子控件工作,直接调用子控件的方法即可。

如果使用强引用,将造成循环的强引用-->儿子只能对父亲进行弱引用。

@property (nonatomic,weak) id<HSGroupBuyingFooterViewDelegate> delegate;//在oc中,只有没有强用的时候,才会被立即释放;一旦自定义视图称为视图控制器的视图包含,极自定义视图为视图控制器的儿子时,且视图控制器为自定义视图(儿子)的代理,此时如果代理是强引用,将造成循环的强引用,”你中有我,我中有你“。--永远呆在内存

这里写图片描述

3.1 delegate的使用场合

  1. 对象A内部发生了一些事情,想通知对象B,对象A想传递数据给对象B
  2. 对象B想监听对象A内部发生了什么事情
  3. 对象A想在自己的方法内部调用对象B的某些方法,并且对象A不能对对象B有耦合依赖

3.2 使用delegate的步骤

  1. 搞清楚谁 是 谁的 delegate
  2. 定义代理协议(协议名称的命名规范:控件类名+Delegate)
  3. 定义代理方法: *代理方法一般都定义为optional *代理方法名称都以空间名开头;代理方法至少有个参数,用于将控件本事传递出去
  4. 设置代理对象(代理对象遵守协议,并实现协议方法)
  5. 在恰当的时刻调用delegate的协议方法,来通知delegate发生了什么事情(在调用之前判断代理是否实现了该代理方法)

IV 通过代码自定义cell

  1. new一个继承自UITableViewCell的类
  2. 重写initWithStyle: reuseIndentifier: 方法(对子控件的属性进行一次性赋值) --有些属性只须设置一次,例如字体、固定的图片
  3. 并添加需要显示的子控件到contentView中
  4. 提供数据模型、frame模型 数据模型用于存放文字、图片数据;frame模型存放数据模型、所有子控件的frame、cell的高度
  5. cell拥有一个frame模型
  6. 重写frame模型的setter方法(设置子控件的显示数frame);对frame的对象实例化采用懒加载方法,即进行getter方法的重写
//
 
//  HSStatusCell.h
 
 
 
 
#import <UIKit/UIKit.h>
 
#import "HSStatus.h"
 
@interface HSStatusCell : UITableViewCell
 
//自定义视图的现实的数据来源于模型,即使用模型装配自定义视图的显示内容
 
@property (nonatomic,strong) HSStatus *status;//视图对应的模型,是视图提供给外界的接口
 
/**
 
 通过数据模型设置视图内容,可以让视图控制器不需要了解视图的细节
 
 */
 
+ (instancetype) tableVieCellwWithStatus:(HSStatus *) status tableView:(UITableView *)tableView;//使用类方法获取自定义视图,参数用于视图的数据装配
 
//
 
+ (instancetype) tableVieCellwWittableView:(UITableView *)tableView;//使用类方法获取自定义视图
 
@end
 
  • 设置位置
- (void)setStatus:(HSStatus *)status{
 
    _status = status;
 
    [self settingData];
 
        //设置位置
 
    [self settingFrame];
 
}
 
 
 
 
- (void) settingData{
 
    //设置位置
 
    [self.textView  setText:self.status.text];
 
    [self.nameView setText:self.status.name];
 
    [self.iconView setImage:self.status.iconImage];
 
    if (self.status.picture.length > 0) {
 
        [self.pictureView setImage:self.status.pictureImage];
 
        [self.pictureView setHidden:NO];
 
    }else{
 
        [self.pictureView setImage:nil];//没有配图的时候,清空图片信息--cell重用的时候,对于可选视图要进行处理
 
        [self.pictureView setHidden:YES];
 
 
 
 
    }
 
    if (self.status.vip) {
 
        [self.vipView setImage:self.status.vipImage];
 
        [self.vipView setHidden:NO];//显示VIP视图
 
        [self.nameView setTextColor:[UIColor redColor]];
 
    }else{
 
        [self.vipView setImage:nil];//不是VIP的时候,清空VIP标识--cell重用的时候,针对可选视图进行特殊处理
 
        [self.vipView setHidden:YES];
 
        [self.nameView setTextColor:[UIColor blackColor]];
 
 
 
 
    }
 
}
 
/**
 
 设置位置信息
 
 */
 
- (void) settingFrame{
 
    //定义间距
 
    CGFloat padding =10;
 
    CGFloat iconX = padding;
 
    CGFloat iconY = padding;
 
    CGFloat iconWidth = 30;
 
    CGFloat iconHeiht = 30;
 
    [self.iconView setFrame:CGRectMake(iconX, iconY, iconWidth, iconHeiht)];
 
    //设置昵称,昵称的大小由文字的长度决定
 
    /**
 
     1.boundingRectWithSize 方法计算给定文本所占用的区域
 
     2.options: 计算多行的的准确高度需要传入NSStringDrawingUsesLineFragmentOrigin
 
     3、attributes 指定字体相关属性;UIKit框架中的第一个头文件   NSAttributedString.h
 
 
 
 
      
 
     */
 
    NSDictionary *nameDict = @{NSFontAttributeName:KnameFont};
 
    CGRect nameFrame = [self.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nameDict context:nil];//返回一个x,y,都为0 的CGRect
 
    nameFrame.origin.x = CGRectGetMaxX(self.iconView.frame)+padding;
 
    nameFrame.origin.y = padding+(CGRectGetHeight(self.iconView.frame)-CGRectGetHeight(nameFrame))*0.5;
 
     
 
    [self.nameView setFrame:nameFrame];
 
     
 
    //设置VIP标识的frame
 
    [self.vipView setFrame:CGRectMake(CGRectGetMaxX(self.nameView.frame)+padding, CGRectGetHeight(self.nameView.frame), 14, 14)];
 
    //设置文本内容的frame
 
 
 
 
    NSDictionary *textDict = @{NSFontAttributeName:KtextFont};
 
    CGRect textFrame = [self.status.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options: NSStringDrawingUsesLineFragmentOrigin attributes:textDict context:nil];
 
    textFrame.origin.x = padding;
 
    textFrame.origin.y = CGRectGetMaxY(self.iconView.frame)+padding ;
 
    [self.textView setFrame:textFrame];
 
    //设置picture的frame
 
    [self.pictureView setFrame:CGRectMake(padding,CGRectGetMaxY(self.textView.frame)+padding, 100, 100)];
 
      
 
}
  • 计算行高
#pragma mark - tableView delegate方法
 
#if 1
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
 
    /**
 
     问题背景: 执行此方法的时候,cell还没被实例化;行高实在实例化cell之后,设置cell对象的模型属性status的时候计算的
 
     解决方案:得到模型-》确定行高
 
     */
 
    //在cell实例化之前,获取行高-》先获取到模型
 
    HSStatus *statuse = self.stautses[indexPath.row];
 
    //开始计算行高
 
//    CGFloat height = padding+iconView.frame.height+padding+textView.frame.height+pictureView.frame.height+padidng;
 
    CGFloat iconViewHeight = 30;
 
    CGFloat pictureViewHeight = 100;
 
    CGFloat padding = 10;
 
    CGFloat textViewHeight = [statuse.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.height;
 
    CGFloat height =iconViewHeight+textViewHeight+((statuse.picture.length >0 ? pictureViewHeight+padding: 0))+padding*2;
 
    return height;
 
}
 
#endif

see also

🍅 联系作者: iOS逆向(公号:iosrev)


🍅 作者简介:CSDN 博客专家认证🏆丨全站 Top 50、华为云云享专家认证🏆、iOS逆向公号号主


🍅 简历模板、技术互助。关注我,都给你。