#pragma mark - 数据源方法
// 如果没有实现,默认是1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
// 每个分组中的数据总数
// sction:分组的编号
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
// 第0个分组
return 5;
} else {
return 18;
}
}
// 告诉表格控件,每一行cell单元格的细节
// indexPath
// @property(nonatomic,readonly) NSInteger section; 分组
// @property(nonatomic,readonly) NSInteger row; 行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 实例化TableViewCell时,使用initWithStyle方法来进行实例化
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.textLabel.text = [NSString stringWithFormat:@"XXXX %02d X - %04d", indexPath.section, indexPath.row];
return cell;
}
// 返回分组的标题文字
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:@"XXX %02d X", section];
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
if (section == 0) {
return @"太牛叉了";
} else {
return @"牛叉闪闪亮";
}
}
必备代码:
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)heroWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+ (NSArray *)heros
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"heros.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrayM addObject:[self heroWithDict:dict]];
}
return arrayM;
}
/**
UITableViewCellStyleDefault, 默认类型 标题+可选图像
UITableViewCellStyleValue1, 标题+明细+图像
UITableViewCellStyleValue2, 不显示图像,标题+明细
UITableViewCellStyleSubtitle 标题+明细+图像
*/
// 告诉表格每个单元格的明细信息
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"表格行明细 %d", indexPath.row);
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
// 取出英雄对象
HMHero *hero = self.heros[indexPath.row];
// 标题
cell.textLabel.text = hero.name;
// 明细信息
cell.detailTextLabel.text = hero.intro;
// 图像
cell.imageView.image = [UIImage imageNamed:hero.icon];
// 设置右边的箭头
// 1> UITableViewCellAccessoryDisclosureIndicator 箭头,可以"提示"用户,当前行是可以点击的,通常选中行,会跳到新的页面
// 2> UITableViewCellAccessoryCheckmark 对号,通常提示用户该行数据设置完毕,使用的比较少
// 3> UITableViewCellAccessoryDetailButton 按钮,通常点击按钮可以做独立的操作,例如alertView
// 点击按钮并不会选中行
// 4> UITableViewCellAccessoryDetailDisclosureButton 按钮+箭头,各自操作
// cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
// 指定右侧的自定义视图
/**
通常accessoryType提供的类型不能满足时,才会使用自定义控件
但是需要自行添加监听方法,通常用在自定义cell,不要写在视图控制器中!!!
自定义控件的事件触发,同样不会影响表格行的选中!
*/
UISwitch *switcher = [[UISwitch alloc] init];
// 添加监听方法
[switcher addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
cell.accessoryView = switcher;
return cell;
}
/**
代理方法的优先级比rowHeight优先级高
应用场景:很多应用程序,每一行的高度是不一样的,例如:新浪微博
表格工作观察的小结
1> 要知道总共有多少数据
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
2> 计算“每一行”的行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
问题:在此方法执行时,cell被实例化了吗?
方法的作用是什么?
scrollView需要指定contentSize才能够滚动,如果没有实现方法,行高默认是44
需要知道每一行的高度,才能够准确的计算出contentSize
知道每一行的高度后,自然知道每一个屏幕应该显示多少行,表格明细方法的执行次数就知道了
3> 表格明细
调用屏幕显示所需要的行数,懒加载,只有要显示的表格行,才会被实例化!
小的结论:
* tableView.rowHeight: 效率高,适用于所有的表格行高度一致
* 代理方法指定行高: 效率差,适合于每一个行的行高不一样,能够让表格更加的灵活
*/
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self) {
// [self setValuesForKeysWithDictionary:dict];
[self setValue:dict[@"title"] forKey:@"title"];
// dict[@"cars"]存放的是字典的数组
// 希望将字典的数组转换成HMCar模型的数组
// [self setValue:dict[@"cars"] forKey:@"cars"];
self.cars = [HMCar carsWithArray:dict[@"cars"]];
}
return self;
}
+ (instancetype)carGroupWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+ (NSArray *)carGroups
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cars_total.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrayM addObject:[self carGroupWithDict:dict]];
}
return arrayM;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p> {title: %@, cars: %@}", self.class, self, self.title, self.cars];
}
// 单元格
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 可重用标示符
static NSString *ID = @"Cell";
// 让表格缓冲区查找可重用cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 如果没有找到可重用cell
if (cell == nil) {
// 实例化cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
// 设置cell内容
// 1> 取出数据模型
HMCarGroup *group = self.carGroups[indexPath.section];
HMCar *car = group.cars[indexPath.row];
// 2> 设置数据
cell.imageView.image = [UIImage imageNamed:car.icon];
cell.textLabel.text = car.name;
return cell;
}
// 右侧索引列表
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
// 索引数组中的"内容",跟分组无关
// 索引数组中的下标,对应的是分组的下标
// return @[@"哇哈哈", @"hello", @"哇哈哈", @"hello", @"哇哈哈", @"hello", @"哇哈哈", @"hello"];
// 返回self.carGroup中title的数组
// NSMutableArray *arrayM = [NSMutableArray array];
// for (HMCarGroup *group in self.carGroups) {
// [arrayM addObject:group.title];
// }
// return arrayM;
// KVC是cocoa的大招
// 用来间接获取或者修改对象属性的方式
// 使用KVC在获取数值时,如果指定对象不包含keyPath的"键名",会自动进入对象的内部查找
// 如果取值的对象是一个数组,同样返回一个数组
NSArray *array = [self.carGroups valueForKeyPath:@"cars.name"];
NSLog(@"%@", array);
return [self.carGroups valueForKeyPath:@"title"];
}
// 只要实现了此方法,就能够支持手势拖拽删除了,删除需要自己干!
/**
UITableViewCellEditingStyleNone,
UITableViewCellEditingStyleDelete, 删除
UITableViewCellEditingStyleInsert 添加
*/
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSLog(@"要删除");
// MVC => 数据是保存在模型中
// 1. 删除self.dataList中indexPath对应的数据
[self.dataList removeObjectAtIndex:indexPath.row];
NSLog(@"%@", self.dataList);
// 2. 刷新表格(重新加载数据)
// 重新加载所有数据
// [self.tableView reloadData];
// deleteRowsAtIndexPaths让表格控件动画删除指定的行
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
NSLog(@"要添加数据");
// 1. 向数组添加数据
[self.dataList insertObject:@"王小二" atIndex:indexPath.row + 1];
// 2. 刷新表格
// [self.tableView reloadData];
// insertRowsAtIndexPaths让表格控件动画在指定indexPath添加指定行
// 新建一个indexPath
NSIndexPath *path = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationMiddle];
}
}
// 只要实现此方法,就可以显示拖动控件
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
// 界面数据UITableView已经完成了
// 调整数据即可
// [self.dataList exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
// 1. 将源从数组中取出
id source = self.dataList[sourceIndexPath.row];
// 2. 将源从数组中删除
[self.dataList removeObjectAtIndex:sourceIndexPath.row];
NSLog(@"%@", self.dataList);
// 3. 将源插入到数组中的目标位置
[self.dataList insertObject:source atIndex:destinationIndexPath.row];
NSLog(@"%@", self.dataList);
}
#pragma mark - 代理方法
// 返回编辑样式,如果没有实现此方法,默认都是删除
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// if (indexPath.row % 2) {
// return UITableViewCellEditingStyleInsert;
// } else {
// return UITableViewCellEditingStyleDelete;
// }
return UITableViewCellEditingStyleInsert;
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tgs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 可重用标示符
static NSString *ID = @"Cell";
// 2. tableView查询可重用Cell
HMTgCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3. 如果没有可重用cell
if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
// 从XIB加载自定义视图
cell = [[[NSBundle mainBundle] loadNibNamed:@"HMTgCell" owner:nil options:nil] lastObject];
}
// 4. 设置Cell内容
// 1> 取出模型
HMTg *tg = self.tgs[indexPath.row];
// cell.textLabel.text = tg.title;
// cell.imageView.image = [UIImage imageNamed:tg.icon];
//
// cell.detailTextLabel.text = [NSString stringWithFormat:@"¥%@ 已有%@人购买", tg.price, tg.buyCount];
cell.titleLabel.text = tg.title;
cell.iconView.image = [UIImage imageNamed:tg.icon];
cell.priceLabel.text = tg.price;
cell.buyCountLabel.text = tg.buyCount;
return cell;
}
- (void)setStatus:(HMStatus *)status
{
_status = status;
// 0. 定义间距
CGFloat padding = 10;
// 1. 头像
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
self.iconF = CGRectMake(iconX, iconY, iconW, iconH);
// 2. 姓名大小由文字的长度来决定
// boundingRectWithSize计算给定文本字符串所占的区域
// 返回值是一个x,y = 0的CGRect,w,h是计算好的宽高
//
// 如果要计算多行的准确高度,需要传入NSStringDrawingUsesLineFragmentOrigin选项
// dict用于指定字体的相关属性的字典,UIKit框架中的第一个头文件
// context: nil
NSDictionary *nameDict = @{NSFontAttributeName: kNameFont};
CGRect nameFrame = [self.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nameDict context:nil];
nameFrame.origin.x = CGRectGetMaxX(self.iconF) + padding;
nameFrame.origin.y = padding + (self.iconF.size.height - nameFrame.size.height) * 0.5;
self.nameF = nameFrame;
// vip图标
CGFloat vipX = CGRectGetMaxX(self.nameF) + padding;
CGFloat vipY = self.nameF.origin.y;
CGFloat vipW = 14;
CGFloat vipH = 14;
self.vipF = CGRectMake(vipX, vipY, vipW, vipH);
// 正文
NSDictionary *textDict = @{NSFontAttributeName: kTextFont};
CGRect textFrame = [self.status.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:textDict context:nil];
textFrame.origin.x = padding;
textFrame.origin.y = CGRectGetMaxY(self.iconF) + padding;
self.textF = textFrame;
if (self.status.picture.length > 0) {
// 配图
CGFloat pictureX = padding;
CGFloat pictureY = CGRectGetMaxY(textFrame) + padding;
CGFloat pictureW = 100;
CGFloat pictureH = 100;
self.pictureF = CGRectMake(pictureX, pictureY, pictureW, pictureH);
self.cellHeight = CGRectGetMaxY(self.pictureF) + padding;
} else {
self.cellHeight = CGRectGetMaxY(self.textF) + padding;
}
}
+ (NSArray *)statusFrames
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"statuses.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
// 要添加statusFrame对象
HMStatusFrame *statusFrame = [[HMStatusFrame alloc] init];
// 实例化一个新的Status模型
HMStatus *status = [HMStatus statusWithDict:dict];
// 调用自己的setter方法,保存status数据模型,同时计算出所有控件的位置
statusFrame.status = status;
// 将statusFrame添加到数组
[arrayM addObject:statusFrame];
}
return arrayM;
}
- (UIImageView *)iconView
{
if (_iconView == nil) {
_iconView = [[UIImageView alloc] init];
[self.contentView addSubview:_iconView];
}
return _iconView;
}
- (UILabel *)nameView
{
if (_nameView == nil) {
_nameView = [[UILabel alloc] init];
// 默认字体是17号
_nameView.font = kNameFont;
[self.contentView addSubview:_nameView];
}
return _nameView;
}
- (UIImageView *)vipView
{
if (_vipView == nil) {
_vipView = [[UIImageView alloc] init];
_vipView.image = [UIImage imageNamed:@"vip"];
_vipView.hidden = YES;
[self.contentView addSubview:_vipView];
}
return _vipView;
}
- (UILabel *)textView
{
if (_textView == nil) {
_textView = [[UILabel alloc] init];
_textView.font = kTextFont;
_textView.numberOfLines = 0;
[self.contentView addSubview:_textView];
}
return _textView;
}
- (UIImageView *)pictureView
{
if (_pictureView == nil) {
_pictureView = [[UIImageView alloc] init];
[self.contentView addSubview:_pictureView];
}
return _pictureView;
}
- (void)setStatus:(HMStatus *)status
{
_status = status;
// 1> 设置数据
[self settingData];
// 2> 设置位置
[self settingFrame];
}
/** 设置数据 */
- (void)settingData
{
// 头像
self.iconView.image = [UIImage imageNamed:self.status.icon];
// 姓名
self.nameView.text = self.status.name;
// vip(可选的)
if (self.status.vip) {
self.vipView.hidden = NO;
self.nameView.textColor = [UIColor redColor];
} else {
self.vipView.hidden = YES;
self.nameView.textColor = [UIColor blackColor];
}
// 正文
self.textView.text = self.status.text;
// 配图(可选参数)
// imageNamed:nil CUICatalog: Invalid asset name supplied: (null), or invalid scale factor: 2.000000
if (self.status.picture.length > 0) {
self.pictureView.hidden = NO;
self.pictureView.image = [UIImage imageNamed:self.status.picture];
} else {
self.pictureView.hidden = YES;
}
}
/** 设置位置 */
- (void)settingFrame
{
// 0. 定义间距
CGFloat padding = 10;
// 1. 头像
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH);
// 2. 姓名大小由文字的长度来决定
// boundingRectWithSize计算给定文本字符串所占的区域
// 返回值是一个x,y = 0的CGRect,w,h是计算好的宽高
//
// 如果要计算多行的准确高度,需要传入NSStringDrawingUsesLineFragmentOrigin选项
// dict用于指定字体的相关属性的字典,UIKit框架中的第一个头文件
// context: nil
NSDictionary *nameDict = @{NSFontAttributeName: kNameFont};
CGRect nameFrame = [self.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nameDict context:nil];
nameFrame.origin.x = CGRectGetMaxX(self.iconView.frame) + padding;
nameFrame.origin.y = padding + (self.iconView.bounds.size.height - nameFrame.size.height) * 0.5;
self.nameView.frame = nameFrame;
// vip图标
CGFloat vipX = CGRectGetMaxX(self.nameView.frame) + padding;
CGFloat vipY = self.nameView.frame.origin.y;
CGFloat vipW = 14;
CGFloat vipH = 14;
self.vipView.frame = CGRectMake(vipX, vipY, vipW, vipH);
// 正文
NSDictionary *textDict = @{NSFontAttributeName: kTextFont};
CGRect textFrame = [self.status.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:textDict context:nil];
textFrame.origin.x = padding;
textFrame.origin.y = CGRectGetMaxY(self.iconView.frame) + padding;
self.textView.frame = textFrame;
CGFloat cellHeight;
if (self.status.picture.length > 0) {
// 配图
CGFloat pictureX = padding;
CGFloat pictureY = CGRectGetMaxY(textFrame) + padding;
CGFloat pictureW = 100;
CGFloat pictureH = 100;
self.pictureView.frame = CGRectMake(pictureX, pictureY, pictureW, pictureH);
cellHeight = CGRectGetMaxY(self.pictureView.frame) + padding;
} else {
cellHeight = CGRectGetMaxY(self.textView.frame) + padding;
}
#pragma mark - 代理方法
/** 计算单元格行高 */
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
/**
计算行高的方法,会在加载表格数据时,有多少行计算多少次 contentSize
问题:此方法执行的时候,cell还没有被实例化!
但是:行高计算是在实例化cell时,通过设置status属性,计算的=>有了status模型,就可以知道行高!
问题:如何在cell实例化之前,获得行高?
解决方法:通过status可以计算得到行高!=》再建立一个模型,专门计算所有控件的位置
*/
HMStatusFrame *statusFrame = self.statusFrames[indexPath.row];
return statusFrame.cellHeight;
}
// 如果没有实现,默认是1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
// 每个分组中的数据总数
// sction:分组的编号
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
// 第0个分组
return 5;
} else {
return 18;
}
}
// 告诉表格控件,每一行cell单元格的细节
// indexPath
// @property(nonatomic,readonly) NSInteger section; 分组
// @property(nonatomic,readonly) NSInteger row; 行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 实例化TableViewCell时,使用initWithStyle方法来进行实例化
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.textLabel.text = [NSString stringWithFormat:@"XXXX %02d X - %04d", indexPath.section, indexPath.row];
return cell;
}
// 返回分组的标题文字
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:@"XXX %02d X", section];
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
if (section == 0) {
return @"太牛叉了";
} else {
return @"牛叉闪闪亮";
}
}
必备代码:
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)heroWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+ (NSArray *)heros
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"heros.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrayM addObject:[self heroWithDict:dict]];
}
return arrayM;
}
/**
UITableViewCellStyleDefault, 默认类型 标题+可选图像
UITableViewCellStyleValue1, 标题+明细+图像
UITableViewCellStyleValue2, 不显示图像,标题+明细
UITableViewCellStyleSubtitle 标题+明细+图像
*/
// 告诉表格每个单元格的明细信息
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"表格行明细 %d", indexPath.row);
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
// 取出英雄对象
HMHero *hero = self.heros[indexPath.row];
// 标题
cell.textLabel.text = hero.name;
// 明细信息
cell.detailTextLabel.text = hero.intro;
// 图像
cell.imageView.image = [UIImage imageNamed:hero.icon];
// 设置右边的箭头
// 1> UITableViewCellAccessoryDisclosureIndicator 箭头,可以"提示"用户,当前行是可以点击的,通常选中行,会跳到新的页面
// 2> UITableViewCellAccessoryCheckmark 对号,通常提示用户该行数据设置完毕,使用的比较少
// 3> UITableViewCellAccessoryDetailButton 按钮,通常点击按钮可以做独立的操作,例如alertView
// 点击按钮并不会选中行
// 4> UITableViewCellAccessoryDetailDisclosureButton 按钮+箭头,各自操作
// cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
// 指定右侧的自定义视图
/**
通常accessoryType提供的类型不能满足时,才会使用自定义控件
但是需要自行添加监听方法,通常用在自定义cell,不要写在视图控制器中!!!
自定义控件的事件触发,同样不会影响表格行的选中!
*/
UISwitch *switcher = [[UISwitch alloc] init];
// 添加监听方法
[switcher addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
cell.accessoryView = switcher;
return cell;
}
/**
代理方法的优先级比rowHeight优先级高
应用场景:很多应用程序,每一行的高度是不一样的,例如:新浪微博
表格工作观察的小结
1> 要知道总共有多少数据
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
2> 计算“每一行”的行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
问题:在此方法执行时,cell被实例化了吗?
方法的作用是什么?
scrollView需要指定contentSize才能够滚动,如果没有实现方法,行高默认是44
需要知道每一行的高度,才能够准确的计算出contentSize
知道每一行的高度后,自然知道每一个屏幕应该显示多少行,表格明细方法的执行次数就知道了
3> 表格明细
调用屏幕显示所需要的行数,懒加载,只有要显示的表格行,才会被实例化!
小的结论:
* tableView.rowHeight: 效率高,适用于所有的表格行高度一致
* 代理方法指定行高: 效率差,适合于每一个行的行高不一样,能够让表格更加的灵活
*/
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self) {
// [self setValuesForKeysWithDictionary:dict];
[self setValue:dict[@"title"] forKey:@"title"];
// dict[@"cars"]存放的是字典的数组
// 希望将字典的数组转换成HMCar模型的数组
// [self setValue:dict[@"cars"] forKey:@"cars"];
self.cars = [HMCar carsWithArray:dict[@"cars"]];
}
return self;
}
+ (instancetype)carGroupWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+ (NSArray *)carGroups
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cars_total.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrayM addObject:[self carGroupWithDict:dict]];
}
return arrayM;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p> {title: %@, cars: %@}", self.class, self, self.title, self.cars];
}
// 单元格
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 可重用标示符
static NSString *ID = @"Cell";
// 让表格缓冲区查找可重用cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 如果没有找到可重用cell
if (cell == nil) {
// 实例化cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
// 设置cell内容
// 1> 取出数据模型
HMCarGroup *group = self.carGroups[indexPath.section];
HMCar *car = group.cars[indexPath.row];
// 2> 设置数据
cell.imageView.image = [UIImage imageNamed:car.icon];
cell.textLabel.text = car.name;
return cell;
}
// 右侧索引列表
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
// 索引数组中的"内容",跟分组无关
// 索引数组中的下标,对应的是分组的下标
// return @[@"哇哈哈", @"hello", @"哇哈哈", @"hello", @"哇哈哈", @"hello", @"哇哈哈", @"hello"];
// 返回self.carGroup中title的数组
// NSMutableArray *arrayM = [NSMutableArray array];
// for (HMCarGroup *group in self.carGroups) {
// [arrayM addObject:group.title];
// }
// return arrayM;
// KVC是cocoa的大招
// 用来间接获取或者修改对象属性的方式
// 使用KVC在获取数值时,如果指定对象不包含keyPath的"键名",会自动进入对象的内部查找
// 如果取值的对象是一个数组,同样返回一个数组
NSArray *array = [self.carGroups valueForKeyPath:@"cars.name"];
NSLog(@"%@", array);
return [self.carGroups valueForKeyPath:@"title"];
}
// 只要实现了此方法,就能够支持手势拖拽删除了,删除需要自己干!
/**
UITableViewCellEditingStyleNone,
UITableViewCellEditingStyleDelete, 删除
UITableViewCellEditingStyleInsert 添加
*/
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSLog(@"要删除");
// MVC => 数据是保存在模型中
// 1. 删除self.dataList中indexPath对应的数据
[self.dataList removeObjectAtIndex:indexPath.row];
NSLog(@"%@", self.dataList);
// 2. 刷新表格(重新加载数据)
// 重新加载所有数据
// [self.tableView reloadData];
// deleteRowsAtIndexPaths让表格控件动画删除指定的行
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
NSLog(@"要添加数据");
// 1. 向数组添加数据
[self.dataList insertObject:@"王小二" atIndex:indexPath.row + 1];
// 2. 刷新表格
// [self.tableView reloadData];
// insertRowsAtIndexPaths让表格控件动画在指定indexPath添加指定行
// 新建一个indexPath
NSIndexPath *path = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationMiddle];
}
}
// 只要实现此方法,就可以显示拖动控件
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
// 界面数据UITableView已经完成了
// 调整数据即可
// [self.dataList exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
// 1. 将源从数组中取出
id source = self.dataList[sourceIndexPath.row];
// 2. 将源从数组中删除
[self.dataList removeObjectAtIndex:sourceIndexPath.row];
NSLog(@"%@", self.dataList);
// 3. 将源插入到数组中的目标位置
[self.dataList insertObject:source atIndex:destinationIndexPath.row];
NSLog(@"%@", self.dataList);
}
#pragma mark - 代理方法
// 返回编辑样式,如果没有实现此方法,默认都是删除
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// if (indexPath.row % 2) {
// return UITableViewCellEditingStyleInsert;
// } else {
// return UITableViewCellEditingStyleDelete;
// }
return UITableViewCellEditingStyleInsert;
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tgs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 可重用标示符
static NSString *ID = @"Cell";
// 2. tableView查询可重用Cell
HMTgCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3. 如果没有可重用cell
if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
// 从XIB加载自定义视图
cell = [[[NSBundle mainBundle] loadNibNamed:@"HMTgCell" owner:nil options:nil] lastObject];
}
// 4. 设置Cell内容
// 1> 取出模型
HMTg *tg = self.tgs[indexPath.row];
// cell.textLabel.text = tg.title;
// cell.imageView.image = [UIImage imageNamed:tg.icon];
//
// cell.detailTextLabel.text = [NSString stringWithFormat:@"¥%@ 已有%@人购买", tg.price, tg.buyCount];
cell.titleLabel.text = tg.title;
cell.iconView.image = [UIImage imageNamed:tg.icon];
cell.priceLabel.text = tg.price;
cell.buyCountLabel.text = tg.buyCount;
return cell;
}
- (void)setStatus:(HMStatus *)status
{
_status = status;
// 0. 定义间距
CGFloat padding = 10;
// 1. 头像
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
self.iconF = CGRectMake(iconX, iconY, iconW, iconH);
// 2. 姓名大小由文字的长度来决定
// boundingRectWithSize计算给定文本字符串所占的区域
// 返回值是一个x,y = 0的CGRect,w,h是计算好的宽高
//
// 如果要计算多行的准确高度,需要传入NSStringDrawingUsesLineFragmentOrigin选项
// dict用于指定字体的相关属性的字典,UIKit框架中的第一个头文件
// context: nil
NSDictionary *nameDict = @{NSFontAttributeName: kNameFont};
CGRect nameFrame = [self.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nameDict context:nil];
nameFrame.origin.x = CGRectGetMaxX(self.iconF) + padding;
nameFrame.origin.y = padding + (self.iconF.size.height - nameFrame.size.height) * 0.5;
self.nameF = nameFrame;
// vip图标
CGFloat vipX = CGRectGetMaxX(self.nameF) + padding;
CGFloat vipY = self.nameF.origin.y;
CGFloat vipW = 14;
CGFloat vipH = 14;
self.vipF = CGRectMake(vipX, vipY, vipW, vipH);
// 正文
NSDictionary *textDict = @{NSFontAttributeName: kTextFont};
CGRect textFrame = [self.status.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:textDict context:nil];
textFrame.origin.x = padding;
textFrame.origin.y = CGRectGetMaxY(self.iconF) + padding;
self.textF = textFrame;
if (self.status.picture.length > 0) {
// 配图
CGFloat pictureX = padding;
CGFloat pictureY = CGRectGetMaxY(textFrame) + padding;
CGFloat pictureW = 100;
CGFloat pictureH = 100;
self.pictureF = CGRectMake(pictureX, pictureY, pictureW, pictureH);
self.cellHeight = CGRectGetMaxY(self.pictureF) + padding;
} else {
self.cellHeight = CGRectGetMaxY(self.textF) + padding;
}
}
+ (NSArray *)statusFrames
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"statuses.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
// 要添加statusFrame对象
HMStatusFrame *statusFrame = [[HMStatusFrame alloc] init];
// 实例化一个新的Status模型
HMStatus *status = [HMStatus statusWithDict:dict];
// 调用自己的setter方法,保存status数据模型,同时计算出所有控件的位置
statusFrame.status = status;
// 将statusFrame添加到数组
[arrayM addObject:statusFrame];
}
return arrayM;
}
- (UIImageView *)iconView
{
if (_iconView == nil) {
_iconView = [[UIImageView alloc] init];
[self.contentView addSubview:_iconView];
}
return _iconView;
}
- (UILabel *)nameView
{
if (_nameView == nil) {
_nameView = [[UILabel alloc] init];
// 默认字体是17号
_nameView.font = kNameFont;
[self.contentView addSubview:_nameView];
}
return _nameView;
}
- (UIImageView *)vipView
{
if (_vipView == nil) {
_vipView = [[UIImageView alloc] init];
_vipView.image = [UIImage imageNamed:@"vip"];
_vipView.hidden = YES;
[self.contentView addSubview:_vipView];
}
return _vipView;
}
- (UILabel *)textView
{
if (_textView == nil) {
_textView = [[UILabel alloc] init];
_textView.font = kTextFont;
_textView.numberOfLines = 0;
[self.contentView addSubview:_textView];
}
return _textView;
}
- (UIImageView *)pictureView
{
if (_pictureView == nil) {
_pictureView = [[UIImageView alloc] init];
[self.contentView addSubview:_pictureView];
}
return _pictureView;
}
- (void)setStatus:(HMStatus *)status
{
_status = status;
// 1> 设置数据
[self settingData];
// 2> 设置位置
[self settingFrame];
}
/** 设置数据 */
- (void)settingData
{
// 头像
self.iconView.image = [UIImage imageNamed:self.status.icon];
// 姓名
self.nameView.text = self.status.name;
// vip(可选的)
if (self.status.vip) {
self.vipView.hidden = NO;
self.nameView.textColor = [UIColor redColor];
} else {
self.vipView.hidden = YES;
self.nameView.textColor = [UIColor blackColor];
}
// 正文
self.textView.text = self.status.text;
// 配图(可选参数)
// imageNamed:nil CUICatalog: Invalid asset name supplied: (null), or invalid scale factor: 2.000000
if (self.status.picture.length > 0) {
self.pictureView.hidden = NO;
self.pictureView.image = [UIImage imageNamed:self.status.picture];
} else {
self.pictureView.hidden = YES;
}
}
/** 设置位置 */
- (void)settingFrame
{
// 0. 定义间距
CGFloat padding = 10;
// 1. 头像
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH);
// 2. 姓名大小由文字的长度来决定
// boundingRectWithSize计算给定文本字符串所占的区域
// 返回值是一个x,y = 0的CGRect,w,h是计算好的宽高
//
// 如果要计算多行的准确高度,需要传入NSStringDrawingUsesLineFragmentOrigin选项
// dict用于指定字体的相关属性的字典,UIKit框架中的第一个头文件
// context: nil
NSDictionary *nameDict = @{NSFontAttributeName: kNameFont};
CGRect nameFrame = [self.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nameDict context:nil];
nameFrame.origin.x = CGRectGetMaxX(self.iconView.frame) + padding;
nameFrame.origin.y = padding + (self.iconView.bounds.size.height - nameFrame.size.height) * 0.5;
self.nameView.frame = nameFrame;
// vip图标
CGFloat vipX = CGRectGetMaxX(self.nameView.frame) + padding;
CGFloat vipY = self.nameView.frame.origin.y;
CGFloat vipW = 14;
CGFloat vipH = 14;
self.vipView.frame = CGRectMake(vipX, vipY, vipW, vipH);
// 正文
NSDictionary *textDict = @{NSFontAttributeName: kTextFont};
CGRect textFrame = [self.status.text boundingRectWithSize:CGSizeMake(300, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:textDict context:nil];
textFrame.origin.x = padding;
textFrame.origin.y = CGRectGetMaxY(self.iconView.frame) + padding;
self.textView.frame = textFrame;
CGFloat cellHeight;
if (self.status.picture.length > 0) {
// 配图
CGFloat pictureX = padding;
CGFloat pictureY = CGRectGetMaxY(textFrame) + padding;
CGFloat pictureW = 100;
CGFloat pictureH = 100;
self.pictureView.frame = CGRectMake(pictureX, pictureY, pictureW, pictureH);
cellHeight = CGRectGetMaxY(self.pictureView.frame) + padding;
} else {
cellHeight = CGRectGetMaxY(self.textView.frame) + padding;
}
#pragma mark - 代理方法
/** 计算单元格行高 */
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
/**
计算行高的方法,会在加载表格数据时,有多少行计算多少次 contentSize
问题:此方法执行的时候,cell还没有被实例化!
但是:行高计算是在实例化cell时,通过设置status属性,计算的=>有了status模型,就可以知道行高!
问题:如何在cell实例化之前,获得行高?
解决方法:通过status可以计算得到行高!=》再建立一个模型,专门计算所有控件的位置
*/
HMStatusFrame *statusFrame = self.statusFrames[indexPath.row];
return statusFrame.cellHeight;
}