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;
@endProduct.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中设置这个idProductCell *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;
@endProductCell.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: