11-自定義cell(2種方法)

weixin_33918357發表於2017-05-07

第一種方法 :使用程式碼建立

分析:
(1)重寫initWithStyle方法,在這個方法裡建立控制元件或者尺寸約束(其實控制元件約束一般寫在layoutSubviews裡)。
(2)在layoutSubviews方法裡設定尺寸
(3)重寫模型的set方法

重寫模型的set方法:
先宣告模型型別,接受模型資料

@class XMGTopic;
@interface XMGTopicCell : UITableViewCell
/** 模型資料 */
@property (nonatomic, strong) XMGTopic *topic;
@end

程式碼:

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        // 增加頂部的控制元件,並且設定約束
        // ...
        
        // 增加底部的控制元件,並且設定約束
        // ...
    }
    return self;
}

/*
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    // 設定頂部和底部控制元件的frame
}*/

- (void)setTopic:(XMGTopic *)topic
{
    _topic = topic;
    
    // 設定頂部和底部控制元件的具體資料(比如文字資料、圖片資料)
}
@end

在Controller裡建立cell並匯入資料也有2種方法:

第一種:不常用(最基本的建立cell方法)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"cell";
   // 快取池去取
    XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) { // 快取池沒有,建立
        cell = [[XMGTopicCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];       
    }
   // 匯入資料
    cell.topic = self.topics[indexPath.row];
    return cell;
}

第二種:通過註冊,常用
註冊的功能是:去快取池取cell,若沒有建立cell

/* cell的重用標識 */
  static NSString * const XMGTopicCellId = @"XMGTopicCellId";

// 註冊cell,    registerClass:程式碼註冊,registerNib:xib註冊
  [self.tableView registerClass:[XMGTopicCell class] forCellReuseIdentifier:XMGTopicCellId];

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

   XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:XMGTopicCellId];
   // 匯入資料
   cell.topic = self.topics[indexPath.row];
   return cell;
}


第二種方法:使用xib建立

分析:
(1)在- (void)awakeFromNib中進行控制元件設定
(2)重寫模型的set方法

程式碼:

- (void)awakeFromNib
{
    self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mainCellBackground"]];
}

- (void)setTopic:(XMGTopic *)topic
{
    _topic = topic;
    
    // 頂部控制元件的資料
    [self.profileImageView sd_setImageWithURL:[NSURL URLWithString:topic.profile_image] placeholderImage:[UIImage imageNamed:@"defaultUserIcon"]];
    self.nameLabel.text = topic.name;
    self.passtimeLabel.text = topic.passtime;
    self.text_label.text = topic.text;
    
    // 底部按鈕的文字
    [self setupButtonTitle:self.dingButton number:topic.ding placeholder:@"頂"];
    [self setupButtonTitle:self.caiButton number:topic.cai placeholder:@"踩"];
    [self setupButtonTitle:self.repostButton number:topic.repost placeholder:@"分享"];
    [self setupButtonTitle:self.commentButton number:topic.comment placeholder:@"評論"];
}

在Controller裡註冊cell並匯入資料2種方法
第一種:通過mainBundle

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"cell";
   // 快取池去取
    XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) { // 快取池沒有,建立
        cell = [[[NSBundle mainBundle] loadNibNamed:self  owner:nil options:nil] firstObject];  
    }
   // 匯入資料
    cell.topic = self.topics[indexPath.row];
    return cell;
}

第二種:通過註冊載入cell

/* cell的重用標識 */
  static NSString * const XMGTopicCellId = @"XMGTopicCellId";

// 註冊cell
    UINib *nib = [UINib nibWithNibName:NSStringFromClass([XMGTopicCell class]) bundle:nil];
    [self.tableView registerNib:nib forCellReuseIdentifier:XMGTopicCellId];

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:XMGTopicCellId];
    
    cell.topic = self.topics[indexPath.row];
    
    return cell;
}

技巧:因為通過mainBundle載入資源,使用地方很多,可以將其封裝在UIView的分類裡
程式碼:

+ (instancetype)xmg_viewFromXib;
+ (instancetype)xmg_viewFromXib
{
    return [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil].firstObject;
}

擴充:若有四種cell,其中有共同的部分,也有各自不同的部分,如何做?

分析:2種思路

第一種思路:父cell+子cell
分析:

  • 因為xib無法繼承,所以需要用純程式碼或者頂部一個xib,底部一個xib,新增到cell上
  • 在自定義子cell時,其它都相同,就是-setTopic時,需要先呼叫一下父控制元件的-setTopic方法,即將共同的頂部與底部先載入進來並設定好資料。
3680843-914163297a0cb572.png
1.png

子cell的自定義程式碼如下:

@implementation XMGVoiceCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        // 增加中間的聲音控制元件,並且設定約束
        // ...
        
        self.backgroundColor = [UIColor greenColor];
    }
    return self;
}

/*
 - (void)layoutSubviews
 {
 [super layoutSubviews];
 
 // 設定中間聲音控制元件的frame
 }*/

- (void)setTopic:(XMGTopic *)topic
{
    [super setTopic:topic];
    
    // 設定中間聲音控制元件的具體資料(比如文字資料、圖片資料)
}

@end

第二種思路:只用一種cell

3680843-89fcc2e1b8f48150.png
1.png

分析:

**(1)建立整體cell;
(2)中間內容: 自定義三種xib,分別是視訊、語音、圖片
(3)中間內容懶載入進整體cell中
**

3680843-548a16b60d3053b8.png
1.png
@class XMGTopic;

@interface XMGTopicCell : UITableViewCell
/** 模型資料 */
@property (nonatomic, strong) XMGTopic *topic;
/* 中間控制元件 */
/** 圖片控制元件 */
@property (nonatomic, weak) XMGTopicPictureView *pictureView;
/** 聲音控制元件 */
@property (nonatomic, weak) XMGTopicVoiceView *voiceView;
/** 視訊控制元件 */
@property (nonatomic, weak) XMGTopicVideoView *videoView;
@end

@implementation XMGTopicCell
#pragma mark - 懶載入
- (XMGTopicPictureView *)pictureView
{
    if (!_pictureView) {
        XMGTopicPictureView *pictureView = [XMGTopicPictureView xmg_viewFromXib];
        [self.contentView addSubview:pictureView];
        _pictureView = pictureView;
    }
    return _pictureView;
}

- (XMGTopicVoiceView *)voiceView
{
    if (!_voiceView) {
        XMGTopicVoiceView *voiceView = [XMGTopicVoiceView xmg_viewFromXib];
        [self.contentView addSubview:voiceView];
        _voiceView = voiceView;
    }
    return _voiceView;
}

- (XMGTopicVideoView *)videoView
{
    if (!_videoView) {
        XMGTopicVideoView *videoView = [XMGTopicVideoView xmg_viewFromXib];
        [self.contentView addSubview:videoView];
        _videoView = videoView;
    }
    return _videoView;
}

#pragma mark - 初始化
- (void)awakeFromNib
{
    self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mainCellBackground"]];
}

- (void)setTopic:(XMGTopic *)topic
{
    _topic = topic;
    
    // 頂部控制元件的資料
    UIImage *placeholder = [UIImage xmg_circleImageNamed:@"defaultUserIcon"];
    [self.profileImageView sd_setImageWithURL:[NSURL URLWithString:topic.profile_image] placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        // 圖片下載失敗,直接返回,按照它的預設做法
        if (!image) return;
        
        self.profileImageView.image = [image xmg_circleImage];
    }];
    
    self.nameLabel.text = topic.name;
    self.passtimeLabel.text = topic.passtime;
    self.text_label.text = topic.text;
    
    // 底部按鈕的文字
    [self setupButtonTitle:self.dingButton number:topic.ding placeholder:@"頂"];
    [self setupButtonTitle:self.caiButton number:topic.cai placeholder:@"踩"];
    [self setupButtonTitle:self.repostButton number:topic.repost placeholder:@"分享"];
    [self setupButtonTitle:self.commentButton number:topic.comment placeholder:@"評論"];
    
    // 最熱評論
    if (topic.top_cmt.count) { // 有最熱評論
        self.topCmtView.hidden = NO;
        
        NSDictionary *cmt = topic.top_cmt.firstObject;
        NSString *content = cmt[@"content"];
        if (content.length == 0) { // 語音評論
            content = @"[語音評論]";
        }
        NSString *username = cmt[@"user"][@"username"];
        self.topCmtLabel.text = [NSString stringWithFormat:@"%@ : %@", username, content];
    } else { // 沒有最熱評論
        self.topCmtView.hidden = YES;
    }
    
    // 中間的內容
    if (topic.type == XMGTopicTypePicture) { // 圖片
        self.pictureView.hidden = NO;
        self.voiceView.hidden = YES;
        self.videoView.hidden = YES;
    } else if (topic.type == XMGTopicTypeVoice) { // 聲音
        self.pictureView.hidden = YES;
        self.voiceView.hidden = NO;
        self.voiceView.topic = topic;
        self.videoView.hidden = YES;
    } else if (topic.type == XMGTopicTypeVideo) { // 視訊
        self.pictureView.hidden = YES;
        self.voiceView.hidden = YES;
        self.videoView.hidden = NO;
    } else if (topic.type == XMGTopicTypeWord) { // 段子
        self.pictureView.hidden = YES;
        self.voiceView.hidden = YES;
        self.videoView.hidden = YES;
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    if (self.topic.type == XMGTopicTypePicture) { // 圖片
        self.pictureView.frame = self.topic.middleFrame;
    } else if (self.topic.type == XMGTopicTypeVoice) { // 聲音
        self.voiceView.frame = self.topic.middleFrame;
    } else if (self.topic.type == XMGTopicTypeVideo) { // 視訊
        self.videoView.frame = self.topic.middleFrame;
    }
}

問題1:如何避免同一個cell的高度計算多次,即讓同一個cel的高度只計算一次?

答案1:利用模型屬性,額外增加計算cell高度的屬性,重寫cellHeight的getter方法。在cellHeight方法裡判斷是否計算過行高,已經計算過就返回)

流程分析:

  • 當UITableView顯示的時候,系統開始就會呼叫numberOfSectionsnumberOfRows方法,來知道行數和組數

  • 然後呼叫heightForRow方法,計算所有cell的高度,計算出來tableView的contentsize,確定tableView的滑動範圍。

  • 再呼叫cellForRow方法,顯示出cell

  • 當螢幕來回滑動的時候,heightForRow方法會呼叫的頻繁

  • 解決上述問題,就定義一個模型屬性,重寫cellHeight的getter方法,已經計算過就返回

擴充:heightForRow這個方法的特點:

  • 預設情況下,每次重新整理表格的時候,有多少資料,這個方法就一次呼叫多少次 ---如:若有100條資料,每次reloadData,這個方法就會呼叫100次。
  • 每當有cell進入螢幕範圍,就會呼叫一次這個方法。
/* 額外增加的屬性(並非伺服器返回的屬性,僅僅是為了提高開發效率) */
/** 根據當前模型計算出來的cell高度 */
@property (nonatomic, assign) CGFloat cellHeight;
/** 中間內容的frame */
@property (nonatomic, assign) CGRect middleFrame;

#import "XMGTopic.h"

@implementation XMGTopic

- (CGFloat)cellHeight
{
    // 如果已經計算過,就直接返回
    if (_cellHeight) return _cellHeight;
    
    // 文字的Y值
    _cellHeight += 55;
    
    // 文字的高度
    CGSize textMaxSize = CGSizeMake(XMGScreenW - 2 * XMGMarin, MAXFLOAT);
    _cellHeight += [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height + XMGMarin;
    
    // 中間的內容    
    if (self.type != XMGTopicTypeWord) { // 中間有內容(圖片、聲音、視訊)
        CGFloat middleW = textMaxSize.width;
        CGFloat middleH = middleW * self.height / self.width;
        CGFloat middleY = _cellHeight;
        CGFloat middleX = XMGMarin;
        self.middleFrame = CGRectMake(middleX, middleY, middleW, middleH);
        _cellHeight += middleH + XMGMarin;
    }
    
    // 最熱評論
    if (self.top_cmt.count) { // 有最熱評論
        // 標題
        _cellHeight += 21;
        
        // 內容
        NSDictionary *cmt = self.top_cmt.firstObject;
        NSString *content = cmt[@"content"];
        if (content.length == 0) {
            content = @"[語音評論]";
        }
        NSString *username = cmt[@"user"][@"username"];
        NSString *cmtText = [NSString stringWithFormat:@"%@ : %@", username, content];
        _cellHeight += [cmtText boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:16]} context:nil].size.height + XMGMarin;
    }
    
    // 工具條
    _cellHeight += 35 + XMGMarin;
    
    return _cellHeight;
}

@end

在Controller,返回每行模型對應的行高(在模型裡,如果已經計算好,就會返回,若沒有計算行高,就會繼續執行計算行高)

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    XMGTopic * topic=self.topics[indexPath.row];
    return topic.cellHeight;
}

相關文章