IOS的UITableView

UITableView

概述

1 UITableView
2     一般用来展示表格数据、可以滚动(继承自UIScrollView).性能极佳
3 UITableView分两种样式:
4     Plain,不分组的样式
5     Grouped,分组的样式
6     UITableView默认为Plain样式,改为Grouped后实现分组,如果再改回Plain 那么在滚动的时候 上一层的头标签就会一直作为索引显示,类似于通讯录中的A B的显示方式

使用:

 1 如果要使用UITableView 那么需要实现UITableViewDataSource协议后重写 2 
 3 //展现数据有几组,当不实现这个方法时,默认为一组
 4 -(NSInteger)numberOfSectionsInTableView:(UITableView *) tableView
 5 
 6 //一组有几行
 7 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) section
 8 
 9 //每行显示什么内容
10 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
11 
12 //实现右侧的索引栏
13 -(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
14 (获取group数组中的每个对象的title值,并返回到一个NSArray中
15 [self.groups valueForKeyPath:@"title"])
16 
17 //通过代理坚挺cell的点击事件
18 //选中某行
19 -(void)tableView:(UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath
20 
21 //取消选中某行
22 -(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath;
23 
  //设置组标题
  -(NSString *)tableView :(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

  //设置组描述
  -(NSString *)tableView :(UITableView *)tableView titleForFooterInsection:(NSInteger)section;


24 //修改每行的行高 25 1.如果tableView的行高一样,那么就在控制器的viewDidLoad中统一设置行高tableView.rowHeight(这种方法比较高效) 26 2.通过代理方法实现: 27 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath(低效)

  //把UITableView中的最后一行的数据滚动到最上面
      NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0];
     [self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
 

 UITableView的常见属性

1 rowHeight 可以统一设置所有行的高度
2 
3 separatorColor 分割线的颜色
4 separatorStyle 分割线的样式
5 
6 tableHeaderView 一般可以放广告
7 tableFooterView  一般可以放加载更多

Cell的常见属性

 1 imageView
 2 textLabel
 3 detailTextLabel
 4 
 5 accessoryType
 6 accessoryView
 7 
 8 backgroundColor,设置单元格的背景颜色
 9 
10 backgroundView 可以利用这个属性来设置单元格的背景图片,指定一个UIImageView就可以了
11 
12 selectedBackgroundView 当某行被选中的时候的背景

 单元格Cell的重用

1 //注意:只适用于单元格样式一致的时候
2 //单元格重用的基本方法
3 //1.声明一个 静态的重用ID (只所以声明静态是为了节省控制器不断的释放,创建成员对象)
4 //2.根据重用ID去缓存池中获取对应的cell对象
5 //3.如果没有获取到,就创建一个,如果获取到了就直接设置单元格内容
6 //4.返回单元格

注意:当使用自定义的cell的时候 是无法通过 dequeueReusableCellWithIdentifier:ID的方式来指定ID的

所以需要在布局文件中进行设置

代码示例:

1.  .plist的数据结构

  

 模型代码:

 1.1)CZGroup.h 

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface GZGroup :NSObject
 4 
 5 @property (nonatomic ,copy) NSString *titile;
 6 @property (nonatomic, strong) NSArray *cars;
 7 
 8 -(instancetype)initWithDict :(NSDictionary *)dict;
 9 +(instancetype)groupWithDict:(NSDictionary *)dict;
10 
11 @end

  GZGroup.m

#import "CZGroup.h"
#import "CZCar.h"
@implementation CZGroup

- (instancetype)initWithDict:(NSDictionary *)dict
{
    if (self = [super init]) {
        //        self.title = dict[@"title"];
        //        self.cars = dict[@"cars"];

        [self setValuesForKeysWithDictionary:dict];
        
        // 当有模型嵌套的时候需要手动把字典转成模型
        // 创建一个用来保存模型的数组
        NSMutableArray *arrayModels = [NSMutableArray array];
        // 手动做一下字典转模型
        for (NSDictionary *item_dict in dict[@"cars"]) {
            CZCar *model = [CZCar carWithDict:item_dict];
            [arrayModels addObject:model];
        }
        self.cars = arrayModels;
    }
    return self;
}
+ (instancetype)groupWithDict:(NSDictionary *)dict
{
    return [[self alloc] initWithDict:dict];
}
@end

控制器代码,重用cell需要这是一个ID

 1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 2 {
 3     // 1. 获取模型数据
 4     // 根据组的索引获取对应的组的模型
 5     CZGroup *group = self.groups[indexPath.section];
 6     // 根据当前行的索引, 获取对应组的对应行的车
 7     CZCar *car = group.cars[indexPath.row];
 8     
 9     
10     
11     // 2. 创建单元格
12     // 2.1 声明一个重用ID
13     static NSString *ID = @"car_cell";
14     // 2.2 根据重用ID去缓存池中获取对应的cell对象
15     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
16     // 2.3 如果没有获取到, 那么就创建一个
17     if (cell == nil) {
18         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
19     }
20     
21     // 3. 设置单元格内容
22     cell.imageView.image = [UIImage imageNamed:car.icon];
23     cell.textLabel.text = car.name;
24     
25     // 4. 返回单元格
26     return cell;
27 }

 自定义Cell

案例一:团购

Viewontroller.m

  1 #import "ViewController.h"
  2 #import "CZGoods.h"
  3 #import "CZGoodsCell.h"
  4 #import "CZFooterView.h"
  5 #import "CZHeaderView.h"
  6 
  7 @interface ViewController () <UITableViewDataSource, CZFooterViewDelegate>
  8 
  9 // 用来存储所有的团购商品的数据
 10 @property (nonatomic, strong) NSMutableArray *goods;
 11 
 12 @property (weak, nonatomic) IBOutlet UITableView *tableView;
 13 @end
 14 
 15 @implementation ViewController
 16 
 17 
 18 #pragma mark - 懒加载数据
 19 - (NSMutableArray *)goods
 20 {
 21     if (_goods == nil) {
 22         NSString *path = [[NSBundle mainBundle] pathForResource:@"tgs.plist" ofType:nil];
 23         NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
 24         NSMutableArray *arrayModels = [NSMutableArray array];
 25         for (NSDictionary *dict in arrayDict) {
 26             CZGoods *model = [CZGoods goodsWithDict:dict];
 27             [arrayModels addObject:model];
 28         }
 29         _goods = arrayModels;
 30     }
 31     return _goods;
 32 }
 33 
 34 #pragma mark - 数据源方法
   //返回有UITableView 有多少个组,默认为1,当组为1时 可以忽略不写 35 //- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 36 //{ 37 // return 1; 38 //} 39 40 //返回组内有多少行数据 41 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 42 { 43 return self.goods.count; 44 } 45 46 //返回Cell对象 47 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 48 { 49 // 1. 获取模型数据 50 CZGoods *model = self.goods[indexPath.row]; 51 52 // 2. 创建单元格 53 // 通过xib的方式来创建单元格 54 CZGoodsCell *cell = [CZGoodsCell goodsCellWithTableView:tableView]; 55 56 57 // 3. 把模型数据设置给单元格 58 // 在控制器中直接为cell的每个子控件赋值数据造成的问题: 59 // 1. 控制器强依赖于Cell, 一旦cell内部的子控件发生了变化, 那么控制器中的代码也得改(这就造成了紧耦合) 60 // 2. cell的封装不够完整, 凡是用到这个cell的地方, 每次都要编写为cell的子控件依次赋值的语句,比如:cell.xxx = model.title; 61 // 3. 解决: 直接把模型传递给自定义Cell, 然后在自定义cell内部解析model中的数据赋值给自定义cell内部的子控件。 62 cell.goods = model; 63 64 // 4.返回单元格 65 return cell; 66 } 67 68 69 70 #pragma mark - 隐藏状态栏 71 - (BOOL)prefersStatusBarHidden 72 { 73 return YES; 74 } 75 80 - (void)viewDidLoad { 81 [super viewDidLoad]; 82 self.tableView.rowHeight = 44; 83 84 // 设置UITableView的footerView 85 // UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; 86 // 87 // btn.backgroundColor = [UIColor redColor]; 88 // btn.frame = CGRectMake(20, 50, 30, 100); 89 // // tableView的footerView的特点: 只能修改x和height的值, Y 和 width不能改。 90 // self.tableView.tableFooterView = btn; 91 92 93 94 // 通过Xib设置UITableView的footerView 95 CZFooterView *footerView = [CZFooterView footerView]; 96 // 设置footerView的代理 97 footerView.delegate = self; 98 self.tableView.tableFooterView = footerView; 99 100 101 // 创建Header View 102 CZHeaderView *headerView = [CZHeaderView headerView]; 103 self.tableView.tableHeaderView = headerView; 104 105 106 107 108 109 } 110 111 #pragma mark - CZFooterView的代理方法 112 113 - (void)footerViewUpdateData:(CZFooterView *)footerView 114 { 115 // 3. 增加一条数据 116 117 118 // 3.1 创建一个模型对象 119 CZGoods *model = [[CZGoods alloc] init]; 120 model.title = @"驴肉火烧"; 121 model.price = @"6.0"; 122 model.buyCount = @"1000"; 123 model.icon = @"37e4761e6ecf56a2d78685df7157f097"; 124 125 // 3.2 把模型对象加到控制器的goods集合当中 126 [self.goods addObject:model]; 127 128 // 4. 刷新UITableView 129 [self.tableView reloadData]; 130 131 // // 局部刷新(只适用于UITableView总行数没有发生变化的情况) 132 // NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0]; 133 // [self.tableView reloadRowsAtIndexPaths:@[idxPath] withRowAnimation:UITableViewRowAnimationLeft]; 134 135 136 // 5. 把UITableView中的最后一行的数据滚动到最上面 137 NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0]; 138 [self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 139 } 140 141 - (void)didReceiveMemoryWarning { 142 [super didReceiveMemoryWarning]; 143 // Dispose of any resources that can be recreated. 144 } 145 146 @end

自定义的CZGoodsCell对象

CZGoodsCell.h

//导入 UIKit/UIKit.h 文件
#import <UIKit/UIKit.h>
@class CZGoods;
//继承 UITableViewCell
@interface CZGoodsCell : UITableViewCell

@property (nonatomic, strong) CZGoods *goods;

// 封装一个创建自定义Cell的方法
+ (instancetype)goodsCellWithTableView:(UITableView *)tableView;
@end

CZGoodsCell.m

 1 #import "CZGoodsCell.h"
 2 #import "CZGoods.h"
 3 
 4 @interface CZGoodsCell ()
 5 @property (weak, nonatomic) IBOutlet UIImageView *imgViewIcon;
 6 @property (weak, nonatomic) IBOutlet UILabel *lblTitle;
 7 @property (weak, nonatomic) IBOutlet UILabel *lblPrice;
 8 @property (weak, nonatomic) IBOutlet UILabel *lblBuyCount;
 9 
10 @end
11 
12 
13 @implementation CZGoodsCell
14 
15 + (instancetype)goodsCellWithTableView:(UITableView *)tableView
16 {
17     static NSString *ID = @"goods_cell";
18     CZGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
19     if (cell == nil) {
20         cell = [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject];
21     }
22     return cell;
23 }
24 
25 
26 - (void)setGoods:(CZGoods *)goods
27 {
28     _goods = goods;
29     // 把模型的数据设置给子控件
30     self.imgViewIcon.image = [UIImage imageNamed:goods.icon];
31     self.lblTitle.text = goods.title;
32     self.lblPrice.text = [NSString stringWithFormat:@"¥ %@", goods.price];
33     self.lblBuyCount.text = [NSString stringWithFormat:@"%@ 人已购买", goods.buyCount];
34 }
35 
36 - (void)awakeFromNib {
37     // Initialization code
38 }
39 
40 - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
41     [super setSelected:selected animated:animated];
42 
43     // Configure the view for the selected state
44 }
45 
46 @end

尾部的加载更多按钮

CZFooterView.h

 1 #import <UIKit/UIKit.h>
 2 @class CZFooterView;
 3 
  //设置一个代理对象 4 @protocol CZFooterViewDelegate <NSObject, UIScrollViewDelegate>
  //提示用户使用这个代理的时候必须实现下面的代理方法 5 @required 6 - (void)footerViewUpdateData:(CZFooterView *)footerView; 7 @end 8 9 @interface CZFooterView : UIView 10 11 + (instancetype)footerView; 12 @property (nonatomic, weak) id<CZFooterViewDelegate> delegate; 13 @end

CZFooterView.m

 1 #import "CZFooterView.h"
 2 
 3 @interface CZFooterView ()
 4 @property (weak, nonatomic) IBOutlet UIButton *btnLoadMore;
 5 @property (weak, nonatomic) IBOutlet UIView *waitingView;
 6 - (IBAction)btnLoadMoreClick;
 7 @end
 8 
 9 
10 @implementation CZFooterView
11 
12 + (instancetype)footerView
13 {
14     CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@"CZFooterView" owner:nil options:nil] lastObject];
15     return footerView;
16 }
17 
18 
19 /**
20 *  加载更多按钮的单击事件
21 */
22 - (IBAction)btnLoadMoreClick {
23     // 1. 隐藏"加载更多"按钮
24     self.btnLoadMore.hidden = YES;
25     
26     // 2. 显示"等待指示器"所在的那个UIView
27     self.waitingView.hidden = NO;
28     
29     
30     
31     //GCD方法,标示延迟一定的时间后执行,由于我们这里是模拟操作,当点击按钮后,数据立刻会刷新,所以为了模拟逼真一些,这里就加了一个延迟操作的方法
32     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
33         
34         // 3. 调用代理方法实现下面的功能
35         // 调用footerViewUpdateData方法之前, 为了保证调用不出错, 所以要先判断一下代理对象是否真的实现了这个方法, 如果实现了这个方法再调用, 否则不调用.
36         if ([self.delegate respondsToSelector:@selector(footerViewUpdateData:)]) {
37             // 3. 增加一条数据
38             // 3.1 创建一个模型对象
39             // 3.2 把模型对象加到控制器的goods集合当中
40             // 4. 刷新UITableView
41             [self.delegate footerViewUpdateData:self];
42         }
43         
44         
45         // 4. 显示"加载更多"按钮
46         self.btnLoadMore.hidden = NO;
47         
48         // 5. 隐藏"等待指示器"所在的那个UIView
49         self.waitingView.hidden = YES;
50         
51     });
52     
53     
54     
55     
56     
57     
58 }
59 @end

案例二:微博

1):当我们的控制器Controller是 tableView的时候,我们可以直接使用Table View Controller,需要指定dataSource 和 delegate的代理对象

2):由于微博中的内容信息是不一致的,table栏的宽度也是不一致的,所以我们没有办法在 viedieLoad中 使用self.tableview.rowHeight 来设置统一的高度,所以我们针对每一个Cell的Frame做了一个封装,在懒加载数据时,将数据直接给Frame对象,然后Frame对象根据内容计算出高度,后再Cell的时候 直接返回Cell对象

 

封装的Frame对象

CZWeiboFrame.h

 1 #import <Foundation/Foundation.h>
 2 #import <CoreGraphics/CoreGraphics.h>
 3 #import <UIKit/UIKit.h>
 4 #define nameFont [UIFont systemFontOfSize:12]
 5 #define textFont [UIFont systemFontOfSize:14]
 6 
 7 @class CZWeibo;
 8 @interface CZWeiboFrame : NSObject
 9 
10 @property (nonatomic, strong) CZWeibo *weibo;
11 
12 // 用来保存头像的frame
13 @property (nonatomic, assign, readonly) CGRect iconFrame;
14 
15 // 昵称的frame
16 @property (nonatomic, assign, readonly) CGRect nameFrame;
17 
18 
19 // vip的frame
20 @property (nonatomic, assign, readonly) CGRect vipFrame;
21 
22 // 正文的frame
23 @property (nonatomic, assign, readonly) CGRect textFrame;
24 
25 //配图的frame
26 @property (nonatomic, assign, readonly) CGRect picFrame;
27 
28 // 行高
29 @property (nonatomic, assign, readonly) CGFloat rowHeight;
30 
31 @end

CZWeiboFrame.m

 1 #import "CZWeiboFrame.h"
 2 #import "CZWeibo.h"
 3 
 4 @implementation CZWeiboFrame
 5 
 6 // 重写weibo属性的set方法
 7 - (void)setWeibo:(CZWeibo *)weibo
 8 {
 9     _weibo = weibo;
10     
11     // 计算每个控件的frame, 和行高
12     
13     // 提取统一的间距
14     CGFloat margin = 10;
15     
16     // 1. 头像
17     CGFloat iconW = 35;
18     CGFloat iconH = 35;
19     CGFloat iconX = margin;
20     CGFloat iconY = margin;
21     _iconFrame = CGRectMake(iconX, iconY, iconW, iconH);
22     
23     
24     
25     // 2. 昵称
26     // 获取昵称字符串
27     NSString *nickName = weibo.name;
28     CGFloat nameX = CGRectGetMaxX(_iconFrame) + margin;
29     
30     // 根据Label中文字的内容, 来动态计算Label的高和宽
31     CGSize nameSize = [self sizeWithText:nickName andMaxSize:CGSizeMake(MAXFLOAT, MAXFLOAT) andFont:nameFont];
32     
33     CGFloat nameW = nameSize.width;
34     CGFloat nameH = nameSize.height;
35     CGFloat nameY = iconY + (iconH - nameH) / 2;
36     
37     _nameFrame = CGRectMake(nameX, nameY, nameW, nameH);
38     
39     
40     
41     // 3. 会员
42     CGFloat vipW = 10;
43     CGFloat vipH = 10;
44     CGFloat vipX = CGRectGetMaxX(_nameFrame) + margin;
45     CGFloat vipY = nameY;
46     _vipFrame = CGRectMake(vipX, vipY, vipW, vipH);
47     
48     
49     
50     // 4. 正文
51     CGFloat textX = iconX;
52     CGFloat textY = CGRectGetMaxY(_iconFrame) + margin;
53     CGSize textSize = [self sizeWithText:weibo.text andMaxSize:CGSizeMake(300, MAXFLOAT) andFont:textFont];
54     CGFloat textW = textSize.width;
55     CGFloat textH = textSize.height;
56     _textFrame = CGRectMake(textX, textY, textW, textH);
57     
58     
59     // 5. 配图
60     CGFloat picW = 100;
61     CGFloat picH = 100;
62     CGFloat picX = iconX;
63     CGFloat picY = CGRectGetMaxY(_textFrame) + margin;
64     _picFrame = CGRectMake(picX, picY, picW, picH);
65     
66     
67     //6. 计算每行的高度
68     CGFloat rowHeight = 0;
69     if (self.weibo.picture) {
70         // 如果有配图, 那么行高就等于配图的最大的Y值  + margin
71         rowHeight = CGRectGetMaxY(_picFrame) + margin;
72     } else {
73         // 如果没有配图, 那么行高就等于正文的最大的Y值  + margin
74         rowHeight = CGRectGetMaxY(_textFrame) + margin;
75     }
76     
77     // 注意::: 计算完毕行高以后,不要忘记为属性赋值。
78     _rowHeight = rowHeight;
79     
80     
81 }
82 
83 // 根据给定的字符串、最大值的size、给定的字体, 来计算文字应该占用的大小
84 - (CGSize)sizeWithText:(NSString *)text andMaxSize:(CGSize)maxSize andFont:(UIFont *)font
85 {
86     NSDictionary *attr = @{NSFontAttributeName : font};
87     return [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size;
88 }
89 @end

自定义的cell对象

之前封装的frame主要用来存储所需要的对象数据,以及计算各个控件和总的tableView Item的frame 

而我们在cell中是用来创建我们需要的对象,以及将最终形成的布局文件返回给tableView

CZWeiboCell.h

1 #import <UIKit/UIKit.h>
2 @class CZWeiboFrame;
3 @interface CZWeiboCell : UITableViewCell
4 
5 @property (nonatomic, strong) CZWeiboFrame *weiboFrame;
6 
7 + (instancetype)weiboCellWithTableView:(UITableView *)tableView;
8 @end

CZWeiboCell.m

  1 #import "CZWeiboCell.h"
  2 #import "CZWeibo.h"
  3 #import "CZWeiboFrame.h"
  4 
  5 
  6 
  7 @interface CZWeiboCell ()
  8 @property (nonatomic, weak) UIImageView *imgViewIcon;
  9 @property (nonatomic, weak) UILabel *lblNickName;
 10 @property (nonatomic, weak) UIImageView *imgViewVip;
 11 @property (nonatomic, weak) UILabel *lblText;
 12 @property (nonatomic, weak) UIImageView *imgViewPicture;
 13 
 14 
 15 @end
 16 
 17 
 18 @implementation CZWeiboCell
 19 
 20 
 21 #pragma mark - 重写单元格的initWithStyle:方法
 22 
 23 + (instancetype)weiboCellWithTableView:(UITableView *)tableView
 24 {
 25     static NSString *ID = @"weibo_cell";
 26     CZWeiboCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
 27     if (cell == nil) {
 28         cell = [[CZWeiboCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
 29     }
 30     return cell;
 31 }
 32 
 33 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
 34 {
 35     if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
 36         // 创建5个子控件
 37         
 38         // 1. 头像
 39         UIImageView *imgViewIcon = [[UIImageView alloc] init];
 40         [self.contentView addSubview:imgViewIcon];
 41         self.imgViewIcon = imgViewIcon;
 42         
 43         // 2. 昵称
 44         UILabel *lblNickName = [[UILabel alloc] init];
 45         // 设置Label的文字大小
 46         lblNickName.font = nameFont;
 47         
 48         [self.contentView addSubview:lblNickName];
 49         self.lblNickName = lblNickName;
 50         
 51         // 3. 会员
 52         UIImageView *imgViewVip = [[UIImageView alloc] init];
 53         imgViewVip.image = [UIImage imageNamed:@"vip"];
 54         [self.contentView addSubview:imgViewVip];
 55         self.imgViewVip = imgViewVip;
 56         
 57         // 4. 正文
 58         UILabel *lblText = [[UILabel alloc] init];
 59         lblText.font = textFont;
 60         // 设置正文的Label可以自动换行
 61         lblText.numberOfLines = 0;
 62         [self.contentView addSubview:lblText];
 63         self.lblText = lblText;
 64         
 65         // 5. 配图
 66         UIImageView *imgViewPicture = [[UIImageView alloc] init];
 67         [self.contentView addSubview:imgViewPicture];
 68         self.imgViewPicture = imgViewPicture;
 69     }
 70     return self;
 71 }
 72 
 73 
 74 #pragma mark - 重写weibo属性的set方法
 75 - (void)setWeiboFrame:(CZWeiboFrame *)weiboFrame
 76 {
 77     _weiboFrame = weiboFrame;
 78     
 79     // 1. 设置当前单元格中的子控件的数据
 80     [self settingData];
 81     
 82     // 2. 设置当前单元格中的子控件的frame
 83     [self settingFrame];
 84 }
 85 
 86 
 87 // 设置数据的方法
 88 - (void)settingData
 89 {
 90     CZWeibo *model = self.weiboFrame.weibo;
 91     // 1. 头像
 92     self.imgViewIcon.image = [UIImage imageNamed:model.icon];
 93     
 94     // 2. 昵称
 95     self.lblNickName.text = model.name;
 96     
 97     // 3. 会员
 98     if (model.isVip) {
 99         // 设置显示会员图标
100         self.imgViewVip.hidden = NO;
101         // 设置昵称的颜色是红色
102         self.lblNickName.textColor = [UIColor redColor];
103     } else {
104         // 设置隐藏会员图标
105         self.imgViewVip.hidden = YES;
106         // 设置昵称的颜色是黑色
107         self.lblNickName.textColor = [UIColor blackColor];
108     }
109     
110     // 4. 正文
111     self.lblText.text = model.text;
112  
113     
114     // 5. 配图
115     if (model.picture) {
116         // 有配图
117         // 如果model.picture的值是nil, 那么下面这句话执行会报异常
118         self.imgViewPicture.image = [UIImage imageNamed:model.picture];
119         // 显示图片框
120         self.imgViewPicture.hidden = NO;
121     } else {
122         // 如果没有配图, 隐藏图片框
123         self.imgViewPicture.hidden = YES;
124     }
125 
126 }
127 
128 // 设置frame的方法
129 - (void)settingFrame
130 {
131     // 1. 头像
132     
133     self.imgViewIcon.frame = self.weiboFrame.iconFrame;
134     
135     // 2. 昵称
136     self.lblNickName.frame = self.weiboFrame.nameFrame;
137     
138     // 3. 会员
139     self.imgViewVip.frame = self.weiboFrame.vipFrame;
140     
141     // 4. 正文
142     
143     self.lblText.frame = self.weiboFrame.textFrame;
144     
145     // 5. 配图
146     self.imgViewPicture.frame = self.weiboFrame.picFrame;
147 }
148 
149 - (void)awakeFromNib {
150     // Initialization code
151 }
152 
153 - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
154     [super setSelected:selected animated:animated];
155 
156     // Configure the view for the selected state
157 }
158 
159 @end

TableViewController控制器的类

这个类会把我们需要重写的方法展现出来,我们只需要对我们用到的方法进行重写就可以了

  1 #import "CZTableViewController.h"
  2 #import "CZWeibo.h"
  3 #import "CZWeiboCell.h"
  4 #import "CZWeiboFrame.h"
  5 
  6 @interface CZTableViewController ()
  7 
  8 // 现在要求weiboFrames集合中保存的很多个CZWeiboFrame模型,不再是CZWeibo模型了。
  9 @property (nonatomic, strong) NSArray *weiboFrames;
 10 
 11 @end
 12 
 13 @implementation CZTableViewController
 14 
 15 #pragma mark - 懒加载数据
 16 - (NSArray *)weiboFrames
 17 {
 18     if (_weiboFrames == nil) {
 19         NSString *path = [[NSBundle mainBundle] pathForResource:@"weibos.plist" ofType:nil];
 20         
 21         NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
 22         
 23         NSMutableArray *arrayModels = [NSMutableArray array];
 24         
 25         for (NSDictionary *dict in arrayDict) {
 26             // 创建一个数据模型
 27             CZWeibo *model = [CZWeibo weiboWithDict:dict];
 28             
 29             // 创建一个frame 模型
 30             // 创建了一个空得frame模型
 31             CZWeiboFrame *modelFrame = [[CZWeiboFrame alloc] init];
 32             
 33             // 把数据模型赋值给了modeFrame模型中的weibo属性
 34             modelFrame.weibo = model;
 35             
 36             
 37             [arrayModels addObject:modelFrame];
 38         }
 39         _weiboFrames = arrayModels;
 40     }
 41     return _weiboFrames;
 42 }
 43 
 44 
 45 
 46 - (void)viewDidLoad {
 47     [super viewDidLoad];
 48     
 49     // 统一设置行高
 50     //self.tableView.rowHeight = 300;
 51     
 52 //    NSLog(@"%@", self.view);
 53 //    NSLog(@"%@", self.tableView);
 54     
 55     // Uncomment the following line to preserve selection between presentations.
 56     // self.clearsSelectionOnViewWillAppear = NO;
 57     
 58     // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
 59     // self.navigationItem.rightBarButtonItem = self.editButtonItem;
 60 }
 61 
 62 - (void)didReceiveMemoryWarning {
 63     [super didReceiveMemoryWarning];
 64     // Dispose of any resources that can be recreated.
 65 }
 66 
 67 #pragma mark - Table view 数据源方法
 68 
 69 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 70     return 1;
 71 }
 72 
 73 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 74     return self.weiboFrames.count;
 75 }
 76 
 77 
 78 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 79     
 80     
 81     // 1. 获取模型数据
 82     CZWeiboFrame *model = self.weiboFrames[indexPath.row];
 83     
 84     
 85     // 2. 创建单元格
 86     CZWeiboCell *cell = [CZWeiboCell weiboCellWithTableView:tableView];
 87     
 88     // 3. 设置单元格数据
 89     cell.weiboFrame = model;
 90     
 91     // 4. 返回单元格
 92     return cell;
 93 }
 94 
 95 
 96 
 97 #pragma mark - Table view 代理方法
 98 
 99 // 返回每行的行高的方法,对于这个案例,其中最重要的就是关于如何计算行高
100 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
101 {
102     CZWeiboFrame *weiboFrame = self.weiboFrames[indexPath.row];
103     return weiboFrame.rowHeight;
104 }
105 
106 
107 - (BOOL)prefersStatusBarHidden
108 {
109     return YES;
110 }
111 
112 
113 @end

 案例三:做一个类似于QQ聊天的tableView界面

这个界面有两个难点:

1.信息背后的框体 要包裹住消息内容

2.监听系统的键盘弹出事件,将我们的View 整体向上位移

问题1:

 1 我们通过 设置 内边距的形式来进行解决
 2 
 3 btnText.contentEdgeInsets = UIEdgeInsetsMake(15, 20, 15, 20); 
 4 
 5 同时对于图片我们要选择平铺的方式进行拉伸
 6 
 7  // 加载图片
 8     UIImage *imageNormal = [UIImage imageNamed:imgNor];
 9     UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted];
10     
11     // 用平铺的方式拉伸图片
12     imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5];
13     imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5];
14     
15     // 设置背景图
16     [self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal];
17     [self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted];

问题二:

 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3     // 取消分割线
 4     self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
 5     
 6     // 设置UITableView的背景色
 7     self.tableView.backgroundColor = [UIColor colorWithRed:236 / 255.0 green:236 / 255.0 blue:236 / 255.0 alpha:1.0];
 8     
 9     // 设置UITableView的行不允许被选中
10     self.tableView.allowsSelection = NO;
11     
12     // 设置文本框最左侧有一段间距
13     UIView *leftVw = [[UIView alloc] init];
14     leftVw.frame = CGRectMake(0, 0, 5, 1);
15     
16     // 把leftVw设置给文本框
17     self.txtInput.leftView = leftVw;
18     self.txtInput.leftViewMode = UITextFieldViewModeAlways;
19     
20     
21     // 监听键盘的弹出事件
22     // 1. 创建一个NSNotificationCenter对象。
23     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
24     
25     // 2. 监听键盘的弹出通知
26     [center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
27     
28 }
29 
30 - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo
31 {
32 //    NSLog(@"通知名称: %@", noteInfo.name);
33 //    
34 //    NSLog(@"通知的发布者: %@", noteInfo.object);
35 //    
36 //    NSLog(@"通知的具体内容: %@", noteInfo.userInfo);
37     // 1. 获取当键盘显示完毕或者隐藏完毕后的Y值
38     CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
39     CGFloat keyboardY = rectEnd.origin.y;
40     
41     // 用键盘的Y值减去屏幕的高度计算出平移的值
42     // 1. 如果是键盘弹出事件, 那么计算出的值就是负的键盘的高度
43     // 2. 如果是键盘的隐藏事件, 那么计算出的值就是零, 因为键盘在隐藏以后, 键盘的Y值就等于屏幕的高度。
44     CGFloat tranformValue = keyboardY - self.view.frame.size.height;
45     
46     [UIView animateWithDuration:0.25 animations:^{
47         // 让控制器的View执行一次“平移”
48         self.view.transform = CGAffineTransformMakeTranslation(0, tranformValue);
49     }];
50     
51     
52     
53     // 让UITableView的最后一行滚动到最上面
54     NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0];
55     [self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
56 }
57 
58 
59 // ***************** 注意: 监听通知以后一定要在监听通知的对象的dealloc方法中移除监听 *************/.
60 
61 - (void)dealloc
62 {
63     // 移除通知
64     [[NSNotificationCenter defaultCenter] removeObserver:self];
65 }

 

 

整体代码

1.做一个NSString的类扩展 用于字体最大的尺寸

NSString+CZNSStringExt.h

 1 #import <Foundation/Foundation.h>
 2 #import <UIKit/UIKit.h>
 3 @interface NSString (CZNSStringExt)
 4 
 5 // 对象方法
 6 - (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font;
 7 
 8 // 类方法
 9 + (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font;
10 @end

NSString+CZNSStringExt.m

 1 #import "NSString+CZNSStringExt.h"
 2 
 3 @implementation NSString (CZNSStringExt)
 4 
 5 // 实现对象方法
 6 - (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font
 7 {
 8     NSDictionary *attrs = @{NSFontAttributeName : font};
 9     return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
10 }
11 
12 // 类方法
13 + (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font
14 {
15     return [text sizeOfTextWithMaxSize:maxSize font:font];
16 }
17 
18 @end

2.控制器对象

ViewController.m

  1 #import "ViewController.h"
  2 #import "CZMessage.h"
  3 #import "CZMessageFrame.h"
  4 #import "CZMessageCell.h"
  5 
  6 @interface ViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
  7 @property (weak, nonatomic) IBOutlet UITableView *tableView;
  8 
  9 // 用来保存所有的消息的frame模型对象
 10 @property (nonatomic, strong) NSMutableArray *messageFrames;
 11 @property (weak, nonatomic) IBOutlet UITextField *txtInput;
 12 
 13 @end
 14 
 15 @implementation ViewController
 16 #pragma mark - /********** 懒加载数据 *********/
 17 - (NSMutableArray *)messageFrames
 18 {
 19     if (_messageFrames == nil) {
 20         NSString *path = [[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil];
 21         NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
 22         
 23         NSMutableArray *arrayModels = [NSMutableArray array];
 24         for (NSDictionary *dict in arrayDict) {
 25             // 创建一个数据模型
 26             CZMessage *model = [CZMessage messageWithDict:dict];
 27             
 28             // 获取上一个数据模型
 29             CZMessage *lastMessage = (CZMessage *)[[arrayModels lastObject] message];
 30             
 31             // 判断当前模型的“消息发送时间”是否和上一个模型的“消息发送时间”一致, 如果一致做个标记
 32             if ([model.time isEqualToString:lastMessage.time]) {
 33                 model.hideTime = YES;
 34             }
 35             
 36             // 创建一个frame 模型
 37             CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init];
 38             
 39             modelFrame.message = model;
 40             
 41             
 42             // 把frame 模型加到arrayModels
 43             [arrayModels addObject:modelFrame];
 44         }
 45         _messageFrames = arrayModels;
 46     }
 47     return _messageFrames;
 48 }
 49 
 50 
 51 #pragma mark - /********** 文本框的代理方法 *********/
 52 //- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
 53 //{
 54 //    return YES;
 55 //}
 56 
 57 // 当键盘上的return键被单击的时候触发
 58 - (BOOL)textFieldShouldReturn:(UITextField *)textField
 59 {
 60     // 1. 获取用户输入的文本
 61     NSString *text = textField.text;
 62     
 63     // 2. 发送用户的消息
 64     [self sendMessage:text withType:CZMessageTypeMe];
 65     
 66     // 3. 发送一个系统消息
 67     [self sendMessage:@"不认识!" withType:CZMessageTypeOther];
 68     
 69     // 清空文本框
 70     textField.text = nil;
 71     
 72     return YES;
 73 }
 74 
 75 // 发送消息
 76 - (void)sendMessage:(NSString *)msg withType:(CZMessageType)type
 77 {
 78     // 2. 创建一个数据模型和frame 模型
 79     CZMessage *model = [[CZMessage alloc] init];
 80     
 81     // 获取当前系统时间
 82     NSDate *nowDate = [NSDate date];
 83     // 创建一个日期时间格式化器
 84     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
 85     // 设置格式
 86     formatter.dateFormat = @"今天 HH:mm";
 87     // 进行日期时间的格式化
 88     model.time = [formatter stringFromDate:nowDate];
 89     model.type = type;
 90     model.text = msg;
 91     
 92     
 93     
 94     // 根据当前消息的时间和上一条消息的时间, 来设置是否需要隐藏时间Label
 95     CZMessageFrame *lastMessageFrame = [self.messageFrames lastObject];
 96     NSString *lastTime = lastMessageFrame.message.time;
 97     if ([model.time isEqualToString:lastTime]) {
 98         model.hideTime = YES;
 99     }
100     
101     //***** 注意: 要先设置数据模型的hideTime属性, 然后再设置modelFrame.message = model;
102     // 因为在设置modelFrame.message = model;的时候set方法中, 内部会用到model.hideTime属性。
103     
104     // 创建一个frame 模型
105     CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init];
106     modelFrame.message = model;
107     
108     
109     
110     // 3. 把frame 模型加到集合中
111     [self.messageFrames addObject:modelFrame];
112     
113     
114     
115     
116     // 4. 刷新UITableView的数据
117     [self.tableView reloadData];
118     
119     // 5. 把最后一行滚动到最上面
120     NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0];
121     [self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
122 }
123 
124 
125 
126 #pragma mark - /********** UITableView的代理方法 *********/
127 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
128 {
129     // 把键盘叫回去, 思路: 让控制器所管理的UIView结束编辑
130     [self.view endEditing:YES];
131 }
132 
133 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
134 {
135     NSLog(@"★★★★★★★★★");
136 }
137 
138 
139 
140 #pragma mark - /********** 数据源方法 *********/
141 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
142 {
143     return 1;
144 }
145 
146 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
147 {
148     return self.messageFrames.count;
149 }
150 
151 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
152 {
153     // 1. 获取模型数据
154     CZMessageFrame *modelFrame = self.messageFrames[indexPath.row];
155     
156     // 2. 创建单元格
157     
158     CZMessageCell *cell = [CZMessageCell messageCellWithTableView:tableView];
159     
160     // 3. 把模型设置给单元格对象
161     cell.messageFrame = modelFrame;
162     
163     // 4.返回单元格
164     return cell;
165 }
166 
167 // 返回每一行的行高
168 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
169 {
170     CZMessageFrame *messageFrame = self.messageFrames[indexPath.row];
171     return messageFrame.rowHeight;
172 }
173 
174 
175 
176 #pragma mark - /********** 其他 *********/
177 - (void)viewDidLoad {
178     [super viewDidLoad];
179     // 取消分割线
180     self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
181     
182     // 设置UITableView的背景色
183     self.tableView.backgroundColor = [UIColor colorWithRed:236 / 255.0 green:236 / 255.0 blue:236 / 255.0 alpha:1.0];
184     
185     // 设置UITableView的行不允许被选中
186     self.tableView.allowsSelection = NO;
187     
188     // 设置文本框最左侧有一段间距
189     UIView *leftVw = [[UIView alloc] init];
190     leftVw.frame = CGRectMake(0, 0, 5, 1);
191     
192     // 把leftVw设置给文本框
193     self.txtInput.leftView = leftVw;
194     self.txtInput.leftViewMode = UITextFieldViewModeAlways;
195     
196     
197     // 监听键盘的弹出事件
198     // 1. 创建一个NSNotificationCenter对象。
199     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
200     
201     // 2. 监听键盘的弹出通知
202     [center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
203     
204 }
205 
206 - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo
207 {
208 //    NSLog(@"通知名称: %@", noteInfo.name);
209 //    
210 //    NSLog(@"通知的发布者: %@", noteInfo.object);
211 //    
212 //    NSLog(@"通知的具体内容: %@", noteInfo.userInfo);
213     // 1. 获取当键盘显示完毕或者隐藏完毕后的Y值
214     CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
215     CGFloat keyboardY = rectEnd.origin.y;
216     
217     // 用键盘的Y值减去屏幕的高度计算出平移的值
218     // 1. 如果是键盘弹出事件, 那么计算出的值就是负的键盘的高度
219     // 2. 如果是键盘的隐藏事件, 那么计算出的值就是零, 因为键盘在隐藏以后, 键盘的Y值就等于屏幕的高度。
220     CGFloat tranformValue = keyboardY - self.view.frame.size.height;
221     
222     [UIView animateWithDuration:0.25 animations:^{
223         // 让控制器的View执行一次“平移”
224         self.view.transform = CGAffineTransformMakeTranslation(0, tranformValue);
225     }];
226     
227     
228     
229     // 让UITableView的最后一行滚动到最上面
230     NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0];
231     [self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
232 }
233 
234 
235 // ***************** 注意: 监听通知以后一定要在监听通知的对象的dealloc方法中移除监听 *************/.
236 
237 - (void)dealloc
238 {
239     // 移除通知
240     [[NSNotificationCenter defaultCenter] removeObserver:self];
241 }
242 
243 
244 - (void)didReceiveMemoryWarning {
245     [super didReceiveMemoryWarning];
246     // Dispose of any resources that can be recreated.
247 }
248 
249 - (BOOL)prefersStatusBarHidden
250 {
251     return YES;
252 }
253 
254 @end

3.自定义的cell对象

CZMessageCell.h

 1 #import <UIKit/UIKit.h>
 2 
 3 @class CZMessageFrame;
 4 @interface CZMessageCell : UITableViewCell
 5 
 6 // 为自定义单元格增加一个frame 模型属性
 7 @property (nonatomic, strong) CZMessageFrame *messageFrame;
 8 
 9 
10 // 封装一个创建自定义Cell的方法
11 + (instancetype)messageCellWithTableView:(UITableView *)tableView;
12 
13 @end

CZMessageCell.m

  1 #import "CZMessageCell.h"
  2 #import "CZMessage.h"
  3 #import "CZMessageFrame.h"
  4 
  5 @interface CZMessageCell ()
  6 
  7 @property (nonatomic, weak) UILabel *lblTime;
  8 @property (nonatomic, weak) UIImageView *imgViewIcon;
  9 @property (nonatomic, weak) UIButton *btnText;
 10 @end
 11 
 12 
 13 @implementation CZMessageCell
 14 
 15 #pragma mark -  重写initWithStyle方法
 16 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
 17 {
 18     if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
 19         // 创建子控件
 20         
 21         // 显示时间的label
 22         UILabel *lblTime = [[UILabel alloc] init];
 23         // 设置文字大小
 24         lblTime.font = [UIFont systemFontOfSize:12];
 25         // 设置文字居中
 26         lblTime.textAlignment = NSTextAlignmentCenter;
 27         [self.contentView addSubview:lblTime];
 28         self.lblTime = lblTime;
 29         
 30         
 31         // 显示头像的UIImageView
 32         UIImageView *imgViewIcon = [[UIImageView alloc] init];
 33         [self.contentView addSubview:imgViewIcon];
 34         self.imgViewIcon = imgViewIcon;
 35         
 36         
 37         // 显示正文的按钮
 38         UIButton *btnText = [[UIButton alloc] init];
 39         // 设置正文的字体大小
 40         btnText.titleLabel.font = textFont;
 41         // 修改按钮的正文文字颜色
 42         [btnText setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
 43         // 设置按钮中的label的文字可以换行
 44         btnText.titleLabel.numberOfLines = 0;
 45         // 设置按钮的背景色
 46         //btnText.backgroundColor = [UIColor purpleColor];
 47         
 48         // 设置按钮中的titleLabel的背景色
 49         //btnText.titleLabel.backgroundColor = [UIColor greenColor];
 50         
 51         // 设置按钮的内边距
 52         btnText.contentEdgeInsets = UIEdgeInsetsMake(15, 20, 15, 20);
 53         
 54         [self.contentView addSubview:btnText];
 55         self.btnText = btnText;
 56     }
 57     
 58     // 设置单元格的背景色为clearColor
 59     self.backgroundColor = [UIColor clearColor];
 60     return self;
 61 }
 62 
 63 
 64 #pragma mark -  重写frame 模型的set方法
 65 - (void)setMessageFrame:(CZMessageFrame *)messageFrame
 66 {
 67     _messageFrame = messageFrame;
 68     
 69     // 获取数据模型
 70     CZMessage *message = messageFrame.message;
 71     
 72     // 分别设置每个子控件的数据 和 frame
 73     
 74     // 设置 "时间Label"的数据 和 frame
 75     self.lblTime.text = message.time;
 76     self.lblTime.frame = messageFrame.timeFrame;
 77     self.lblTime.hidden = message.hideTime;
 78     
 79     
 80     
 81     // 设置 头像
 82     // 根据消息类型, 判断应该使用哪张图片
 83     NSString *iconImg = message.type == CZMessageTypeMe ? @"me" : @"other";
 84     self.imgViewIcon.image = [UIImage imageNamed:iconImg];
 85     self.imgViewIcon.frame = messageFrame.iconFrame;
 86     
 87     
 88     // 设置消息正文
 89     [self.btnText setTitle:message.text forState:UIControlStateNormal];
 90     self.btnText.frame = messageFrame.textFrame;
 91     
 92     
 93     // 设置正文的背景图
 94     NSString *imgNor, *imgHighlighted;
 95     if (message.type == CZMessageTypeMe) {
 96         // 自己发的消息
 97         imgNor = @"chat_send_nor";
 98         imgHighlighted = @"chat_send_press_pic";
 99         
100         // 设置消息的正文文字颜色为 "白色"
101         [self.btnText setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
102     } else {
103         // 对方发的消息
104         imgNor = @"chat_recive_nor";
105         imgHighlighted = @"chat_recive_press_pic";
106         
107        // 设置消息的正文文字颜色为 "黑色"
108         [self.btnText setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
109     }
110     
111     // 加载图片
112     UIImage *imageNormal = [UIImage imageNamed:imgNor];
113     UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted];
114     
115     // 用平铺的方式拉伸图片
116     imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5];
117     imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5];
118     
119     // 设置背景图
120     [self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal];
121     [self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted];
122 }
123 
124 
125 #pragma mark -  创建自定义Cell的方法
126 + (instancetype)messageCellWithTableView:(UITableView *)tableView
127 {
128     static NSString *ID = @"message_cell";
129     CZMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
130     if (cell == nil) {
131         cell = [[CZMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
132     }
133     return cell;
134 }
135 
136 - (void)awakeFromNib {
137     // Initialization code
138 }
139 
140 - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
141     [super setSelected:selected animated:animated];
142 
143     // Configure the view for the selected state
144 }
145 
146 @end

4.消息模型

CZMessage.h

 1 #import <Foundation/Foundation.h>
 2 
 3 typedef enum {
 4     CZMessageTypeMe = 0,    // 表示自己
 5     CZMessageTypeOther = 1  // 表示对方
 6 } CZMessageType;
 7 
 8 
 9 @interface CZMessage : NSObject
10 
11 // 消息正文
12 @property (nonatomic, copy) NSString *text;
13 
14 // 消息发送时间
15 @property (nonatomic, copy) NSString *time;
16 
17 // 消息的类型(表示是对方发送的消息还是自己发送的消息)
18 @property (nonatomic, assign) CZMessageType type;
19 
20 // 用来记录是否需要显示"时间Label"
21 @property (nonatomic, assign) BOOL hideTime;
22 
23 
24 
25 - (instancetype)initWithDict:(NSDictionary *)dict;
26 + (instancetype)messageWithDict:(NSDictionary *)dict;
27 
28 @end

CZMeesge.m

 1 #import "CZMessage.h"
 2 
 3 @implementation CZMessage
 4 
 5 - (instancetype)initWithDict:(NSDictionary *)dict
 6 {
 7     if (self = [super init]) {
 8         [self setValuesForKeysWithDictionary:dict];
 9     }
10     return self;
11 }
12 
13 + (instancetype)messageWithDict:(NSDictionary *)dict
14 {
15     return [[self alloc] initWithDict:dict];
16 }
17 @end

5.展示信息的frame

CZMessageFram.h

 1 #import <Foundation/Foundation.h>
 2 #import <CoreGraphics/CoreGraphics.h>
 3 #define textFont [UIFont systemFontOfSize:13]
 4 
 5 @class CZMessage;
 6 @interface CZMessageFrame : NSObject
 7 
 8 // 引用数据模型
 9 @property (nonatomic, strong) CZMessage *message;
10 
11 // 时间Label的frame
12 @property (nonatomic, assign, readonly) CGRect timeFrame;
13 
14 // 头像的frame
15 @property (nonatomic, assign, readonly) CGRect iconFrame;
16 
17 // 正文的frame
18 @property (nonatomic, assign, readonly) CGRect textFrame;
19 
20 // 行高
21 @property (nonatomic, assign, readonly) CGFloat rowHeight;
22 
23 @end

CZMessageFrame.m

 1 #import "CZMessageFrame.h"
 2 #import <UIKit/UIKit.h>
 3 #import "CZMessage.h"
 4 #import "NSString+CZNSStringExt.h"
 5 
 6 @implementation CZMessageFrame
 7 
 8 - (void)setMessage:(CZMessage *)message
 9 {
10     _message = message;
11     
12     // 计算每个控件的frame 和 行高
13     // 获取屏幕宽度
14     CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
15     // 设置一个统一的间距
16     CGFloat margin = 5;
17     
18     // 计算时间label的frame
19     CGFloat timeX = 0;
20     CGFloat timeY = 0;
21     CGFloat timeW = screenW;
22     CGFloat timeH = 15;
23     if (!message.hideTime) {
24         // 如果需要显示时间label, 那么再计算时间label的frame
25         _timeFrame = CGRectMake(timeX, timeY, timeW, timeH);
26     }
27     
28     
29     
30     // 计算头像的frame
31     CGFloat iconW = 30;
32     CGFloat iconH = 30;
33     CGFloat iconY = CGRectGetMaxY(_timeFrame) + margin;
34     CGFloat iconX = message.type == CZMessageTypeOther ? margin : screenW - margin - iconW;
35     _iconFrame = CGRectMake(iconX, iconY, iconW, iconH);
36     
37     
38 
39     // 计算消息正文的frame
40     // 1. 先计算正文的大小
41     CGSize textSize = [message.text sizeOfTextWithMaxSize:CGSizeMake(200, MAXFLOAT) font:textFont];
42     CGFloat textW = textSize.width + 40;
43     CGFloat textH = textSize.height + 30;
44     // 2. 再计算x,y
45     CGFloat textY = iconY;
46     CGFloat textX = message.type == CZMessageTypeOther ? CGRectGetMaxX(_iconFrame) : (screenW - margin - iconW - textW);
47     _textFrame = CGRectMake(textX, textY, textW, textH);
48     
49     
50     
51     // 计算行高
52     // 获取 头像的最大的Y值和正文的最大的Y值, 然后用最大的Y值+ margin
53     CGFloat maxY = MAX(CGRectGetMaxY(_textFrame), CGRectGetMaxY(_iconFrame));
54     _rowHeight = maxY + margin;
55     
56 }
57 @end

方案四,好友列表

几个要注意的:

1.组上得图标旋转后变形

2.cell重用的问题

这里直接继承的TableViewController控制器 直接上代码了

CZQQFriendsTableViewController.m

  1 #import "CZQQFriendsTableViewController.h"
  2 #import "CZGroup.h"
  3 #import "CZFriend.h"
  4 #import "CZFriendCell.h"
  5 #import "CZGroupHeaderView.h"
  6 
  7 @interface CZQQFriendsTableViewController () <CZGroupHeaderViewDelegate>
  8 
  9 // 保存所有的朋友信息(分组信息)
 10 @property (nonatomic, strong) NSArray *groups;
 11 @end
 12 
 13 @implementation CZQQFriendsTableViewController
 14 
 15 #pragma mark - *********** 懒加载 ***********
 16 - (NSArray *)groups
 17 {
 18     if (_groups == nil) {
 19         NSString *path = [[NSBundle mainBundle] pathForResource:@"friends.plist" ofType:nil];
 20         NSArray *arrayDicts = [NSArray arrayWithContentsOfFile:path];
 21         
 22         NSMutableArray *arrayModels = [NSMutableArray array];
 23         for (NSDictionary *dict in arrayDicts) {
 24             CZGroup *model = [CZGroup groupWithDict:dict];
 25             [arrayModels addObject:model];
 26         }
 27         _groups = arrayModels;
 28         
 29     }
 30     return _groups;
 31 }
 32 
 33 
 34 #pragma mark - *********** CZGroupHeaderViewDelegate的代理方法 ***********
 35 - (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView
 36 {
 37     // 刷新table view
 38     //[self.tableView reloadData];
 39     
 40     // 局部刷新(只刷新某个组)
 41     // 创建一个用来表示某个组的对象
 42     NSIndexSet *idxSet = [NSIndexSet indexSetWithIndex:groupHeaderView.tag];
 43     [self.tableView reloadSections:idxSet withRowAnimation:UITableViewRowAnimationFade];
 44 }
 45 
 46 
 47 
 48 
 49 
 50 #pragma mark - *********** 实现数据源方法 ***********
 51 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 52 {
 53     return self.groups.count;
 54 }
 55 
 56 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 57 {
 58     // 因为在这个方法中, 要根据当前组的状态(是否是展开), 来设置不同的返回值
 59     // 所以, 需要为CZGroup模型增加一个用来保存"是否展开"状态的属性
 60     CZGroup *group = self.groups[section];
 61     if (group.isVisible) {
 62         return group.friends.count;
 63     } else {
 64         return 0;
 65     }
 66     
 67 }
 68 
 69 // 返回每行的单元格
 70 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 71 {
 72     // 1. 获取模型对象(数据)
 73     CZGroup *group = self.groups[indexPath.section];
 74     CZFriend *friend = group.friends[indexPath.row];
 75     
 76     // 2. 创建单元格(视图)
 77     CZFriendCell *cell = [CZFriendCell friendCellWithTableView:tableView];
 78     
 79     // 3. 设置单元格数据(把模型设置给单元格)
 80     cell.friendModel = friend;
 81     
 82     // 4. 返回单元格
 83     return cell;
 84 }
 85 
 86 
 87 //// 设置每一组的组标题(下面的这个方法只能设置每一组的组标题字符串, 但是我们要的是每一组中还包含其他子控件)
 88 //- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
 89 //{
 90 //    CZGroup *group = self.groups[section];
 91 //    return group.name;
 92 //}
 93 
 94 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
 95 {
 96     // 不要在这个方法中直接创建一个UIView对象返回, 因为这样无法实现重用该UIView
 97     // 为了能重用每个Header中的UIView, 所以这里要返回一个UITableViewHeaderFooterView
 98     // 1. 获取模型数据
 99     CZGroup *group = self.groups[section];
100     
101     
102     // 2. 创建UITableViewHeaderFooterView
103     CZGroupHeaderView *headerVw = [CZGroupHeaderView groupHeaderViewWithTableView:tableView];
104     headerVw.tag = section;
105     
106     // 3. 设置数据
107     headerVw.group = group;
108     
109     // 设置headerView的代理为当前控制器
110     headerVw.delegate = self;
111     
112     
113     // 在刚刚创建好的header view中获取的header view的frame都是0, 因为刚刚创建好的header view我们没有为其frame赋值, 所以frame都是 0
114     // 但是, 程序运行起来以后, 我们看到的header view是有frame的。原因是: 在当前方法当中, 将header view返回以后, UITableView在执行的时候, 会用到header view, UITableView既然要用Header View, 那么就必须将header view添加到UITableview中, 当把header view添加到UITableView中的时候, UITableView内部会根据一些设置来动态的为header view的frame赋值,也就是说在UITableView即将使用header view的时候, 才会为header view的frame赋值。
115     
116     // 4. 返回view
117     return headerVw;
118     
119     
120 }
121 
122 
123 
124 
125 #pragma mark - *********** 隐藏状态栏 ***********
126 - (BOOL)prefersStatusBarHidden
127 {
128     return YES;
129 }
130 
131 
132 #pragma mark - *********** 控制器的viewDidLoad方法 ***********
133 - (void)viewDidLoad
134 {
135     [super viewDidLoad];
136     
137     // 统一设置每组的组标题的高度
138     self.tableView.sectionHeaderHeight = 44;
139 }
140 
141 @end

cell模型对象

CZFriendCell.h

1 #import <UIKit/UIKit.h>
2 @class CZFriend;
3 @interface CZFriendCell : UITableViewCell
4 
5 + (instancetype)friendCellWithTableView:(UITableView *)tableView;
6 
7 @property (nonatomic, strong) CZFriend *friendModel;
8 @end

CZFriendCell.m

 1 #import "CZFriendCell.h"
 2 #import "CZFriend.h"
 3 
 4 @implementation CZFriendCell
 5 
 6 
 7 + (instancetype)friendCellWithTableView:(UITableView *)tableView
 8 {
 9     static NSString *ID = @"friend_cell";
10     CZFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
11     if (cell == nil) {
12         cell = [[CZFriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
13     }
14     return cell;
15 }
16 
17 
18 - (void)setFriendModel:(CZFriend *)friendModel
19 {
20     _friendModel = friendModel;
21     
22     // 把模型中的数据设置给单元格的子控件
23     self.imageView.image = [UIImage imageNamed:friendModel.icon];
24     self.textLabel.text = friendModel.name;
25     self.detailTextLabel.text = friendModel.intro;
26     
27     // 根据当前的好友是不是vip来决定是否要将"昵称"显示为红色
28     self.textLabel.textColor = friendModel.isVip ? [UIColor redColor] : [UIColor blackColor];
29 }
30 
31 
32 - (void)awakeFromNib {
33     // Initialization code
34 }
35 
36 - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
37     [super setSelected:selected animated:animated];
38 
39     // Configure the view for the selected state
40 }
41 
42 @end

CZGroupHeaderView.h

 1 #import <UIKit/UIKit.h>
 2 @class CZGroupHeaderView;
 3 @protocol CZGroupHeaderViewDelegate <NSObject>
 4 
 5 - (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView;
 6 
 7 @end
 8 
 9 
10 @class CZGroup;
11 @interface CZGroupHeaderView : UITableViewHeaderFooterView
12 
13 @property (nonatomic, strong) CZGroup *group;
14 
15 + (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView;
16 
17 // 增加一个代理属性
18 @property (nonatomic, weak) id<CZGroupHeaderViewDelegate> delegate;
19 
20 @end

CZGroupHeaderView.m

  1 #import "CZGroupHeaderView.h"
  2 #import "CZGroup.h"
  3 
  4 @interface CZGroupHeaderView ()
  5 
  6 @property (nonatomic, weak) UIButton *btnGroupTitle;
  7 
  8 @property (nonatomic, weak) UILabel *lblCount;
  9 
 10 @end
 11 
 12 
 13 @implementation CZGroupHeaderView
 14 
 15 
 16 // 封装一个类方法来创建一个header view
 17 + (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView
 18 {
 19     static NSString *ID = @"group_header_view";
 20     CZGroupHeaderView *headerVw = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
 21     if (headerVw == nil) {
 22         headerVw = [[CZGroupHeaderView alloc] initWithReuseIdentifier:ID];
 23     }
 24     return headerVw;
 25 }
 26 
 27 // 重写initWithReuseIdentifier方法, 在创建headerView的时候, 同时创建子控件
 28 - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
 29 {
 30     if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
 31         // 创建按钮
 32         UIButton *btnGroupTitle = [[UIButton alloc] init];
 33         // 设置按钮的图片(三角图片)
 34         [btnGroupTitle setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal];
 35         // 设置按钮的文字颜色
 36         [btnGroupTitle setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
 37         // 设置按钮默认的背景图片和高亮时的背景图片
 38         [btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg"] forState:UIControlStateNormal];
 39         // 设置按钮高亮的背景图片和高亮时的背景图片
 40         [btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted];
 41         // 设置按钮中内容整体左对齐
 42         btnGroupTitle.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
 43         // 设置按钮的内容的内边距
 44         btnGroupTitle.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
 45         // 设置按钮标题距离左边的边距
 46         btnGroupTitle.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0);
 47         
 48         // 为按钮增加一个点击事件
 49         [btnGroupTitle addTarget:self action:@selector(btnGroupTitleClicked) forControlEvents:UIControlEventTouchUpInside];
 50         
 51         // 设置按钮中图片的现实模式
 52         btnGroupTitle.imageView.contentMode = UIViewContentModeCenter;
 53         // 设置图片框超出的部分不要截掉
 54         btnGroupTitle.imageView.clipsToBounds = NO;
 55         
 56         [self.contentView addSubview:btnGroupTitle];
 57         self.btnGroupTitle = btnGroupTitle;
 58         
 59         // 创建lable
 60         UILabel *lblCount = [[UILabel alloc] init];
 61         [self.contentView addSubview:lblCount];
 62         self.lblCount = lblCount;
 63     }
 64     return self;
 65 }
 66 
 67 
 68 // 组标题按钮的点击事件
 69 - (void)btnGroupTitleClicked
 70 {
 71     // 1. 设置组的状态
 72     self.group.visible = !self.group.isVisible;
 73     
 74 //    // 2.刷新tableView
 75     // 通过代理来实现
 76     if ([self.delegate respondsToSelector:@selector(groupHeaderViewDidClickTitleButton:)]) {
 77         // 调用代理方法
 78         [self.delegate groupHeaderViewDidClickTitleButton:self];
 79     }
 80     
 81    
 82 }
 83 
 84 // 当一个新的header view 已经加到某个父控件中的时候执行这个方法。
 85 - (void)didMoveToSuperview
 86 {
 87     if (self.group.isVisible) {
 88         // 3. 让按钮中的图片实现旋转
 89         self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
 90     } else {
 91         self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(0);
 92     }
 93 }
 94 
 95 // 重写group属性的set方法
 96 - (void)setGroup:(CZGroup *)group
 97 {
 98     _group = group;
 99     // 设置数据
100     
101     // 设置按钮上的文字
102     [self.btnGroupTitle setTitle:group.name forState:UIControlStateNormal];
103     // 设置 lblCount商的文字
104     self.lblCount.text = [NSString stringWithFormat:@"%d / %d", group.online, group.friends.count];
105     
106     // 设置按钮中的图片旋转问题
107     if (self.group.isVisible) {
108         // 3. 让按钮中的图片实现旋转
109         self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
110     } else {
111         self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(0);
112     }
113     
114     
115     // 设置frame不要写在这里, 因为在这里获取的当前控件(self)的宽和高都是0
116     
117     
118    
119 }
120 
121 // 当当前控件的frame发生改变的时候会调用这个方法
122 - (void)layoutSubviews
123 {
124     [super layoutSubviews];
125     
126     // 设置按钮的frame
127     self.btnGroupTitle.frame = self.bounds;
128     //NSLog(@"%@", NSStringFromCGRect(self.btnGroupTitle.frame));
129     
130     // 设置lable的frame
131     CGFloat lblW = 100;
132     CGFloat lblH = self.bounds.size.height;
133     CGFloat lblX = self.bounds.size.width - 10 - lblW;
134     CGFloat lblY = 0;
135     self.lblCount.frame = CGRectMake(lblX, lblY, lblW, lblH);
136     //NSLog(@"%@", NSStringFromCGRect(self.lblCount.frame));
137 }
138 
139 /*
140 // Only override drawRect: if you perform custom drawing.
141 // An empty implementation adversely affects performance during animation.
142 - (void)drawRect:(CGRect)rect {
143     // Drawing code
144 }
145 */
146 
147 @end

 

posted @ 2015-05-26 09:08  王世桢  阅读(568)  评论(0编辑  收藏  举报