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

weixin_33978044發表於2016-09-13

1.xib方式建立

每個cell的顯示的內容都是固定的,也就是cell的高度都是相同的

載入資料

有plist檔案資料結構如下


1727123-c573c7af62bcdb8e.png

建立資料模型

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中拖子控制元件。


1727123-d9cf6051a01ae56d.png

建立一個繼承自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 原始碼閱讀

相關文章