iOS纯代码自定义UITableViewCell及性能优化

知识准备

自定义Cell的实现方式

        1. 纯代码方式:纯代码又可以通过frame和autolayout技术来实现

        2.XIB方式

        3.Storyboard方式

Cell的种类

 根据不同的分法有多种分类,可以根据高度、内容种类

     固定高度单元格(UITableView中的所有单元格的高度都是固定高度,一般数据内容的大小和子控件的个数都是确定的)

     可变高度单元格(UITableView中的每个单元格的高度由内容决定,大部分情况是由于数据内容的多少不确定,如文本内容长度,或有没有图片等)

    单类型单元格(UITableView中的单元格中的数据只有一种,即所有单元格的内容完全一样或者差别非常小)

     混合型单元格(UITableView中的单元格中的有些单元格和其他单元格的展示的内容和子控件个数相差很大,例如很多App在列表中都有广告和推广之类的Cell,这些Cell一般是有一段文字描述,一张大图片,一个下载按钮等,和自己App要展示的内容完全不一样,实现方式只需要在cellForRowAtIndexPath方法中返回不同种类的Cell实例对象即可)

技术准备:

     缓存池(循环利用):

 缓存池是对UITableViewCell的循环利用,假如UITableView的高度最多能显示4行,当用户稍微上拉再加载一行,那么之前的第一行Cell就会放到缓存池数组中,临时被保存起来,如果再稍微上拉一下,再加载一个新行,那么这个新行不是新创建的,而是从缓存池中拿到的第一行的那行,接着,把第二行单元格放入到缓存池中,这样做的目的是减少单元格创建的数量,因为一下子创建所有单元格肯定会耗费性能的!因为缓存池也会存放App的其他单元格,为了区分需要给单元格指定一个id标识符,这样就不会拿错单元格了。

 在循环利用的时候需要特别注意,有if必须有else, 一般在cellForRow 或 setModel 方法中要根据不同情况分别处理,但是不能只写if,必须要else,因为Cell是循环利用的,你不确定下次加载的Cell是那个重用Cell, 

估算高度(estimatedRowHeight)

UITableView继承自UIScrollView,因UIScroolView需要指定contentSize,即内容尺寸,滚动的区域,那么UITableView怎么获得该值呢?UITableView首先要知道表格要显示多少行,然后每行都要调用heightForRowAtIndexPath方法获取每行的单元格的高度,然后相加获取总高度;因滚动条的高度是根据内容的高度的计算的,所以也可以确定滚动条的高度。当加载新单元格时,系统会先调用heightForRow,再调用cellForRowAtIndexPath, 这时每个cell又要重新调用高度方法, 因UITableView中系统本身也会频繁调用heightForRow,所以这个方法会被调用很多次,会被很频繁的调用。苹果也意识到这个问题,为了缓解这个问题,苹果就出了一个新技术 估算高度,如果设置了估算高度,系统会根据这个行的估算高度 * 行数 = contentSize的高度,这样刚一开始加载的时候就不在调用heightForRowAtIndexPath了,而且系统还会先调用cellForRow方法再调用heightForRow方法,估算高度也算对性能的一点小优化!


自定义控件的步骤:

        1. 重写initWithFrame、awakeFromNib 等初始化方法用来初始化子控件

         2. 重写layoutSubviews方法布局子控件的frame

         3. 为Cell增加一个模型属性,并实现setModel方法,用来给子控件填充数据

         4. 提供一个便利的初始化方法用来快速初始化自定义控件(该步骤可选)

 计算UILabel的尺寸(高度、宽度):

          计算UILabel的尺寸是和字体相关的,不同的字体类型,字体的大小  这些因素都会影响UILabel的宽度,所以计算尺寸的时候字体参数是必须的

           计算单行的UILabel: 

                        sizeWithAttributes:@{NSFontAttributeName:[UIFont systemOfSize:17.f]}

           计算多行的UILabel:

计算多行和单行有点不一样,当因为是多行,要确定当UILabel究竟在多宽的时候换行,同样的内容长度,宽度越宽,高度就越低,所以必须指定需要换行的最大的约束尺寸,宽度是指在多宽的时候要换行,高度是指要返回的最大的高度,如果设置的最大高度为300,实际计算的结果大于300,那么只返回300,所以这个值一般都写很大的一个值,可以使用一个常量MAXFloat

             sizeWithFont:constrainedToSize:    //   该方法已经废弃了

             boundingRectWithSize:options:attributes:context;

行高

           rowHeight属性是UITableView的属性,而不是UITableViewCell的属性

调用顺序

- viewDidLoad
- tableView:numberOfRowsInSection:
- tableView:heightForRowAtIndexPath:  该方法一直循环调用N次,因初始化控件时需要知道contentSize的尺寸,只有知道所有cell的高度,才能计算总高度,根据contentSize的高度也能确定滚动条的高度
- tableView:cellForRowAtIndexPath:    该方法一直循环调用N次
- initWithStyle:reuseIdentifier:
- setModel:
- layoutSubviews 该方法一直循环调用N次

需要注意的是自定义cell是先设置模型数据,后调用layoutSubviews布局子视图

示例代码:

1.plist文件, 模拟数据源

<array>
       <dict>
		<key>text</key>
		<string>奥利奥原味</string>
		<key>icon</key>
		<string>7735164184.jpeg</string>
		<key>picture</key>
		<string>926680527.jpeg</string>
		<key>name</key>
		<string>内涵段子</string>
		<key>vip</key>
		<integer>1</integer>
	</dict>
</array>

2.模型

Status:微博模型           

#import <Foundation/Foundation.h>

#import "CellFrame.h"
@interface Status : NSObject

@property (copy, nonatomic) NSString *icon;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic, getter=isVip) BOOL vip;
@property (copy, nonatomic) NSString *text;
@property (copy, nonatomic) NSString *picture;

@property (strong, nonatomic) CellFrame *cellFrame;

@end
//-----------------------------------------
#import "Status.h"

@implementation Status

- (CellFrame *)cellFrame {
    if (_cellFrame == nil) {
        _cellFrame = [[CellFrame alloc] initWithStatus:self];
    }
    
    return _cellFrame;
}

@end</span>

 单元格Frame

<span style="font-weight: normal;">     
#import <UIKit/UIKit.h>

@class Status;

@interface CellFrame : NSObject

@property (assign, nonatomic) CGRect headFrame;
@property (assign, nonatomic) CGRect nicknameFrame;
@property (assign, nonatomic) CGRect vipFrame;
@property (assign, nonatomic) CGRect contentFrame;
@property (assign, nonatomic) CGRect pictureFrame;
@property (assign, nonatomic) CGFloat cellHeight;

- (instancetype)initWithStatus:(Status *)status;
@end

//------------------------------------------
#import "CellFrame.h"
#import "Status.h"

@interface CellFrame ()
@property (weak, nonatomic) Status *status;
@end

@implementation CellFrame

- (instancetype)initWithStatus:(Status *)status {
    if (self = [super init]) {
        _status = status;
    }
    
    return self;
}

- (CGFloat)cellHeight {
    if (_cellHeight == 0) {
        // head frame
        CGFloat margin = 10;
        self.headFrame = CGRectMake(margin, margin, 50, 50);
        
        // nickename frame
        CGFloat nickenameLabelX = CGRectGetMaxX(self.headFrame) + margin;
        CGFloat nickenameLabelY = CGRectGetMinY(self.headFrame);
        CGSize nickenameLabelSize = [self.status.name sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];
        self.nicknameFrame = CGRectMake(nickenameLabelX, nickenameLabelY, nickenameLabelSize.width, nickenameLabelSize.height);
        
        // vip frame
        if (self.status.isVip) {
            CGFloat vipImageViewX = CGRectGetMaxX(self.nicknameFrame) + margin;
            CGFloat vipImageViewY = CGRectGetMinY(self.nicknameFrame);
            self.vipFrame = CGRectMake(vipImageViewX, vipImageViewY, 14, 14);
        }
        
        // content frame
        CGFloat contentLabelX = CGRectGetMinX(self.headFrame);
        CGFloat contentLabelY = CGRectGetMaxY(self.headFrame) + margin;
        CGRect contentRect = [self.status.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 2 * margin, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14.f]} context:nil];
        self.contentFrame = CGRectMake(contentLabelX, contentLabelY, contentRect.size.width, contentRect.size.height);
        
        // picture frame
        if (self.status.picture != nil) {
            CGFloat pictureLabelX = CGRectGetMinX(self.contentFrame);
            CGFloat pictureLabelY = CGRectGetMaxY(self.contentFrame) + margin;
            self.pictureFrame = CGRectMake(pictureLabelX, pictureLabelY, 70, 100);
            self.cellHeight = CGRectGetMaxY(self.pictureFrame) + margin;
        } else {
            self.cellHeight = CGRectGetMaxY(self.contentFrame) + margin;
        }

    }
    
    return _cellHeight;
}
@end</span>

3.自定义Cell

#import <UIKit/UIKit.h>
#import "Status.h"
@interface TableViewCell : UITableViewCell

@property (strong, nonatomic) Status *status;

@end
#import "TableViewCell.h"

@interface TableViewCell ()

@property (weak, nonatomic) UIImageView *headImageView;
@property (weak, nonatomic) UILabel *nicknameLabel;
@property (weak, nonatomic) UIImageView *vipImageView;
@property (weak, nonatomic) UILabel *contentLabel;
@property (weak, nonatomic) UIImageView *pictureImageView;

@end

@implementation TableViewCell


// 1. 初始化子视图
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        // 头像
        UIImageView *headImageView = [[UIImageView alloc] init];
        [self.contentView addSubview:headImageView];
        self.headImageView = headImageView;
        
        // 昵称
        UILabel *nicknameLabel = [[UILabel alloc] init];
        nicknameLabel.font = [UIFont systemFontOfSize:17.f];
        [self.contentView addSubview:nicknameLabel];
        self.nicknameLabel = nicknameLabel;
        
        // 图标
        UIImageView *vipImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"vip"]];
        [self.contentView addSubview:vipImageView];
        self.vipImageView = vipImageView;
        
        // 正文
        UILabel *contentLabel = [[UILabel alloc] init];
        contentLabel.numberOfLines = 0;
        contentLabel.font = [UIFont systemFontOfSize:14.f];
        [self.contentView addSubview:contentLabel];
        self.contentLabel = contentLabel;
        
        // 配图
        UIImageView *pictureImageView = [[UIImageView alloc] init];
        [self.contentView addSubview:pictureImageView];
        self.pictureImageView = pictureImageView;
    }
    
    return self;
}


// 2. 布局子视图
- (void)layoutSubviews {
    [super layoutSubviews];
    
    self.headImageView.frame = self.status.cellFrame.headFrame;
    self.nicknameLabel.frame = self.status.cellFrame.nicknameFrame;
    self.vipImageView.frame = self.status.cellFrame.vipFrame;
    self.contentLabel.frame = self.status.cellFrame.contentFrame;
    self.pictureImageView.frame = self.status.cellFrame.pictureFrame;
}


// 3. 填充数据
- (void)setStatus:(Status *)status {
    _status = status;
    
    // head
    self.headImageView.image = [UIImage imageNamed:status.icon];
    
    // nickname & vip
    self.nicknameLabel.text = status.name;
    if (status.isVip) {
        self.nicknameLabel.textColor = [UIColor orangeColor];
        self.vipImageView.hidden = NO;
    } else {
        self.nicknameLabel.textColor = [UIColor blackColor];
        self.vipImageView.hidden = YES;
    }
    
    // content
    self.contentLabel.text = status.text;
    
    // picture
    if (status.picture != nil) {
        self.pictureImageView.hidden = NO;
        self.pictureImageView.image = [UIImage imageNamed:status.picture];
    } else {
        self.pictureImageView.hidden = YES;
    }
}

@end

4.ViewController

#import <UIKit/UIKit.h>
@interface ViewController : UITableViewController

@end
#import "ViewController.h"
#import "TableViewCell.h"
#import "MJExtension.h"

@interface ViewController ()

@property (strong, nonatomic) NSArray *statuses;

@end

@implementation ViewController

static NSString *ID = @"ViewController";

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[TableViewCell class] forCellReuseIdentifier:ID];
    self.tableView.estimatedRowHeight = 200;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"%s", __func__);
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    cell.status = self.statuses[indexPath.row];
    
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"%s", __func__);
    
    Status *status = self.statuses[indexPath.row];
    return status.cellFrame.cellHeight;
}

/**懒加载 */
- (NSArray *)statuses {
    if (_statuses == nil) {
        _statuses = [Status mj_objectArrayWithFilename:@"statuses.plist"];
    }
    
    return _statuses;
}
@end

示例效果:




示例代码Demo下载地址:http://download.csdn.net/detail/vbirdbest/9612721



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风流 少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值