iOS 自定義tableView Cell、高度自適應

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;
@end

Product.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中設置這個id
    ProductCell *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;
@end

ProductCell.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:
優化UITableViewCell高度計算的那些事
UITableView+FDTemplateLayoutCell 源碼閱讀

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容