1. 程式人生 > >Masonry適配——(7)UITableView中自定義UITableViewCell高度自適應及計算

Masonry適配——(7)UITableView中自定義UITableViewCell高度自適應及計算

在UITableView列表的使用中,因為在自定義的UITableViewCell中頁面相對複雜,所以會出現每一個cell都有不同的高度。這時候就需要根據實際內容進行cell的更新約束,其實說到底也就是哪些UI子檢視應該顯示,或隱藏,哪些UILabel標籤高度是這個數值,哪些UILabel標籤的高度是那個數值。

這樣想的話,我們在研發時就可以根據實際的資料Model進行控制UI的顯示,或隱藏,也就是更新UI的約束,以便設定UI的自適應顯示;其次再計算出實際的高度用於在UITableView的代理回撥方法中設定cell的高度。

先看下效果圖


示例程式碼(model類、cell類)

1、自定義資料model

#import <Foundation/Foundation.h>

@interface TableViewModel : NSObject

@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *content;
@property (nonatomic, strong) NSString *imageName;

@property (nonatomic, assign) CGFloat height;

@end
#import "TableViewModel.h"
#import "TableViewCell.h"

@implementation TableViewModel

- (CGFloat)height
{
    if (!_height)
    {
        // 呼叫cell的方法計算出高度
        _height = [TableViewCell heightTableCellWithModel:self];
    }
    
    return _height;
    
}

@end

2、自定義UITableViewCell

#import <UIKit/UIKit.h>
#import "TableViewModel.h"

static NSString *const identifierTableViewCell = @"TableViewCell";
static CGFloat const heightTableViewCell = (10.0 + 30.0 + 10.0 + 30.0 + 10.0 + 80.0 + 10.0);

@interface TableViewCell : UITableViewCell

@property (nonatomic, strong) TableViewModel *model;

+ (CGFloat)heightTableCellWithModel:(TableViewModel *)model;

@end
#import "TableViewCell.h"

static CGFloat const originXY = 10.0;
static CGFloat const heightTitle = 30.0;
static CGFloat const sizeImage = 80.0;

@interface TableViewCell ()

@property (nonatomic, assign) BOOL didSetupConstraints;

@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *detailLabel;
@property (nonatomic, strong) UIImageView *iconImageView;

@end

@implementation TableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
        [self setUI];
    }
    
    return self;
}

#pragma mark - 檢視

- (void)setUI
{
    [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.mas_equalTo(WidthScreen);
    }];
    
    self.contentView.backgroundColor = [UIColor purpleColor];
    
    self.titleLabel = [[UILabel alloc] init];
    [self.contentView addSubview:self.titleLabel];
    [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(originXY);
        make.left.mas_equalTo(originXY);
        make.right.mas_equalTo(-originXY);
        make.height.mas_equalTo(heightTitle);
    }];
    self.titleLabel.backgroundColor = [UIColor orangeColor];
    self.titleLabel.numberOfLines = 1;
    self.titleLabel.textAlignment = NSTextAlignmentLeft;
    self.titleLabel.textColor = [UIColor blackColor];
    
    UIView *currentView = self.titleLabel;
    
    self.detailLabel = [[UILabel alloc] init];
    [self.contentView addSubview:self.detailLabel];
    [self.detailLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(currentView.mas_bottom).offset(originXY);
        make.left.mas_equalTo(originXY);
        make.right.mas_equalTo(-originXY);

        // 根據實際情況計算高度
    }];
    self.detailLabel.font = [UIFont systemFontOfSize:12.0];
    self.detailLabel.backgroundColor = [UIColor redColor];
    self.detailLabel.textColor = [UIColor yellowColor];
    self.detailLabel.textAlignment = NSTextAlignmentLeft;
    // 多行設定
    self.detailLabel.numberOfLines = 0;
    self.detailLabel.preferredMaxLayoutWidth = (WidthScreen - originXY * 2);
    [self.detailLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
    
    currentView = self.detailLabel;
    
    // 圖示
    self.iconImageView = [[UIImageView alloc] init];
    [self.contentView addSubview:self.iconImageView];
    [self.iconImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(originXY);
        
        // 根據實際情況計算top,高
    }];
    self.iconImageView.backgroundColor = [UIColor greenColor];
}

#pragma mark - setter

- (void)setModel:(TableViewModel *)model
{
    if (model)
    {
        NSString *title = model.title;
        self.titleLabel.text = title;
        
        NSString *content = model.content;
        self.detailLabel.text = content;

        NSString *name = model.imageName;
        if (name && 0 != name.length)
        {
            UIImage *image = [UIImage imageNamed:name];
            self.iconImageView.image = image;
        }
        
        // 特別注意:如果設定了計算得出的高度約束,可能會造成文字顯示不全的情況
//        CGFloat heightText = [[self class] heightTextWithText:content];
//        [self.detailLabel mas_updateConstraints:^(MASConstraintMaker *make) {
//            make.height.mas_equalTo(heightText);
//        }];
        [self.iconImageView mas_updateConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self.detailLabel.mas_bottom).offset(originXY);
            make.size.mas_equalTo((name && 0 != name.length) ? CGSizeMake(sizeImage, sizeImage) : CGSizeZero);
        }];        
        [self layoutIfNeeded];
    }
}

+ (CGFloat)heightTableCellWithModel:(TableViewModel *)model
{
    // 初化高度
    CGFloat height = originXY + heightTitle;
    
    // 計算高度
    NSString *text = model.content;
    CGFloat heightText = [[self class] heightTextWithText:text];
    height += (originXY + heightText);
    
    // 圖片
    NSString *image = model.imageName;
    if (image && 0 != image.length)
    {
        height += (originXY + sizeImage);
    }
    
    height += originXY;
    NSLog(@"heightTableCell = %f, heightText = %f", height, heightText);
    
    return height;
}

+ (CGFloat)heightTextWithText:(NSString *)text
{
    // 計算高度
    CGSize size = [text boundingRectWithSize:CGSizeMake((WidthScreen - 2 * originXY), MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0]} context:nil].size;
    CGFloat heightText = size.height;
    
    return heightText;
}


@end

3、使用

#import "TableViewViewController.h"
#import "TableViewModel.h"
#import "TableViewCell.h"

@interface TableViewViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *mainTableView;
@property (nonatomic, strong) NSArray *array;

@end

@implementation TableViewViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.title = @"tableview";
    
    [self setUI];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - 檢視

- (void)setUI
{
    [self.mainTableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.equalTo(self.view);
    }];
}

#pragma mark - UITableViewDataSource, UITableViewDelegate

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewModel *model = self.array[indexPath.row];
    CGFloat height = model.height;
    
    NSLog(@"index = %ld, height:%@", indexPath.row, @(height));
    return height;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifierTableViewCell];
    if (cell == nil)
    {
        cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifierTableViewCell];
    }
    
    TableViewModel *model = self.array[indexPath.row];
    cell.model = model;    
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    TableViewModel *model = self.array[indexPath.row];
    NSLog(@"index = %ld, height = %@", indexPath.row, @(model.height));
}

#pragma mark - getter

- (UITableView *)mainTableView
{
    if (!_mainTableView)
    {
        _mainTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
        [self.view addSubview:_mainTableView];

        _mainTableView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.3];
        _mainTableView.delegate = self;
        _mainTableView.dataSource = self;
        
        // 設定為動態高度
        _mainTableView.estimatedRowHeight = UITableViewAutomaticDimension;
    }
    
    return _mainTableView;
}

- (NSArray *)array
{
    if (!_array)
    {
        NSArray *tmpArray = @[@"hello,我是iOS開發菜鳥。",@"很多iOS開發者應該都用過autolayout,如果用故事版和XIB的話非常好用,但是如果用純程式碼的方式寫的話就感覺這東西太囉嗦了,一點都不好用,還不如frame來得快,然而在公司專案中一般都是多人開發,因此還是以純程式碼寫的方式比較多。",@"一個國外大神推出了一套封裝好autolayout框架Masnory,Masonry是一個輕量級的佈局框架擁有自己的描述語法採用更優雅的鏈式語法封裝自動佈局簡潔明瞭並具有高可讀性而且同時支援iOS和Max OS X。",@"銀行不改變,就讓支付寶就改變銀行,至少讓銀行睡不著。",@"其實押金池這個是有很多爭議的,押金金融確實是令很多人高潮,也是摩拜被人議論得最多的一種盈利模式。押金池的本質是滾雪球,每個進來的使用者都要交299塊押金,那麼這些押金是否可以用於下一批單車的採購,然後投放到其他城市,接著又有人用,又有押金,如此滾雪球似的增長。但是如果是這樣的話,那麼很多行業都有押金機制,那是否都可以這樣玩呢?",@"其實在很多外行看來,幾千萬的註冊量確實是很厲害,但是這個註冊量的月活流量真的不怎麼值得去關注,畢竟像摩拜這樣的大公司,這些廣告費只能是算做一個外快錢,並不能作為支援企業活下去。",@"政府自己就有公益車,有什麼理由要補貼你,不競爭就不錯了吧?政府關係參考滴滴至今,大談這是優勢的人,應該再考慮考慮。",@"類似廣告,現階段只是快錢,單車背後的人群,量級,產品回報率,這些東西究竟能在廣告主品牌主值多少錢,打問號。",@"租金就是使用者租用單車平臺所得到的錢,一小時一塊錢,那這樣必須得有很高的使用頻率才能夠獲取高的租金,但是摩拜其運營就是一個無底洞,單單這些租金恐怕是成本也很難賺回來。",@"融資仍然是摩拜如今最賺錢的手段,在1月4好,摩拜已經完成了D輪融資,融資金額為2.15億美元,摩拜就是在不斷的融資中拓展了其資金鍊,使其能夠繼續的生存下去。",@"有人分析出來摩拜利用GPS來分析使用者的具體行為,通過統計使用者的行為包裝成資料賣給需要的大企業,但是這些資料具體參考價值也是使用者的活躍地區在哪幾個地方而已,具體價值還是有待研究的。",@"晚上跟公司領導和同事打麻將,散場後回到家,老婆問怎麼樣?我苦著臉說:“跟領導打牌能贏嗎?輸了五百多。”老婆聽後點了點頭,說:“下次別去打了,這次的錢就不從你下個月零用錢里扣了。”我爽快的答應了。第二天和老婆去菜場買菜,正巧遇到領導老婆,那老孃們一開口:“小律啊,你昨天手氣真是太好了!一摞三,贏了一千多啊!”額,我特麼差點沒被這句話給噎死....。",@"表姐大齡剩女一枚,昨天有人給她介紹物件,表姐去了。見面地點約在一家餐廳,男人又矮又醜,表姐看不上。“本來我想走的,後來……”表姐沒說完就害羞起來……我問:後來你被他的言談吸引沒走?表姐:他都走了,還點了一桌子菜,我怕浪費……",@"一天李靖對哪吒說,兒啊,你知道我為何總是對你不滿嗎?哪吒說難道是因為我太調皮了嗎。李靖一巴掌就扇了上去,你個小逼崽子在你娘肚子裡一待就是三年,急得為父手都能託塔了!哪吒母親一笑說,怪不得塔下面有一個洞呢。",@"我四肢發達,好鬥,一次在學校打傷一同學,去醫院花了好多錢,不敢跟家裡講,於是找同學借,一同班女同學借給我最多……一段時間後,她沒錢吃飯,催我還錢,我沒辦法,天天帶她到朋友那裡蹭飯……不久,她就變成我女朋友了…………終於,她將我帶到她家見父母,居然遇到被我打傷的那個學生,他開口說:姐夫好!。",@"羅納爾迪尼奧,個人榮譽:世界足球先生(2004,2005),金球獎(2005),南美足球先生(2013),金足獎(2009),國家隊榮譽:美洲盃冠軍(1999),世界盃冠軍(2002),聯合會杯冠軍(2005),俱樂部榮譽:西甲冠軍(2004,2005),歐洲冠軍聯賽冠軍(2006),南美解放者杯冠軍(2013),西班牙超級盃冠軍(2004,2005)。",@"自從360免費後,一下子電腦的病毒似乎都消失了。",@"周鴻禕在一次節目上透露開始做360時,自己把別人賣到200元的軟體,自己賣到25元,發現還是做不過別人。因為自己不是第一個做了,可能是第四個,第五個,市場都被別人佔領了。",@"哪怕改變一個模式也是重新。",@"你現在做什麼行業呢?是否能把自己賣的東西和360一樣做到免費,而從其他地方賺錢?這是給每個創業者啟發的。",@"一位錘子科技前員工在微博上釋出長文《我為什麼離開錘子科技》,向網友公開他離開錘子科技的原因。這位錘子科技前員工主要敘述了三個問題:一個是對上級的管理方式的不滿,經常催促任務和越過中間級別佈置詳細任務;另一個是公司福利減少,沒有中秋紅包;最後一個是錘子科技的所謂彈性工作制度的問題,作者認為“所謂的彈性工作制就是一種可以讓你每天加班到10點但是不用付薪水的制度。",@"馬雲曾經說過:“員工的離職原因很多,只有兩點最真實: 1、錢,沒給到位; 2、心,委屈了。”",@"華為的薪酬主要包括三部分:工資、獎金和分紅;當然如若外派國外,還有外派補助+艱苦補助。華為的這種高薪政策及配“股票”的政策讓員工極具“主人翁”意識:在公司大發展時,一起享受公司發展帶來的紅利;在公司困難時,能迎難而上與公司同舟共濟。",@"華為不僅“捨得花錢”,更重要是“懂得分錢”,設計了一套“定崗定薪,易崗易薪”,意思就是工資薪酬是根據崗位來設定。",@"很多時候我們過高估計了機遇的力量,低估了規劃的重要性,不明確的樂觀主義者只知道未來越來越好,卻不知道究竟多好,因此不去制定具體計劃。他想在未來獲利,但是卻認為沒有必要制定具體規劃。"];
        
        NSMutableArray *modelArray = [[NSMutableArray alloc] init];
        for (int i = 0; i < tmpArray.count; i++)
        {
            TableViewModel *model = [[TableViewModel alloc] init];
            model.title = [NSString stringWithFormat:@"第 %@ 個cell", @(i)];
            model.content = tmpArray[i];
            model.imageName = (arc4random() % 10 % (i + 1) == 2 ? @"tianshi.png" : @"futou.png");
            if (i % 3 == 1)
            {
                model.imageName = nil;
            }
            
            [modelArray addObject:model];
        }
        
        _array = [[NSArray alloc] initWithArray:modelArray];
    }
    
    return _array;
}

@end