UITableView
概述
UITableView
一般用来展示表格数据、可以滚动(继承自UIScrollView).性能极佳
UITableView分两种样式:
Plain,不分组的样式
Grouped,分组的样式
UITableView默认为Plain样式,改为Grouped后实现分组,如果再改回Plain 那么在滚动的时候 上一层的头标签就会一直作为索引显示,类似于通讯录中的A B的显示方式
使用:
如果要使用UITableView 那么需要实现UITableViewDataSource协议后重写
//展现数据有几组,当不实现这个方法时,默认为一组
-(NSInteger)numberOfSectionsInTableView:(UITableView *) tableView //一组有几行
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) section //每行显示什么内容
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; //实现右侧的索引栏
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
(获取group数组中的每个对象的title值,并返回到一个NSArray中
[self.groups valueForKeyPath:@"title"]) //通过代理坚挺cell的点击事件
//选中某行
-(void)tableView:(UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath //取消选中某行
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath;
23
//设置组标题
-(NSString *)tableView :(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; //设置组描述
-(NSString *)tableView :(UITableView *)tableView titleForFooterInsection:(NSInteger)section; //修改每行的行高
.如果tableView的行高一样,那么就在控制器的viewDidLoad中统一设置行高tableView.rowHeight(这种方法比较高效)
.通过代理方法实现:
-(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的常见属性
rowHeight 可以统一设置所有行的高度 separatorColor 分割线的颜色
separatorStyle 分割线的样式 tableHeaderView 一般可以放广告
tableFooterView 一般可以放加载更多
Cell的常见属性
imageView
textLabel
detailTextLabel accessoryType
accessoryView backgroundColor,设置单元格的背景颜色 backgroundView 可以利用这个属性来设置单元格的背景图片,指定一个UIImageView就可以了 selectedBackgroundView 当某行被选中的时候的背景
单元格Cell的重用
//注意:只适用于单元格样式一致的时候
//单元格重用的基本方法
//1.声明一个 静态的重用ID (只所以声明静态是为了节省控制器不断的释放,创建成员对象)
//2.根据重用ID去缓存池中获取对应的cell对象
//3.如果没有获取到,就创建一个,如果获取到了就直接设置单元格内容
//4.返回单元格
注意:当使用自定义的cell的时候 是无法通过 dequeueReusableCellWithIdentifier:ID的方式来指定ID的
所以需要在布局文件中进行设置
代码示例:
1. .plist的数据结构
模型代码:
1.1)CZGroup.h
#import <Foundation/Foundation.h> @interface GZGroup :NSObject @property (nonatomic ,copy) NSString *titile;
@property (nonatomic, strong) NSArray *cars; -(instancetype)initWithDict :(NSDictionary *)dict;
+(instancetype)groupWithDict:(NSDictionary *)dict; @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
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 获取模型数据
// 根据组的索引获取对应的组的模型
CZGroup *group = self.groups[indexPath.section];
// 根据当前行的索引, 获取对应组的对应行的车
CZCar *car = group.cars[indexPath.row]; // 2. 创建单元格
// 2.1 声明一个重用ID
static NSString *ID = @"car_cell";
// 2.2 根据重用ID去缓存池中获取对应的cell对象
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 2.3 如果没有获取到, 那么就创建一个
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
} // 3. 设置单元格内容
cell.imageView.image = [UIImage imageNamed:car.icon];
cell.textLabel.text = car.name; // 4. 返回单元格
return cell;
}
自定义Cell
案例一:团购
Viewontroller.m
#import "ViewController.h"
#import "CZGoods.h"
#import "CZGoodsCell.h"
#import "CZFooterView.h"
#import "CZHeaderView.h" @interface ViewController () <UITableViewDataSource, CZFooterViewDelegate> // 用来存储所有的团购商品的数据
@property (nonatomic, strong) NSMutableArray *goods; @property (weak, nonatomic) IBOutlet UITableView *tableView;
@end @implementation ViewController #pragma mark - 懒加载数据
- (NSMutableArray *)goods
{
if (_goods == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"tgs.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
CZGoods *model = [CZGoods goodsWithDict:dict];
[arrayModels addObject:model];
}
_goods = arrayModels;
}
return _goods;
} #pragma mark - 数据源方法
//返回有UITableView 有多少个组,默认为1,当组为1时 可以忽略不写
//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
//{
// return 1;
//} //返回组内有多少行数据
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.goods.count;
} //返回Cell对象
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 获取模型数据
CZGoods *model = self.goods[indexPath.row]; // 2. 创建单元格
// 通过xib的方式来创建单元格
CZGoodsCell *cell = [CZGoodsCell goodsCellWithTableView:tableView]; // 3. 把模型数据设置给单元格
// 在控制器中直接为cell的每个子控件赋值数据造成的问题:
// 1. 控制器强依赖于Cell, 一旦cell内部的子控件发生了变化, 那么控制器中的代码也得改(这就造成了紧耦合)
// 2. cell的封装不够完整, 凡是用到这个cell的地方, 每次都要编写为cell的子控件依次赋值的语句,比如:cell.xxx = model.title;
// 3. 解决: 直接把模型传递给自定义Cell, 然后在自定义cell内部解析model中的数据赋值给自定义cell内部的子控件。
cell.goods = model; // 4.返回单元格
return cell;
} #pragma mark - 隐藏状态栏
- (BOOL)prefersStatusBarHidden
{
return YES;
} - (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = ; // 设置UITableView的footerView
// UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
//
// btn.backgroundColor = [UIColor redColor];
// btn.frame = CGRectMake(20, 50, 30, 100);
// // tableView的footerView的特点: 只能修改x和height的值, Y 和 width不能改。
// self.tableView.tableFooterView = btn; // 通过Xib设置UITableView的footerView
CZFooterView *footerView = [CZFooterView footerView];
// 设置footerView的代理
footerView.delegate = self;
self.tableView.tableFooterView = footerView; // 创建Header View
CZHeaderView *headerView = [CZHeaderView headerView];
self.tableView.tableHeaderView = headerView; } #pragma mark - CZFooterView的代理方法 - (void)footerViewUpdateData:(CZFooterView *)footerView
{
// 3. 增加一条数据 // 3.1 创建一个模型对象
CZGoods *model = [[CZGoods alloc] init];
model.title = @"驴肉火烧";
model.price = @"6.0";
model.buyCount = @"";
model.icon = @"37e4761e6ecf56a2d78685df7157f097"; // 3.2 把模型对象加到控制器的goods集合当中
[self.goods addObject:model]; // 4. 刷新UITableView
[self.tableView reloadData]; // // 局部刷新(只适用于UITableView总行数没有发生变化的情况)
// NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0];
// [self.tableView reloadRowsAtIndexPaths:@[idxPath] withRowAnimation:UITableViewRowAnimationLeft]; // 5. 把UITableView中的最后一行的数据滚动到最上面
NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - inSection:];
[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @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
#import "CZGoodsCell.h"
#import "CZGoods.h" @interface CZGoodsCell ()
@property (weak, nonatomic) IBOutlet UIImageView *imgViewIcon;
@property (weak, nonatomic) IBOutlet UILabel *lblTitle;
@property (weak, nonatomic) IBOutlet UILabel *lblPrice;
@property (weak, nonatomic) IBOutlet UILabel *lblBuyCount; @end @implementation CZGoodsCell + (instancetype)goodsCellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"goods_cell";
CZGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject];
}
return cell;
} - (void)setGoods:(CZGoods *)goods
{
_goods = goods;
// 把模型的数据设置给子控件
self.imgViewIcon.image = [UIImage imageNamed:goods.icon];
self.lblTitle.text = goods.title;
self.lblPrice.text = [NSString stringWithFormat:@"¥ %@", goods.price];
self.lblBuyCount.text = [NSString stringWithFormat:@"%@ 人已购买", goods.buyCount];
} - (void)awakeFromNib {
// Initialization code
} - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated]; // Configure the view for the selected state
} @end
尾部的加载更多按钮
CZFooterView.h
#import <UIKit/UIKit.h>
@class CZFooterView;
//设置一个代理对象
@protocol CZFooterViewDelegate <NSObject, UIScrollViewDelegate>
//提示用户使用这个代理的时候必须实现下面的代理方法
@required
- (void)footerViewUpdateData:(CZFooterView *)footerView;
@end @interface CZFooterView : UIView + (instancetype)footerView;
@property (nonatomic, weak) id<CZFooterViewDelegate> delegate;
@end
CZFooterView.m
#import "CZFooterView.h" @interface CZFooterView ()
@property (weak, nonatomic) IBOutlet UIButton *btnLoadMore;
@property (weak, nonatomic) IBOutlet UIView *waitingView;
- (IBAction)btnLoadMoreClick;
@end @implementation CZFooterView + (instancetype)footerView
{
CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@"CZFooterView" owner:nil options:nil] lastObject];
return footerView;
} /**
* 加载更多按钮的单击事件
*/
- (IBAction)btnLoadMoreClick {
// 1. 隐藏"加载更多"按钮
self.btnLoadMore.hidden = YES; // 2. 显示"等待指示器"所在的那个UIView
self.waitingView.hidden = NO; //GCD方法,标示延迟一定的时间后执行,由于我们这里是模拟操作,当点击按钮后,数据立刻会刷新,所以为了模拟逼真一些,这里就加了一个延迟操作的方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 3. 调用代理方法实现下面的功能
// 调用footerViewUpdateData方法之前, 为了保证调用不出错, 所以要先判断一下代理对象是否真的实现了这个方法, 如果实现了这个方法再调用, 否则不调用.
if ([self.delegate respondsToSelector:@selector(footerViewUpdateData:)]) {
// 3. 增加一条数据
// 3.1 创建一个模型对象
// 3.2 把模型对象加到控制器的goods集合当中
// 4. 刷新UITableView
[self.delegate footerViewUpdateData:self];
} // 4. 显示"加载更多"按钮
self.btnLoadMore.hidden = NO; // 5. 隐藏"等待指示器"所在的那个UIView
self.waitingView.hidden = YES; }); }
@end
案例二:微博
1):当我们的控制器Controller是 tableView的时候,我们可以直接使用Table View Controller,需要指定dataSource 和 delegate的代理对象
2):由于微博中的内容信息是不一致的,table栏的宽度也是不一致的,所以我们没有办法在 viedieLoad中 使用self.tableview.rowHeight 来设置统一的高度,所以我们针对每一个Cell的Frame做了一个封装,在懒加载数据时,将数据直接给Frame对象,然后Frame对象根据内容计算出高度,后再Cell的时候 直接返回Cell对象
封装的Frame对象
CZWeiboFrame.h
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKit.h>
#define nameFont [UIFont systemFontOfSize:12]
#define textFont [UIFont systemFontOfSize:14] @class CZWeibo;
@interface CZWeiboFrame : NSObject @property (nonatomic, strong) CZWeibo *weibo; // 用来保存头像的frame
@property (nonatomic, assign, readonly) CGRect iconFrame; // 昵称的frame
@property (nonatomic, assign, readonly) CGRect nameFrame; // vip的frame
@property (nonatomic, assign, readonly) CGRect vipFrame; // 正文的frame
@property (nonatomic, assign, readonly) CGRect textFrame; //配图的frame
@property (nonatomic, assign, readonly) CGRect picFrame; // 行高
@property (nonatomic, assign, readonly) CGFloat rowHeight; @end
CZWeiboFrame.m
#import "CZWeiboFrame.h"
#import "CZWeibo.h" @implementation CZWeiboFrame // 重写weibo属性的set方法
- (void)setWeibo:(CZWeibo *)weibo
{
_weibo = weibo; // 计算每个控件的frame, 和行高 // 提取统一的间距
CGFloat margin = ; // 1. 头像
CGFloat iconW = ;
CGFloat iconH = ;
CGFloat iconX = margin;
CGFloat iconY = margin;
_iconFrame = CGRectMake(iconX, iconY, iconW, iconH); // 2. 昵称
// 获取昵称字符串
NSString *nickName = weibo.name;
CGFloat nameX = CGRectGetMaxX(_iconFrame) + margin; // 根据Label中文字的内容, 来动态计算Label的高和宽
CGSize nameSize = [self sizeWithText:nickName andMaxSize:CGSizeMake(MAXFLOAT, MAXFLOAT) andFont:nameFont]; CGFloat nameW = nameSize.width;
CGFloat nameH = nameSize.height;
CGFloat nameY = iconY + (iconH - nameH) / ; _nameFrame = CGRectMake(nameX, nameY, nameW, nameH); // 3. 会员
CGFloat vipW = ;
CGFloat vipH = ;
CGFloat vipX = CGRectGetMaxX(_nameFrame) + margin;
CGFloat vipY = nameY;
_vipFrame = CGRectMake(vipX, vipY, vipW, vipH); // 4. 正文
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(_iconFrame) + margin;
CGSize textSize = [self sizeWithText:weibo.text andMaxSize:CGSizeMake(, MAXFLOAT) andFont:textFont];
CGFloat textW = textSize.width;
CGFloat textH = textSize.height;
_textFrame = CGRectMake(textX, textY, textW, textH); // 5. 配图
CGFloat picW = ;
CGFloat picH = ;
CGFloat picX = iconX;
CGFloat picY = CGRectGetMaxY(_textFrame) + margin;
_picFrame = CGRectMake(picX, picY, picW, picH); //6. 计算每行的高度
CGFloat rowHeight = ;
if (self.weibo.picture) {
// 如果有配图, 那么行高就等于配图的最大的Y值 + margin
rowHeight = CGRectGetMaxY(_picFrame) + margin;
} else {
// 如果没有配图, 那么行高就等于正文的最大的Y值 + margin
rowHeight = CGRectGetMaxY(_textFrame) + margin;
} // 注意::: 计算完毕行高以后,不要忘记为属性赋值。
_rowHeight = rowHeight; } // 根据给定的字符串、最大值的size、给定的字体, 来计算文字应该占用的大小
- (CGSize)sizeWithText:(NSString *)text andMaxSize:(CGSize)maxSize andFont:(UIFont *)font
{
NSDictionary *attr = @{NSFontAttributeName : font};
return [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size;
}
@end
自定义的cell对象
之前封装的frame主要用来存储所需要的对象数据,以及计算各个控件和总的tableView Item的frame
而我们在cell中是用来创建我们需要的对象,以及将最终形成的布局文件返回给tableView
CZWeiboCell.h
#import <UIKit/UIKit.h>
@class CZWeiboFrame;
@interface CZWeiboCell : UITableViewCell @property (nonatomic, strong) CZWeiboFrame *weiboFrame; + (instancetype)weiboCellWithTableView:(UITableView *)tableView;
@end
CZWeiboCell.m
#import "CZWeiboCell.h"
#import "CZWeibo.h"
#import "CZWeiboFrame.h" @interface CZWeiboCell ()
@property (nonatomic, weak) UIImageView *imgViewIcon;
@property (nonatomic, weak) UILabel *lblNickName;
@property (nonatomic, weak) UIImageView *imgViewVip;
@property (nonatomic, weak) UILabel *lblText;
@property (nonatomic, weak) UIImageView *imgViewPicture; @end @implementation CZWeiboCell #pragma mark - 重写单元格的initWithStyle:方法 + (instancetype)weiboCellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"weibo_cell";
CZWeiboCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[CZWeiboCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
return cell;
} - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 创建5个子控件 // 1. 头像
UIImageView *imgViewIcon = [[UIImageView alloc] init];
[self.contentView addSubview:imgViewIcon];
self.imgViewIcon = imgViewIcon; // 2. 昵称
UILabel *lblNickName = [[UILabel alloc] init];
// 设置Label的文字大小
lblNickName.font = nameFont; [self.contentView addSubview:lblNickName];
self.lblNickName = lblNickName; // 3. 会员
UIImageView *imgViewVip = [[UIImageView alloc] init];
imgViewVip.image = [UIImage imageNamed:@"vip"];
[self.contentView addSubview:imgViewVip];
self.imgViewVip = imgViewVip; // 4. 正文
UILabel *lblText = [[UILabel alloc] init];
lblText.font = textFont;
// 设置正文的Label可以自动换行
lblText.numberOfLines = ;
[self.contentView addSubview:lblText];
self.lblText = lblText; // 5. 配图
UIImageView *imgViewPicture = [[UIImageView alloc] init];
[self.contentView addSubview:imgViewPicture];
self.imgViewPicture = imgViewPicture;
}
return self;
} #pragma mark - 重写weibo属性的set方法
- (void)setWeiboFrame:(CZWeiboFrame *)weiboFrame
{
_weiboFrame = weiboFrame; // 1. 设置当前单元格中的子控件的数据
[self settingData]; // 2. 设置当前单元格中的子控件的frame
[self settingFrame];
} // 设置数据的方法
- (void)settingData
{
CZWeibo *model = self.weiboFrame.weibo;
// 1. 头像
self.imgViewIcon.image = [UIImage imageNamed:model.icon]; // 2. 昵称
self.lblNickName.text = model.name; // 3. 会员
if (model.isVip) {
// 设置显示会员图标
self.imgViewVip.hidden = NO;
// 设置昵称的颜色是红色
self.lblNickName.textColor = [UIColor redColor];
} else {
// 设置隐藏会员图标
self.imgViewVip.hidden = YES;
// 设置昵称的颜色是黑色
self.lblNickName.textColor = [UIColor blackColor];
} // 4. 正文
self.lblText.text = model.text; // 5. 配图
if (model.picture) {
// 有配图
// 如果model.picture的值是nil, 那么下面这句话执行会报异常
self.imgViewPicture.image = [UIImage imageNamed:model.picture];
// 显示图片框
self.imgViewPicture.hidden = NO;
} else {
// 如果没有配图, 隐藏图片框
self.imgViewPicture.hidden = YES;
} } // 设置frame的方法
- (void)settingFrame
{
// 1. 头像 self.imgViewIcon.frame = self.weiboFrame.iconFrame; // 2. 昵称
self.lblNickName.frame = self.weiboFrame.nameFrame; // 3. 会员
self.imgViewVip.frame = self.weiboFrame.vipFrame; // 4. 正文 self.lblText.frame = self.weiboFrame.textFrame; // 5. 配图
self.imgViewPicture.frame = self.weiboFrame.picFrame;
} - (void)awakeFromNib {
// Initialization code
} - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated]; // Configure the view for the selected state
} @end
TableViewController控制器的类
这个类会把我们需要重写的方法展现出来,我们只需要对我们用到的方法进行重写就可以了
#import "CZTableViewController.h"
#import "CZWeibo.h"
#import "CZWeiboCell.h"
#import "CZWeiboFrame.h" @interface CZTableViewController () // 现在要求weiboFrames集合中保存的很多个CZWeiboFrame模型,不再是CZWeibo模型了。
@property (nonatomic, strong) NSArray *weiboFrames; @end @implementation CZTableViewController #pragma mark - 懒加载数据
- (NSArray *)weiboFrames
{
if (_weiboFrames == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"weibos.plist" ofType:nil]; NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; NSMutableArray *arrayModels = [NSMutableArray array]; for (NSDictionary *dict in arrayDict) {
// 创建一个数据模型
CZWeibo *model = [CZWeibo weiboWithDict:dict]; // 创建一个frame 模型
// 创建了一个空得frame模型
CZWeiboFrame *modelFrame = [[CZWeiboFrame alloc] init]; // 把数据模型赋值给了modeFrame模型中的weibo属性
modelFrame.weibo = model; [arrayModels addObject:modelFrame];
}
_weiboFrames = arrayModels;
}
return _weiboFrames;
} - (void)viewDidLoad {
[super viewDidLoad]; // 统一设置行高
//self.tableView.rowHeight = 300; // NSLog(@"%@", self.view);
// NSLog(@"%@", self.tableView); // Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} #pragma mark - Table view 数据源方法 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return ;
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.weiboFrames.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1. 获取模型数据
CZWeiboFrame *model = self.weiboFrames[indexPath.row]; // 2. 创建单元格
CZWeiboCell *cell = [CZWeiboCell weiboCellWithTableView:tableView]; // 3. 设置单元格数据
cell.weiboFrame = model; // 4. 返回单元格
return cell;
} #pragma mark - Table view 代理方法 // 返回每行的行高的方法,对于这个案例,其中最重要的就是关于如何计算行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CZWeiboFrame *weiboFrame = self.weiboFrames[indexPath.row];
return weiboFrame.rowHeight;
} - (BOOL)prefersStatusBarHidden
{
return YES;
} @end
案例三:做一个类似于QQ聊天的tableView界面
这个界面有两个难点:
1.信息背后的框体 要包裹住消息内容
2.监听系统的键盘弹出事件,将我们的View 整体向上位移
问题1:
我们通过 设置 内边距的形式来进行解决 btnText.contentEdgeInsets = UIEdgeInsetsMake(, , , ); 同时对于图片我们要选择平铺的方式进行拉伸 // 加载图片
UIImage *imageNormal = [UIImage imageNamed:imgNor];
UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted]; // 用平铺的方式拉伸图片
imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5];
imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5]; // 设置背景图
[self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal];
[self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted];
问题二:
- (void)viewDidLoad {
[super viewDidLoad];
// 取消分割线
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // 设置UITableView的背景色
self.tableView.backgroundColor = [UIColor colorWithRed: / 255.0 green: / 255.0 blue: / 255.0 alpha:1.0]; // 设置UITableView的行不允许被选中
self.tableView.allowsSelection = NO; // 设置文本框最左侧有一段间距
UIView *leftVw = [[UIView alloc] init];
leftVw.frame = CGRectMake(, , , ); // 把leftVw设置给文本框
self.txtInput.leftView = leftVw;
self.txtInput.leftViewMode = UITextFieldViewModeAlways; // 监听键盘的弹出事件
// 1. 创建一个NSNotificationCenter对象。
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; // 2. 监听键盘的弹出通知
[center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; } - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo
{
// NSLog(@"通知名称: %@", noteInfo.name);
//
// NSLog(@"通知的发布者: %@", noteInfo.object);
//
// NSLog(@"通知的具体内容: %@", noteInfo.userInfo);
// 1. 获取当键盘显示完毕或者隐藏完毕后的Y值
CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardY = rectEnd.origin.y; // 用键盘的Y值减去屏幕的高度计算出平移的值
// 1. 如果是键盘弹出事件, 那么计算出的值就是负的键盘的高度
// 2. 如果是键盘的隐藏事件, 那么计算出的值就是零, 因为键盘在隐藏以后, 键盘的Y值就等于屏幕的高度。
CGFloat tranformValue = keyboardY - self.view.frame.size.height; [UIView animateWithDuration:0.25 animations:^{
// 让控制器的View执行一次“平移”
self.view.transform = CGAffineTransformMakeTranslation(, tranformValue);
}]; // 让UITableView的最后一行滚动到最上面
NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - inSection:];
[self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
} // ***************** 注意: 监听通知以后一定要在监听通知的对象的dealloc方法中移除监听 *************/. - (void)dealloc
{
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
整体代码
1.做一个NSString的类扩展 用于字体最大的尺寸
NSString+CZNSStringExt.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface NSString (CZNSStringExt) // 对象方法
- (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font; // 类方法
+ (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font;
@end
NSString+CZNSStringExt.m
#import "NSString+CZNSStringExt.h" @implementation NSString (CZNSStringExt) // 实现对象方法
- (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font
{
NSDictionary *attrs = @{NSFontAttributeName : font};
return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
} // 类方法
+ (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font
{
return [text sizeOfTextWithMaxSize:maxSize font:font];
} @end
2.控制器对象
ViewController.m
#import "ViewController.h"
#import "CZMessage.h"
#import "CZMessageFrame.h"
#import "CZMessageCell.h" @interface ViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView; // 用来保存所有的消息的frame模型对象
@property (nonatomic, strong) NSMutableArray *messageFrames;
@property (weak, nonatomic) IBOutlet UITextField *txtInput; @end @implementation ViewController
#pragma mark - /********** 懒加载数据 *********/
- (NSMutableArray *)messageFrames
{
if (_messageFrames == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
// 创建一个数据模型
CZMessage *model = [CZMessage messageWithDict:dict]; // 获取上一个数据模型
CZMessage *lastMessage = (CZMessage *)[[arrayModels lastObject] message]; // 判断当前模型的“消息发送时间”是否和上一个模型的“消息发送时间”一致, 如果一致做个标记
if ([model.time isEqualToString:lastMessage.time]) {
model.hideTime = YES;
} // 创建一个frame 模型
CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init]; modelFrame.message = model; // 把frame 模型加到arrayModels
[arrayModels addObject:modelFrame];
}
_messageFrames = arrayModels;
}
return _messageFrames;
} #pragma mark - /********** 文本框的代理方法 *********/
//- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
//{
// return YES;
//} // 当键盘上的return键被单击的时候触发
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
// 1. 获取用户输入的文本
NSString *text = textField.text; // 2. 发送用户的消息
[self sendMessage:text withType:CZMessageTypeMe]; // 3. 发送一个系统消息
[self sendMessage:@"不认识!" withType:CZMessageTypeOther]; // 清空文本框
textField.text = nil; return YES;
} // 发送消息
- (void)sendMessage:(NSString *)msg withType:(CZMessageType)type
{
// 2. 创建一个数据模型和frame 模型
CZMessage *model = [[CZMessage alloc] init]; // 获取当前系统时间
NSDate *nowDate = [NSDate date];
// 创建一个日期时间格式化器
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// 设置格式
formatter.dateFormat = @"今天 HH:mm";
// 进行日期时间的格式化
model.time = [formatter stringFromDate:nowDate];
model.type = type;
model.text = msg; // 根据当前消息的时间和上一条消息的时间, 来设置是否需要隐藏时间Label
CZMessageFrame *lastMessageFrame = [self.messageFrames lastObject];
NSString *lastTime = lastMessageFrame.message.time;
if ([model.time isEqualToString:lastTime]) {
model.hideTime = YES;
} //***** 注意: 要先设置数据模型的hideTime属性, 然后再设置modelFrame.message = model;
// 因为在设置modelFrame.message = model;的时候set方法中, 内部会用到model.hideTime属性。 // 创建一个frame 模型
CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init];
modelFrame.message = model; // 3. 把frame 模型加到集合中
[self.messageFrames addObject:modelFrame]; // 4. 刷新UITableView的数据
[self.tableView reloadData]; // 5. 把最后一行滚动到最上面
NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - inSection:];
[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
} #pragma mark - /********** UITableView的代理方法 *********/
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// 把键盘叫回去, 思路: 让控制器所管理的UIView结束编辑
[self.view endEditing:YES];
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"★★★★★★★★★");
} #pragma mark - /********** 数据源方法 *********/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return ;
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.messageFrames.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 获取模型数据
CZMessageFrame *modelFrame = self.messageFrames[indexPath.row]; // 2. 创建单元格 CZMessageCell *cell = [CZMessageCell messageCellWithTableView:tableView]; // 3. 把模型设置给单元格对象
cell.messageFrame = modelFrame; // 4.返回单元格
return cell;
} // 返回每一行的行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CZMessageFrame *messageFrame = self.messageFrames[indexPath.row];
return messageFrame.rowHeight;
} #pragma mark - /********** 其他 *********/
- (void)viewDidLoad {
[super viewDidLoad];
// 取消分割线
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // 设置UITableView的背景色
self.tableView.backgroundColor = [UIColor colorWithRed: / 255.0 green: / 255.0 blue: / 255.0 alpha:1.0]; // 设置UITableView的行不允许被选中
self.tableView.allowsSelection = NO; // 设置文本框最左侧有一段间距
UIView *leftVw = [[UIView alloc] init];
leftVw.frame = CGRectMake(, , , ); // 把leftVw设置给文本框
self.txtInput.leftView = leftVw;
self.txtInput.leftViewMode = UITextFieldViewModeAlways; // 监听键盘的弹出事件
// 1. 创建一个NSNotificationCenter对象。
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; // 2. 监听键盘的弹出通知
[center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; } - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo
{
// NSLog(@"通知名称: %@", noteInfo.name);
//
// NSLog(@"通知的发布者: %@", noteInfo.object);
//
// NSLog(@"通知的具体内容: %@", noteInfo.userInfo);
// 1. 获取当键盘显示完毕或者隐藏完毕后的Y值
CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardY = rectEnd.origin.y; // 用键盘的Y值减去屏幕的高度计算出平移的值
// 1. 如果是键盘弹出事件, 那么计算出的值就是负的键盘的高度
// 2. 如果是键盘的隐藏事件, 那么计算出的值就是零, 因为键盘在隐藏以后, 键盘的Y值就等于屏幕的高度。
CGFloat tranformValue = keyboardY - self.view.frame.size.height; [UIView animateWithDuration:0.25 animations:^{
// 让控制器的View执行一次“平移”
self.view.transform = CGAffineTransformMakeTranslation(, tranformValue);
}]; // 让UITableView的最后一行滚动到最上面
NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - inSection:];
[self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
} // ***************** 注意: 监听通知以后一定要在监听通知的对象的dealloc方法中移除监听 *************/. - (void)dealloc
{
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} - (BOOL)prefersStatusBarHidden
{
return YES;
} @end
3.自定义的cell对象
CZMessageCell.h
#import <UIKit/UIKit.h> @class CZMessageFrame;
@interface CZMessageCell : UITableViewCell // 为自定义单元格增加一个frame 模型属性
@property (nonatomic, strong) CZMessageFrame *messageFrame; // 封装一个创建自定义Cell的方法
+ (instancetype)messageCellWithTableView:(UITableView *)tableView; @end
CZMessageCell.m
#import "CZMessageCell.h"
#import "CZMessage.h"
#import "CZMessageFrame.h" @interface CZMessageCell () @property (nonatomic, weak) UILabel *lblTime;
@property (nonatomic, weak) UIImageView *imgViewIcon;
@property (nonatomic, weak) UIButton *btnText;
@end @implementation CZMessageCell #pragma mark - 重写initWithStyle方法
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 创建子控件 // 显示时间的label
UILabel *lblTime = [[UILabel alloc] init];
// 设置文字大小
lblTime.font = [UIFont systemFontOfSize:];
// 设置文字居中
lblTime.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:lblTime];
self.lblTime = lblTime; // 显示头像的UIImageView
UIImageView *imgViewIcon = [[UIImageView alloc] init];
[self.contentView addSubview:imgViewIcon];
self.imgViewIcon = imgViewIcon; // 显示正文的按钮
UIButton *btnText = [[UIButton alloc] init];
// 设置正文的字体大小
btnText.titleLabel.font = textFont;
// 修改按钮的正文文字颜色
[btnText setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
// 设置按钮中的label的文字可以换行
btnText.titleLabel.numberOfLines = ;
// 设置按钮的背景色
//btnText.backgroundColor = [UIColor purpleColor]; // 设置按钮中的titleLabel的背景色
//btnText.titleLabel.backgroundColor = [UIColor greenColor]; // 设置按钮的内边距
btnText.contentEdgeInsets = UIEdgeInsetsMake(, , , ); [self.contentView addSubview:btnText];
self.btnText = btnText;
} // 设置单元格的背景色为clearColor
self.backgroundColor = [UIColor clearColor];
return self;
} #pragma mark - 重写frame 模型的set方法
- (void)setMessageFrame:(CZMessageFrame *)messageFrame
{
_messageFrame = messageFrame; // 获取数据模型
CZMessage *message = messageFrame.message; // 分别设置每个子控件的数据 和 frame // 设置 "时间Label"的数据 和 frame
self.lblTime.text = message.time;
self.lblTime.frame = messageFrame.timeFrame;
self.lblTime.hidden = message.hideTime; // 设置 头像
// 根据消息类型, 判断应该使用哪张图片
NSString *iconImg = message.type == CZMessageTypeMe ? @"me" : @"other";
self.imgViewIcon.image = [UIImage imageNamed:iconImg];
self.imgViewIcon.frame = messageFrame.iconFrame; // 设置消息正文
[self.btnText setTitle:message.text forState:UIControlStateNormal];
self.btnText.frame = messageFrame.textFrame; // 设置正文的背景图
NSString *imgNor, *imgHighlighted;
if (message.type == CZMessageTypeMe) {
// 自己发的消息
imgNor = @"chat_send_nor";
imgHighlighted = @"chat_send_press_pic"; // 设置消息的正文文字颜色为 "白色"
[self.btnText setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
} else {
// 对方发的消息
imgNor = @"chat_recive_nor";
imgHighlighted = @"chat_recive_press_pic"; // 设置消息的正文文字颜色为 "黑色"
[self.btnText setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
} // 加载图片
UIImage *imageNormal = [UIImage imageNamed:imgNor];
UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted]; // 用平铺的方式拉伸图片
imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5];
imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5]; // 设置背景图
[self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal];
[self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted];
} #pragma mark - 创建自定义Cell的方法
+ (instancetype)messageCellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"message_cell";
CZMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[CZMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
return cell;
} - (void)awakeFromNib {
// Initialization code
} - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated]; // Configure the view for the selected state
} @end
4.消息模型
CZMessage.h
#import <Foundation/Foundation.h> typedef enum {
CZMessageTypeMe = , // 表示自己
CZMessageTypeOther = // 表示对方
} CZMessageType; @interface CZMessage : NSObject // 消息正文
@property (nonatomic, copy) NSString *text; // 消息发送时间
@property (nonatomic, copy) NSString *time; // 消息的类型(表示是对方发送的消息还是自己发送的消息)
@property (nonatomic, assign) CZMessageType type; // 用来记录是否需要显示"时间Label"
@property (nonatomic, assign) BOOL hideTime; - (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)messageWithDict:(NSDictionary *)dict; @end
CZMeesge.m
#import "CZMessage.h" @implementation CZMessage - (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
} + (instancetype)messageWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
5.展示信息的frame
CZMessageFram.h
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#define textFont [UIFont systemFontOfSize:13] @class CZMessage;
@interface CZMessageFrame : NSObject // 引用数据模型
@property (nonatomic, strong) CZMessage *message; // 时间Label的frame
@property (nonatomic, assign, readonly) CGRect timeFrame; // 头像的frame
@property (nonatomic, assign, readonly) CGRect iconFrame; // 正文的frame
@property (nonatomic, assign, readonly) CGRect textFrame; // 行高
@property (nonatomic, assign, readonly) CGFloat rowHeight; @end
CZMessageFrame.m
#import "CZMessageFrame.h"
#import <UIKit/UIKit.h>
#import "CZMessage.h"
#import "NSString+CZNSStringExt.h" @implementation CZMessageFrame - (void)setMessage:(CZMessage *)message
{
_message = message; // 计算每个控件的frame 和 行高
// 获取屏幕宽度
CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
// 设置一个统一的间距
CGFloat margin = ; // 计算时间label的frame
CGFloat timeX = ;
CGFloat timeY = ;
CGFloat timeW = screenW;
CGFloat timeH = ;
if (!message.hideTime) {
// 如果需要显示时间label, 那么再计算时间label的frame
_timeFrame = CGRectMake(timeX, timeY, timeW, timeH);
} // 计算头像的frame
CGFloat iconW = ;
CGFloat iconH = ;
CGFloat iconY = CGRectGetMaxY(_timeFrame) + margin;
CGFloat iconX = message.type == CZMessageTypeOther ? margin : screenW - margin - iconW;
_iconFrame = CGRectMake(iconX, iconY, iconW, iconH); // 计算消息正文的frame
// 1. 先计算正文的大小
CGSize textSize = [message.text sizeOfTextWithMaxSize:CGSizeMake(, MAXFLOAT) font:textFont];
CGFloat textW = textSize.width + ;
CGFloat textH = textSize.height + ;
// 2. 再计算x,y
CGFloat textY = iconY;
CGFloat textX = message.type == CZMessageTypeOther ? CGRectGetMaxX(_iconFrame) : (screenW - margin - iconW - textW);
_textFrame = CGRectMake(textX, textY, textW, textH); // 计算行高
// 获取 头像的最大的Y值和正文的最大的Y值, 然后用最大的Y值+ margin
CGFloat maxY = MAX(CGRectGetMaxY(_textFrame), CGRectGetMaxY(_iconFrame));
_rowHeight = maxY + margin; }
@end
方案四,好友列表
几个要注意的:
1.组上得图标旋转后变形
2.cell重用的问题
这里直接继承的TableViewController控制器 直接上代码了
CZQQFriendsTableViewController.m
#import "CZQQFriendsTableViewController.h"
#import "CZGroup.h"
#import "CZFriend.h"
#import "CZFriendCell.h"
#import "CZGroupHeaderView.h" @interface CZQQFriendsTableViewController () <CZGroupHeaderViewDelegate> // 保存所有的朋友信息(分组信息)
@property (nonatomic, strong) NSArray *groups;
@end @implementation CZQQFriendsTableViewController #pragma mark - *********** 懒加载 ***********
- (NSArray *)groups
{
if (_groups == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"friends.plist" ofType:nil];
NSArray *arrayDicts = [NSArray arrayWithContentsOfFile:path]; NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict in arrayDicts) {
CZGroup *model = [CZGroup groupWithDict:dict];
[arrayModels addObject:model];
}
_groups = arrayModels; }
return _groups;
} #pragma mark - *********** CZGroupHeaderViewDelegate的代理方法 ***********
- (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView
{
// 刷新table view
//[self.tableView reloadData]; // 局部刷新(只刷新某个组)
// 创建一个用来表示某个组的对象
NSIndexSet *idxSet = [NSIndexSet indexSetWithIndex:groupHeaderView.tag];
[self.tableView reloadSections:idxSet withRowAnimation:UITableViewRowAnimationFade];
} #pragma mark - *********** 实现数据源方法 ***********
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.groups.count;
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// 因为在这个方法中, 要根据当前组的状态(是否是展开), 来设置不同的返回值
// 所以, 需要为CZGroup模型增加一个用来保存"是否展开"状态的属性
CZGroup *group = self.groups[section];
if (group.isVisible) {
return group.friends.count;
} else {
return ;
} } // 返回每行的单元格
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 获取模型对象(数据)
CZGroup *group = self.groups[indexPath.section];
CZFriend *friend = group.friends[indexPath.row]; // 2. 创建单元格(视图)
CZFriendCell *cell = [CZFriendCell friendCellWithTableView:tableView]; // 3. 设置单元格数据(把模型设置给单元格)
cell.friendModel = friend; // 4. 返回单元格
return cell;
} //// 设置每一组的组标题(下面的这个方法只能设置每一组的组标题字符串, 但是我们要的是每一组中还包含其他子控件)
//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
//{
// CZGroup *group = self.groups[section];
// return group.name;
//} - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// 不要在这个方法中直接创建一个UIView对象返回, 因为这样无法实现重用该UIView
// 为了能重用每个Header中的UIView, 所以这里要返回一个UITableViewHeaderFooterView
// 1. 获取模型数据
CZGroup *group = self.groups[section]; // 2. 创建UITableViewHeaderFooterView
CZGroupHeaderView *headerVw = [CZGroupHeaderView groupHeaderViewWithTableView:tableView];
headerVw.tag = section; // 3. 设置数据
headerVw.group = group; // 设置headerView的代理为当前控制器
headerVw.delegate = self; // 在刚刚创建好的header view中获取的header view的frame都是0, 因为刚刚创建好的header view我们没有为其frame赋值, 所以frame都是 0
// 但是, 程序运行起来以后, 我们看到的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赋值。 // 4. 返回view
return headerVw; } #pragma mark - *********** 隐藏状态栏 ***********
- (BOOL)prefersStatusBarHidden
{
return YES;
} #pragma mark - *********** 控制器的viewDidLoad方法 ***********
- (void)viewDidLoad
{
[super viewDidLoad]; // 统一设置每组的组标题的高度
self.tableView.sectionHeaderHeight = ;
} @end
cell模型对象
CZFriendCell.h
#import <UIKit/UIKit.h>
@class CZFriend;
@interface CZFriendCell : UITableViewCell + (instancetype)friendCellWithTableView:(UITableView *)tableView; @property (nonatomic, strong) CZFriend *friendModel;
@end
CZFriendCell.m
#import "CZFriendCell.h"
#import "CZFriend.h" @implementation CZFriendCell + (instancetype)friendCellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"friend_cell";
CZFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[CZFriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
return cell;
} - (void)setFriendModel:(CZFriend *)friendModel
{
_friendModel = friendModel; // 把模型中的数据设置给单元格的子控件
self.imageView.image = [UIImage imageNamed:friendModel.icon];
self.textLabel.text = friendModel.name;
self.detailTextLabel.text = friendModel.intro; // 根据当前的好友是不是vip来决定是否要将"昵称"显示为红色
self.textLabel.textColor = friendModel.isVip ? [UIColor redColor] : [UIColor blackColor];
} - (void)awakeFromNib {
// Initialization code
} - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated]; // Configure the view for the selected state
} @end
CZGroupHeaderView.h
#import <UIKit/UIKit.h>
@class CZGroupHeaderView;
@protocol CZGroupHeaderViewDelegate <NSObject> - (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView; @end @class CZGroup;
@interface CZGroupHeaderView : UITableViewHeaderFooterView @property (nonatomic, strong) CZGroup *group; + (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView; // 增加一个代理属性
@property (nonatomic, weak) id<CZGroupHeaderViewDelegate> delegate; @end
CZGroupHeaderView.m
#import "CZGroupHeaderView.h"
#import "CZGroup.h" @interface CZGroupHeaderView () @property (nonatomic, weak) UIButton *btnGroupTitle; @property (nonatomic, weak) UILabel *lblCount; @end @implementation CZGroupHeaderView // 封装一个类方法来创建一个header view
+ (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView
{
static NSString *ID = @"group_header_view";
CZGroupHeaderView *headerVw = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
if (headerVw == nil) {
headerVw = [[CZGroupHeaderView alloc] initWithReuseIdentifier:ID];
}
return headerVw;
} // 重写initWithReuseIdentifier方法, 在创建headerView的时候, 同时创建子控件
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
// 创建按钮
UIButton *btnGroupTitle = [[UIButton alloc] init];
// 设置按钮的图片(三角图片)
[btnGroupTitle setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal];
// 设置按钮的文字颜色
[btnGroupTitle setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 设置按钮默认的背景图片和高亮时的背景图片
[btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg"] forState:UIControlStateNormal];
// 设置按钮高亮的背景图片和高亮时的背景图片
[btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted];
// 设置按钮中内容整体左对齐
btnGroupTitle.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
// 设置按钮的内容的内边距
btnGroupTitle.contentEdgeInsets = UIEdgeInsetsMake(, , , );
// 设置按钮标题距离左边的边距
btnGroupTitle.titleEdgeInsets = UIEdgeInsetsMake(, , , ); // 为按钮增加一个点击事件
[btnGroupTitle addTarget:self action:@selector(btnGroupTitleClicked) forControlEvents:UIControlEventTouchUpInside]; // 设置按钮中图片的现实模式
btnGroupTitle.imageView.contentMode = UIViewContentModeCenter;
// 设置图片框超出的部分不要截掉
btnGroupTitle.imageView.clipsToBounds = NO; [self.contentView addSubview:btnGroupTitle];
self.btnGroupTitle = btnGroupTitle; // 创建lable
UILabel *lblCount = [[UILabel alloc] init];
[self.contentView addSubview:lblCount];
self.lblCount = lblCount;
}
return self;
} // 组标题按钮的点击事件
- (void)btnGroupTitleClicked
{
// 1. 设置组的状态
self.group.visible = !self.group.isVisible; // // 2.刷新tableView
// 通过代理来实现
if ([self.delegate respondsToSelector:@selector(groupHeaderViewDidClickTitleButton:)]) {
// 调用代理方法
[self.delegate groupHeaderViewDidClickTitleButton:self];
} } // 当一个新的header view 已经加到某个父控件中的时候执行这个方法。
- (void)didMoveToSuperview
{
if (self.group.isVisible) {
// 3. 让按钮中的图片实现旋转
self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
} else {
self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation();
}
} // 重写group属性的set方法
- (void)setGroup:(CZGroup *)group
{
_group = group;
// 设置数据 // 设置按钮上的文字
[self.btnGroupTitle setTitle:group.name forState:UIControlStateNormal];
// 设置 lblCount商的文字
self.lblCount.text = [NSString stringWithFormat:@"%d / %d", group.online, group.friends.count]; // 设置按钮中的图片旋转问题
if (self.group.isVisible) {
// 3. 让按钮中的图片实现旋转
self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
} else {
self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation();
} // 设置frame不要写在这里, 因为在这里获取的当前控件(self)的宽和高都是0 } // 当当前控件的frame发生改变的时候会调用这个方法
- (void)layoutSubviews
{
[super layoutSubviews]; // 设置按钮的frame
self.btnGroupTitle.frame = self.bounds;
//NSLog(@"%@", NSStringFromCGRect(self.btnGroupTitle.frame)); // 设置lable的frame
CGFloat lblW = ;
CGFloat lblH = self.bounds.size.height;
CGFloat lblX = self.bounds.size.width - - lblW;
CGFloat lblY = ;
self.lblCount.frame = CGRectMake(lblX, lblY, lblW, lblH);
//NSLog(@"%@", NSStringFromCGRect(self.lblCount.frame));
} /*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/ @end