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