zoukankan      html  css  js  c++  java
  • iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

    一、纯代码自定义不等高cell


    废话不多说,直接来看下面这个例子
    先来看下微博的最终效果

    首先创建一个继承UITableViewController的控制器
    @interface ViewController : UITableViewController
    创建一个cell模型
    @interface XMGStatusCell : UITableViewCell
    再创建微博的数据模型
    @interface XMGStatus : NSObject

    纯代码自定义不等高cell

    和前面等高cell的思路是一样的
    1、创建子控件
    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;
    2、布局子控件
    等高与不等高的区别:不等高要动态的计算(lable或者image)的高度

    // 计算不换行文字所占据的尺寸
    NSDictionary *nameAttrs = @{NSFontAttributeName : XMGNameFont};
    CGSize nameSize = [self.status.name sizeWithAttributes:nameAttrs];


    // 计算换行文字所占据的尺寸
    // CGFloat textH = [self.status.text sizeWithFont:XMGTextFont constrainedToSize:textMaxSize].height;
    上面这个方法在ios7.0(ios2.0-7.0)已经过时了 进入头文件系统会提示你用最新的方法 “Use -boundingRectWithSize:options:attributes:context:”

    NSDictionary *textAttrs = @{NSFontAttributeName : XMGTextFont};
    CGFloat textH = [self.status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;

    3、设置数据
    重写模型数据的get方法

    二、计算行高


    这时运行程序会发现所有cell的高度都一样

    而且等于storyboard内cell的高度


    因为从头到尾我们都没有用代码设置过高度,那么在哪里设置呢?
    方案:在heightForRowAtIndexPath:方法调用之前将所有cell的高度计算清楚

     1 /**
     2  *  返回每一行cell的具体高度
     3  */
     4 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
     5 {
     6     XMGStatus *status = self.statuses[indexPath.row];
     7     
     8     CGFloat margin = 10;
     9     CGFloat cellHeight = 0;
    10     
    11     // 头像
    12     CGFloat iconX = margin;
    13     CGFloat iconY = margin;
    14     CGFloat iconWH = 30;
    15     CGRect iconImageViewFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
    16     
    17     // 文字
    18     CGFloat textX = iconX;
    19     CGFloat textY = CGRectGetMaxY(iconImageViewFrame) + margin;
    20     CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 * textX;
    21     CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT);
    22     NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:14]};
    23     CGFloat textH = [status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;
    24     CGRect text_labelFrame = CGRectMake(textX, textY, textW, textH);
    25     
    26     // 配图
    27     if (status.picture) {
    28         CGFloat pictureWH = 100;
    29         CGFloat pictureX = textX;
    30         CGFloat pictureY = CGRectGetMaxY(text_labelFrame) + margin;
    31         CGRect pictureImageViewFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH);
    32         
    33         cellHeight = CGRectGetMaxY(pictureImageViewFrame);
    34     } else {
    35         cellHeight = CGRectGetMaxY(text_labelFrame);
    36     }
    37     
    38     cellHeight += margin;
    39     
    40     return cellHeight;
    41 }

    这样就能达到案例的效果了

    虽然能解决上面的问题,但这样的代码看起来很垃圾,因为控制器知道的太多了,计算高度最好在你拿到数据的时候就已经计算好了,只要拿着用就行了
    我们可以把计算高度封装到数据模型XMGStatus里

      1 /**
      2  *  返回每一行cell的具体高度
      3  */
      4 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
      5 {
      6     XMGStatus *status = self.statuses[indexPath.row];
      7     return status.cellHeight;
      8 }
      9 
     10 /*************XMGStatus*****************/
     11 #import <UIKit/UIKit.h>
     12 
     13 @interface XMGStatus : NSObject
     14 /**** 文字图片数据 ****/
     15 /** 姓名 */
     16 @property (nonatomic, copy) NSString *name;
     17 /** 文本 */
     18 @property (nonatomic, copy) NSString *text;
     19 /** 头像 */
     20 @property (nonatomic, copy) NSString *icon;
     21 /** 配图 */
     22 @property (nonatomic, copy) NSString *picture;
     23 /** 是否为会员 */
     24 @property (nonatomic, assign) BOOL vip;
     25 
     26 /**** frame数据 ****/
     27 /** 头像的frame */
     28 @property (nonatomic, assign) CGRect iconFrame;
     29 /** 昵称的frame */
     30 @property (nonatomic, assign) CGRect nameFrame;
     31 /** 会员的frame */
     32 @property (nonatomic, assign) CGRect vipFrame;
     33 /** 文字的frame */
     34 @property (nonatomic, assign) CGRect textFrame;
     35 /** 配图的frame */
     36 @property (nonatomic, assign) CGRect pictureFrame;
     37 /** cell的高度 */
     38 @property (nonatomic, assign) CGFloat cellHeight;
     39 
     40 @end
     41 
     42 #import "XMGStatus.h"
     43 
     44 @implementation XMGStatus
     45 - (CGFloat)cellHeight
     46 {
     47     if (_cellHeight == 0) {
     48         CGFloat margin = 10;
     49         
     50         // 头像
     51         CGFloat iconX = margin;
     52         CGFloat iconY = margin;
     53         CGFloat iconWH = 30;
     54         self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
     55         
     56         // 昵称(姓名)
     57         CGFloat nameY = iconY;
     58         CGFloat nameX = CGRectGetMaxX(self.iconFrame) + margin;
     59         // 计算文字所占据的尺寸
     60         NSDictionary *nameAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:17]};
     61         CGSize nameSize = [self.name sizeWithAttributes:nameAttrs];
     62         self.nameFrame = (CGRect){{nameX, nameY}, nameSize};
     63         
     64         // 会员图标
     65         if (self.vip) {
     66             CGFloat vipW = 14;
     67             CGFloat vipH = nameSize.height;
     68             CGFloat vipY = nameY;
     69             CGFloat vipX = CGRectGetMaxX(self.nameFrame) + margin;
     70             self.vipFrame = CGRectMake(vipX, vipY, vipW, vipH);
     71         }
     72         
     73         // 文字
     74         CGFloat textX = iconX;
     75         CGFloat textY = CGRectGetMaxY(self.iconFrame) + margin;
     76         CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 * textX;
     77         CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT);
     78         NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:14]};
     79         CGFloat textH = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;
     80         self.textFrame = CGRectMake(textX, textY, textW, textH);
     81         
     82         // 配图
     83         if (self.picture) {
     84             CGFloat pictureWH = 100;
     85             CGFloat pictureX = textX;
     86             CGFloat pictureY = CGRectGetMaxY(self.textFrame) + margin;
     87             self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH);
     88             
     89             _cellHeight = CGRectGetMaxY(self.pictureFrame);
     90         } else {
     91             _cellHeight = CGRectGetMaxY(self.textFrame);
     92         }
     93         _cellHeight += margin;
     94     }
     95     return _cellHeight;
     96 }
     97 @end
     98 
     99 
    100 那么我们在XMGStatusCell.m布局子控件就可以这样写
    101 /**
    102  *  布局子控件
    103  */
    104 - (void)layoutSubviews
    105 {
    106     [super layoutSubviews];
    107     
    108     self.iconImageView.frame = self.status.iconFrame;
    109     self.nameLabel.frame = self.status.nameFrame;
    110     self.vipImageView.frame = self.status.vipFrame;
    111     self.text_label.frame = self.status.textFrame;
    112     self.pictureImageView.frame = self.status.pictureFrame;
    113 }
    114 当然也可以直接在设置控件数据时布局(因为在给cell赋值时使用了setStatus:(XMGStatus *)status方法)
    115 /**
    116  *  设置子控件显示的数据
    117  */
    118 - (void)setStatus:(XMGStatus *)status
    119 {
    120     _status = status;
    121     
    122     self.iconImageView.image = [UIImage imageNamed:status.icon];
    123     self.nameLabel.text = status.name;
    124     self.text_label.text = status.text;
    125     
    126     if (status.isVip) {
    127         self.vipImageView.hidden = NO;
    128         self.nameLabel.textColor = [UIColor orangeColor];
    129     } else {
    130         self.vipImageView.hidden = YES;
    131         self.nameLabel.textColor = [UIColor blackColor];
    132     }
    133     
    134     if (status.picture) {
    135         self.pictureImageView.hidden = NO;
    136         self.pictureImageView.image = [UIImage imageNamed:status.picture];
    137     } else {
    138         self.pictureImageView.hidden = YES;
    139     }
    140     
    141     self.iconImageView.frame = status.iconFrame;
    142     self.nameLabel.frame = status.nameFrame;
    143     self.vipImageView.frame = status.vipFrame;
    144     self.text_label.frame = status.textFrame;
    145     self.pictureImageView.frame = status.pictureFrame;
    146 }

    运行程序效果就和案例一样

    三、自定义不等高cell-storyboard(无配图)


    除了代码自定义不等高cell,我们还可以直接用storyboard来自定义cell,相对来说就 简单很多,我们先来看下没有配图的情况
    1、首先创建一个cell模型,设置好约束

    2、创建一个一个cell模型类,继承UITableViewCell,并且对应着cell模型连线,设置数据

     1 /*******************XMGStatusCell.m*********************/
     2 #import "XMGStatusCell.h"
     3 #import "XMGStatus.h"
     4 
     5 @interface XMGStatusCell()
     6 /** 头像 */
     7 @property (nonatomic, weak) IBOutlet UIImageView *iconImageView;
     8 /** 名称 */
     9 @property (nonatomic, weak) IBOutlet UILabel *nameLabel;
    10 /** 会员图标 */
    11 @property (nonatomic, weak) IBOutlet UIImageView *vipImageView;
    12 /** 文字 */
    13 @property (nonatomic, weak) IBOutlet UILabel *text_label;
    14 @end
    15 
    16 @implementation XMGStatusCell
    17 
    18 /**
    19  *  设置子控件显示的数据
    20  */
    21 - (void)setStatus:(XMGStatus *)status
    22 {
    23     _status = status;
    24     
    25     self.iconImageView.image = [UIImage imageNamed:status.icon];
    26     self.nameLabel.text = status.name;
    27     self.text_label.text = status.text;
    28     
    29     if (status.vip) {
    30         self.vipImageView.hidden = NO;
    31         self.nameLabel.textColor = [UIColor orangeColor];
    32     } else {
    33         self.vipImageView.hidden = YES;
    34         self.nameLabel.textColor = [UIColor blackColor];
    35     }
    36 }
    37 @end

    3、创建数据模型类XMGStatus,在控制器实现数据源方法;
    值得一提的是在返回cell之前必须先告诉tableView所有cell的估算高度,那么可以在viewDidLoad中写上下面这句:
    self.tableView.estimatedRowHeight = 44; // 估算每一行的高度
    而且:必须告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
    self.tableView.rowHeight = UITableViewAutomaticDimension;

    iOS8开始:self-sizing

    如果没写这两句,运行出来的高度都是不对的

     1 #import "ViewController.h"
     2 #import "XMGStatus.h"
     3 #import "MJExtension.h"
     4 #import "XMGStatusCell.h"
     5 
     6 @interface ViewController ()
     7 /** 微博数据 */
     8 @property (nonatomic, strong) NSArray *statuses;
     9 @end
    10 
    11 @implementation ViewController
    12 
    13 NSString *ID = @"status";
    14 
    15 - (NSArray *)statuses
    16 {
    17     if (!_statuses) {
    18         _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
    19     }
    20     return _statuses;
    21 }
    22 
    23 - (void)viewDidLoad {
    24     [super viewDidLoad];
    25     
    26     // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
    27     self.tableView.rowHeight = UITableViewAutomaticDimension;
    28     // 告诉tableView所有cell的估算高度
    29     self.tableView.estimatedRowHeight = 44;
    30 }
    31 
    32 #pragma mark - <数据源>
    33 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    34 {
    35     return self.statuses.count;
    36 }
    37 
    38 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    39 {
    40     XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    41     
    42     cell.status = self.statuses[indexPath.row];
    43     
    44     return cell;
    45 }
    46 @end

    四、自定义不等高cell-storyboard(有配图)


    有图片和无图片其实一样,重点在于如何自动计算行高
    1、首先,cell模型里再添加imageView(配图)
    2、然后,在 XMGStatusCell.m 内添加三个属性
    /** 配图 */
    @property (nonatomic, weak) IBOutlet UIImageView *pictureImageView;
    /** 配图的高度约束 */
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureHeight;
    /** 配图底部间距约束 */
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureBottom;
    3、设置数据

    XMGStatusCell.h

    1 #import <UIKit/UIKit.h>
    2 @class XMGStatus;
    3 
    4 @interface XMGStatusCell : UITableViewCell
    5 /** 模型数据 */
    6 @property (nonatomic, strong) XMGStatus *status;
    7 @end

    XMGStatusCell.m

     1 /**
     2  *  设置子控件显示的数据
     3  */
     4 - (void)setStatus:(XMGStatus *)status
     5 {
     6     _status = status;
     7     
     8     self.iconImageView.image = [UIImage imageNamed:status.icon];
     9     self.nameLabel.text = status.name;
    10     self.text_label.text = status.text;
    11     
    12     if (status.vip) {
    13         self.vipImageView.hidden = NO;
    14         self.nameLabel.textColor = [UIColor orangeColor];
    15     } else {
    16         self.vipImageView.hidden = YES;
    17         self.nameLabel.textColor = [UIColor blackColor];
    18     }
    19     
    20     // 设置配图数据
    21     if (status.picture) { // 有配图
    22         self.pictureHeight.constant = 100;
    23         self.pictureBottom.constant = 10;
    24         self.pictureImageView.image = [UIImage imageNamed:status.picture];
    25     } else { // 没有配图
    26         // 设置图片高度为0
    27         self.pictureHeight.constant = 0;
    28         // 设置图片底部间距为0
    29         self.pictureBottom.constant = 0;
    30     }
    31 }

    XMGStatus.h

     1 #import <UIKit/UIKit.h>
     2 
     3 @interface XMGStatus : NSObject
     4 /**** 文字图片数据 ****/
     5 /** 姓名 */
     6 @property (nonatomic, copy) NSString *name;
     7 /** 文本 */
     8 @property (nonatomic, copy) NSString *text;
     9 /** 头像 */
    10 @property (nonatomic, copy) NSString *icon;
    11 /** 配图 */
    12 @property (nonatomic, copy) NSString *picture;
    13 /** 是否为会员 */
    14 @property (nonatomic, assign) BOOL vip;
    15 @end

    XMGStatus.m

    1 #import "XMGStatus.h"
    2 
    3 @implementation XMGStatus
    4 
    5 @end
     1 #import "ViewController.h"
     2 #import "XMGStatus.h"
     3 #import "MJExtension.h"
     4 #import "XMGStatusCell.h"
     5 
     6 @interface ViewController ()
     7 /** 微博数据 */
     8 @property (nonatomic, strong) NSArray *statuses;
     9 @end
    10 
    11 @implementation ViewController
    12 
    13 NSString *ID = @"status";
    14 
    15 - (NSArray *)statuses
    16 {
    17     if (!_statuses) {
    18         _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
    19     }
    20     return _statuses;
    21 }
    22 
    23 - (void)viewDidLoad {
    24     [super viewDidLoad];
    25     
    26     // iOS8开始:self-sizing
    27     
    28     // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
    29     self.tableView.rowHeight = UITableViewAutomaticDimension;
    30     // 告诉tableView所有cell的估算高度
    31     self.tableView.estimatedRowHeight = 44;
    32 }
    33 
    34 #pragma mark - <数据源>
    35 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    36 {
    37     return self.statuses.count;
    38 }
    39 
    40 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    41 {
    42     XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    43     
    44     cell.status = self.statuses[indexPath.row];
    45     
    46     return cell;
    47 }
    48 
    49 
    50 @end

    运行结果

    五、最终代码


    在以前ios开发中,经常会发现程序在运行前屏幕会黑屏一会,这是为什么呢?我们这里也存在类似问题,因为在程序运行前会要显示一部分cell,苹果会提前将每一个cell的高度都算好,而且内部一些运行也需要调用这个方法,总之,当我们cell特别多时,这个方法的调用会特别频繁,就会出现黑屏一会的情况
    1、解决方案:
    告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数)
    self.tableView.estimatedRowHeight = 200;

    有些公司的项目还是以前的老项目,没有用到IOS8,那么计算高度可以用下面这种方法解决

    2、返回高度
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    上面这两个方法调用顺序依次是先计算高度再返回cell,也就是说应该在返回cell前将高度算好

      1 #import "ViewController.h"
      2 #import "XMGStatus.h"
      3 #import "MJExtension.h"
      4 #import "XMGStatusCell.h"
      5 
      6 @interface ViewController ()
      7 /** 微博数据 */
      8 @property (nonatomic, strong) NSArray *statuses;
      9 @end
     10 
     11 @implementation ViewController
     12 
     13 NSString *ID = @"status";
     14 
     15 - (NSArray *)statuses
     16 {
     17     if (!_statuses) {
     18         _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
     19     }
     20     return _statuses;
     21 }
     22 
     23 - (void)viewDidLoad {
     24     [super viewDidLoad];
     25     // 告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数)
     26     self.tableView.estimatedRowHeight = 200;
     27 }
     28 
     29 #pragma mark - <数据源>
     30 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
     31 {
     32     return self.statuses.count;
     33 }
     34 
     35 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     36 {
     37     XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
     38     
     39     cell.status = self.statuses[indexPath.row];
     40     
     41     return cell;
     42 }
     43 
     44 #pragma mark - <代理方法>
     45 XMGStatusCell *cell;
     46 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
     47 {
     48     
     49     // 创建一个cell(cell的作用:根据模型数据布局所有的子控件,进而计算出cell的高度)
     50     if (!cell) {
     51         cell = [tableView dequeueReusableCellWithIdentifier:ID];
     52     }
     53     // 设置模型数据
     54     cell.status = self.statuses[indexPath.row];
     55     return cell.height;
     56 }
     57 @end
     58 /****************** XMGStatusCell.m **********************/
     59 #import "XMGStatusCell.h"
     60 #import "XMGStatus.h"
     61 
     62 @interface XMGStatusCell()
     63 /** 头像 */
     64 @property (nonatomic, weak) IBOutlet UIImageView *iconImageView;
     65 /** 名称 */
     66 @property (nonatomic, weak) IBOutlet UILabel *nameLabel;
     67 /** 会员图标 */
     68 @property (nonatomic, weak) IBOutlet UIImageView *vipImageView;
     69 /** 文字 */
     70 @property (nonatomic, weak) IBOutlet UILabel *text_label;
     71 /** 配图 */
     72 @property (nonatomic, weak) IBOutlet UIImageView *pictureImageView;
     73 @end
     74 
     75 @implementation XMGStatusCell
     76 
     77 - (void)awakeFromNib
     78 {
     79     // 如果lable有自动换行的情况时
     80     // 手动设置文字的最大宽度(目的是:让label知道自己文字的最大宽度,进而能够计算出自己的frame)
     81     self.text_label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;
     82 }
     83 
     84 /**
     85  *  设置子控件显示的数据
     86  */
     87 - (void)setStatus:(XMGStatus *)status
     88 {
     89     _status = status;
     90     
     91     self.iconImageView.image = [UIImage imageNamed:status.icon];
     92     self.nameLabel.text = status.name;
     93     self.text_label.text = status.text;
     94     
     95     if (status.vip) {
     96         self.vipImageView.hidden = NO;
     97         self.nameLabel.textColor = [UIColor orangeColor];
     98     } else {
     99         self.vipImageView.hidden = YES;
    100         self.nameLabel.textColor = [UIColor blackColor];
    101     }
    102     
    103     // 设置配图数据
    104     if (status.picture) { // 有配图
    105         self.pictureImageView.hidden = NO;
    106         self.pictureImageView.image = [UIImage imageNamed:status.picture];
    107     } else { // 没有配图
    108         self.pictureImageView.hidden = YES;
    109     }
    110 }
    111 
    112 - (CGFloat)height
    113 {
    114     // 强制布局cell内部的所有子控件(label根据文字多少计算出自己最真实的尺寸)
    115     [self layoutIfNeeded];
    116     
    117     // 计算cell的高度
    118     if (self.status.picture) {
    119         return CGRectGetMaxY(self.pictureImageView.frame) + 10;
    120     } else {
    121         return CGRectGetMaxY(self.text_label.frame) + 10;
    122     }
    123 }
    124 @end

    将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    Neko's loop HDU-6444(网络赛1007)
    Parameters
    SETLOCAL
    RD / RMDIR Command
    devenv 命令用法
    Cannot determine the location of the VS Common Tools folder.
    'DEVENV' is not recognized as an internal or external command,
    How to change Visual Studio default environment setting
    error signing assembly unknown error
    What is the Xcopy Command?:
  • 原文地址:https://www.cnblogs.com/chglog/p/4672081.html
Copyright © 2011-2022 走看看