局部刷新
UI的刷新可以使用UITableView的reloadData方法,该方法会重新调用数据源方法,包括计算分组、计算每个分组的行数,生成单元格等刷新整个UITableView。当然这种方式在实际开发中是不可取的,我们不可能因为修改了一个人的信息就刷新整个UITableViewView,此时我们需要采用局部刷新。局部刷新使用起来很简单,只需要调用UITableView的另外一个方法:reloadRowsAtIndexPaths
#pragma mark 窗口的代理方法,用户保存数据
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
//当点击了第二个按钮(OK)
if (buttonIndex==1) {
UITextField *textField= [alertView textFieldAtIndex:0];
//修改模型数据
KCContactGroup *group=_contacts[_selectedIndexPath.section];
KCContact *contact=group.contacts[_selectedIndexPath.row];
contact.phoneNumber=textField.text;
//刷新表格
NSArray *indexPaths=@[_selectedIndexPath];//需要局部刷新的单元格的组、行
[_tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];//后面的参数代表更新时的动画
}
}
这里简单总结一些UITableView的刷新方法:
- (void)reloadData;刷新整个表格。
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分组和行。
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分组。
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;删除时刷新指定的行数据。
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;添加时刷新指定的行数据。
组标题索引
大家在使用iPhone通讯录时会发现右侧可以按字母检索,使用起来很方便,其实这个功能使用UITableView实现很简单,只要实现数据源协议的一个方法,构建一个分组标题的数组即可实现。数组元素的内容和组标题内容未必完全一致,UITableView是按照数组元素的索引和每组数据索引顺序来定位的而不是按内容查找。
#pragma mark 返回每组标题索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
NSLog(@"生成组索引");
NSMutableArray *indexs=[[NSMutableArray alloc]init];
for(KCContactGroup *group in _contacts){
[indexs addObject:group.name];
}
return indexs;
}
效果如下:
单元格cell重用
UITableView中的单元格cell是在显示到用户可视区域后创建的,那么如果用户往下滚动就会继续创建显示在屏幕上的单元格,如果用户向上滚动返回到查看过的内容时同样会重新创建之前已经创建过的单元格。如此一来即使UITableView的内容不是太多,如果用户反复的上下滚动,内存也会瞬间飙升,更何况很多时候UITableView的内容是很多的(例如微博展示列表,基本向下滚动是没有底限的)。
我们曾经提到过如何优化UIScrollView,当时就是利用有限的UIImageView动态切换其内容来尽可能减少资源占用。同样的,在UITableView中也可以采用类似的方式,只是这时我们不是在滚动到指定位置后更改滚动的位置而是要将当前没有显示的Cell重新显示在将要显示的Cell的位置然后更新其内容。原因就是UITableView中的Cell结构布局可能是不同的,通过重新定位是不可取的,而是需要重用已经不再界面显示的已创建过的Cell。
当然,听起来这么做比较复杂,其实实现起来很简单,因为UITableView已经为我们实现了这种机制。在UITableView内部有一个缓存池,初始化时使用initWithStyle:(UITableViewCellStyle) reuseIdentifier:(NSString *)方法指定一个可重用标识,就可以将这个cell放到缓存池。然后在使用时使用指定的标识去缓存池中取得对应的cell然后修改cell内容即可。
#pragma mark返回每行的单元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//NSIndexPath是一个对象,记录了组和行信息
NSLog(@"生成单元格(组:%i,行%i)",indexPath.section,indexPath.row);
KCContactGroup *group=_contacts[indexPath.section];
KCContact *contact=group.contacts[indexPath.row];
//由于此方法调用十分频繁,cell的标示声明成静态变量有利于性能优化
static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
//首先根据标识去缓存池取
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//如果缓存池没有到则重新创建并放到缓存池中
if(!cell){
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
cell.textLabel.text=[contact getName];
cell.detailTextLabel.text=contact.phoneNumber;
NSLog(@"cell:%@",cell);
return cell;
}
不同的方法处理机制
UIView使用delegate, UIControl使用target-action形式。比如UISwitch继承于UIControl而不是UIView(当然UIControl最终也是继承于UIView),继承于UIControl的控件使用addTarget添加对应事件而不是代理;UITableView继承于UIView,就是用delegate。
UITableViewCell的自定义
虽然系统自带的UITableViewCell已经够强大了,但是很多时候这并不能满足我们的需求,所以需要自定义,可以定义成自己需要的任意形式,例如新浪微博的Cell
UITableViewCell高度设置
Cell的高度需要重新设置(前面说过无论Cell内部设置多高都没有用,需要重新设置),这里采用的方法是首先创建对应的Cell,然后在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;方法中设置微博数据计算高度通知UITableView
#pragma mark 重新设置单元格高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//KCStatusTableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
KCStatusTableViewCell *cell= _statusCells[indexPath.row];
cell.status=_status[indexPath.row];
return cell.height;
}