tableView详解

由于iOS是遵循MVC模式设计的,很多操作都是通过代理和外界沟通的,但对于数据源控件除了代理还有一个数据源属性,通过它和外界进行数据交互。 对于UITableView设置完dataSource后需要实现UITableViewDataSource协议,在这个协议中定义了多种 数据操作方法,下面通过创建一个简单的联系人管理进行演示:

首先我们需要创建一个联系人模型KCContact

KCContact.h 

 
#import <Foundation/Foundation.h>

@interface KCContact : NSObject

#pragma mark 姓
@property (nonatomic,copy) NSString *firstName;
#pragma mark 名
@property (nonatomic,copy) NSString *lastName;
#pragma mark 手机号码
@property (nonatomic,copy) NSString *phoneNumber;

#pragma mark 带参数的构造函数
-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;

#pragma mark 取得姓名
-(NSString *)getName;


#pragma mark 带参数的静态对象初始化方法
+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;
@end


KCContact.m

#import "KCContact.h"

@implementation KCContact

-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
    if(self=[super init]){
      self.firstName=firstName;
      self.lastName=lastName;
      self.phoneNumber=phoneNumber;
    }
    return self;
}

-(NSString *)getName{
      return [NSString stringWithFormat:@"%@ %@",_lastName,_firstName];
}

+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
     KCContact *contact1=[[KCContact alloc]initWithFirstName:firstName andLastName:lastName andPhoneNumber:phoneNumber];
     return contact1;
}

@end


为了演示分组显示我们不妨将一组数据也抽象成模型KCContactGroup

KCContactGroup.h

#import <Foundation/Foundation.h>
#import "KCContact.h"

@interface KCContactGroup : NSObject

#pragma mark 组名
@property (nonatomic,copy) NSString *name;

#pragma mark 分组描述
@property (nonatomic,copy) NSString *detail;

#pragma mark 联系人
@property (nonatomic,strong) NSMutableArray *contacts;

#pragma mark 带参数个构造函数
-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

#pragma mark 静态初始化方法
+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

@end


KCContactGroup.m

#import "KCContactGroup.h"

@implementation KCContactGroup


-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
if (self=[super init]) {
self.name=name;
self.detail=detail;
self.contacts=contacts;
}
return self;
}

+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
KCContactGroup *group1=[[KCContactGroup alloc]initWithName:name andDetail:detail andContacts:contacts];
return group1;
}

@end


然后在viewDidLoad方法中创建一些模拟数据同时实现数据源协议方法:

KCMainViewController.m

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource>{
UITableView *_tableView;
NSMutableArray *_contacts;//联系人模型
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
[super viewDidLoad];

//初始化数据
[self initData];

//创建一个分组样式的UITableView
_tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];

//设置数据源,注意必须实现对应的UITableViewDataSource协议
_tableView.dataSource=self;

[self.view addSubview:_tableView];
}

#pragma mark 加载数据
-(void)initData{

_contacts=[[NSMutableArray alloc]init];


KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];

KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];

KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];

[_contacts addObject:group1];




KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];

KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];

KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];

KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];

[_contacts addObject:group2];



KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];

KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];


KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];

[_contacts addObject:group3];



KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];

KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];

KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];

KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];

KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];

KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];

[_contacts addObject:group4];



KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];

KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];

KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];

KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];

[_contacts addObject:group5];


}

#pragma mark - 数据源方法
#pragma mark 返回分组数
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

NSLog(@"计算分组数");

return _contacts.count;

}

#pragma mark 返回每组行数
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

NSLog(@"计算每组(组%i)行数",section);

KCContactGroup *group1=_contacts[section];

return group1.contacts.count;

}

#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];

UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];

cell.textLabel.text=[contact getName];

cell.detailTextLabel.text=contact.phoneNumber;

return cell;

}

#pragma mark 返回每组头标题名称
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{

NSLog(@"生成组(组%i)名称",section);

KCContactGroup *group=_contacts[section];

return group.name;

}

#pragma mark 返回每组尾部说明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{

NSLog(@"生成尾部(组%i)详情",section);

KCContactGroup *group=_contacts[section];

return group.detail;

}
@end


运行可以看到如下效果:



大家在使用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;

}


效果如下:



需要注意的是上面几个重点方法的执行顺序,请看下图:

值得指出的是生成单元格的方法并不是一次全部调用,而是只会生产当前显示在界面上的单元格,当用户滚动操作时再显示其他单元格。

代理

上面我们已经看到通讯录的简单实现,但是我们发现单元格高度、分组标题高度以及尾部说明的高度都需要调整,此时就需要使用代理方法。UITableView代理方法有很多,例如监听单元格显示周期、监听单元格选择编辑操作、设置是否高亮显示单元格、设置行高等。

#pragma mark - 代理方法
#pragma mark 设置分组标题内容高度

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

if(section==0){

return 50;

}

return 40;

}

#pragma mark 设置每行高度(每行高度可以不一样)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

return 45;

}

#pragma mark 设置尾部说明内容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{

return 40;

}

2.监听点击

在iOS中点击某联系个人就可以呼叫这个联系人,这时就需要监听点击操作,这里就不演示呼叫联系人操作了,我们演示一下修改人员信息的操作。

KCMainViewContrller.m


#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
UITableView *_tableView;
NSMutableArray *_contacts;//联系人模型
NSIndexPath *_selectedIndexPath;//当前选中的组和行
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
[super viewDidLoad];

//初始化数据
[self initData];

//创建一个分组样式的UITableView
_tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];

//设置数据源,注意必须实现对应的UITableViewDataSource协议
_tableView.dataSource=self;
//设置代理
_tableView.delegate=self;

[self.view addSubview:_tableView];
}

#pragma mark 加载数据
-(void)initData{

_contacts=[[NSMutableArray alloc]init];


KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];

KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];

KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];

[_contacts addObject:group1];




KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];

KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];

KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];

KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];

[_contacts addObject:group2];




KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];

KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];


KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];

[_contacts addObject:group3];



KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];

KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];

KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];

KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];

KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];

KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];

[_contacts addObject:group4];



KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];

KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];

KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];

KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];

[_contacts addObject:group5];


}

#pragma mark - 数据源方法
#pragma mark 返回分组数
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

NSLog(@"计算分组数");

return _contacts.count;

}

#pragma mark 返回每组行数
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

NSLog(@"计算每组(组%i)行数",section);

KCContactGroup *group1=_contacts[section];

return group1.contacts.count;

}

#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];

UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];

cell.textLabel.text=[contact getName];

cell.detailTextLabel.text=contact.phoneNumber;

return cell;

}

#pragma mark 返回每组头标题名称
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{

NSLog(@"生成组(组%i)名称",section);

KCContactGroup *group=_contacts[section];

return group.name;

}

#pragma mark 返回每组尾部说明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{

NSLog(@"生成尾部(组%i)详情",section);

KCContactGroup *group=_contacts[section];

return group.detail;

}

#pragma mark 返回每组标题索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{

NSLog(@"生成组索引");

NSMutableArray *indexs=[[NSMutableArray alloc]init];

for(KCContactGroup *group in _contacts){

[indexs addObject:group.name];

}

return indexs;

}

#pragma mark - 代理方法
#pragma mark 设置分组标题内容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

if(section==0){

return 50;

}

return 40;

}

#pragma mark 设置每行高度(每行高度可以不一样)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

return 45;

}


#pragma mark 设置尾部说明内容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
     return 40;
}

#pragma mark 点击行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

_selectedIndexPath=indexPath;

KCContactGroup *group=_contacts[indexPath.section];

KCContact *contact=group.contacts[indexPath.row];

//创建弹出窗口

UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];

alert.alertViewStyle=UIAlertViewStylePlainTextInput; //设置窗口内容样式

UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框

textField.text=contact.phoneNumber; //设置文本框内容

[alert show]; //显示窗口

}

#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;

//刷新表格

[_tableView reloadData];

}

}

#pragma mark 重写状态样式方法
-(UIStatusBarStyle)preferredStatusBarStyle{

return UIStatusBarStyleLightContent;

}
@end


在上面的代码中我们通过修改模型来改变UI显示,这种方式是经典的MVC应用,在后面的代码中会经常看到。当然UI的刷新使用了UITableView的reloadData方法,该方法会重新调用数据源方法,包括计算分组、计算每个分组的行数,生成单元格等刷新整个UITableView。当然这种方式在实际开发中是不可取的,我们不可能因为修改了一个人的信息就刷新整个UITableViewView,此时我们需要采用局部刷新。局部刷新使用起来很简单,只需要调用UITableView的另外一个方法:


#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中的单元格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;

}


上面的代码中已经打印了cell的地址,如果大家运行测试上下滚动UITableView会发现滚动时创建的cell地址是初始化时已经创建的。

这里再次给大家强调两点:

  1. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)方法调用很频繁,无论是初始化、上下滚动、刷新都会调用此方法,所有在这里执行的操作一定要注意性能; 
  2. 可重用标识可以有多个,如果在UITableView中有多类结构不同的Cell,可以通过这个标识进行缓存和重新;
很明显iOS设置中第一个accessoryType不在枚举之列,右侧的访问器类型是UISwitch控件,那么如何显示自定义的访问器呢?其实只要设置UITableViewCell的accessoryView即可,它支持任何UIView控件。假设我们在通讯录每组第一行放一个UISwitch,同时切换时可以输出对应信息:

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{

UITableView *_tableView;

NSMutableArray *_contacts;//联系人模型

NSIndexPath *_selectedIndexPath;//当前选中的组和行

}


@end


KCMainViewController.m
@implementation KCMainViewController

- (void)viewDidLoad {
[super viewDidLoad];

//初始化数据
[self initData];

//创建一个分组样式的UITableView
_tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];

//设置数据源,注意必须实现对应的UITableViewDataSource协议
_tableView.dataSource=self;
//设置代理
_tableView.delegate=self;

[self.view addSubview:_tableView];
}

#pragma mark 加载数据
-(void)initData{

_contacts=[[NSMutableArray alloc]init];


KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];

KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];

KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];

[_contacts addObject:group1];




KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];

KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];

KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];

KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];

[_contacts addObject:group2];




KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];

KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];


KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];

[_contacts addObject:group3];



KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];

KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];

KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];

KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];

KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];

KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];

[_contacts addObject:group4];



KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];

KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];

KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];

KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];

[_contacts addObject:group5];


}

#pragma mark - 数据源方法
#pragma mark 返回分组数
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

NSLog(@"计算分组数");

return _contacts.count;

}

#pragma mark 返回每组行数
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

NSLog(@"计算每组(组%i)行数",section);

KCContactGroup *group1=_contacts[section];

return group1.contacts.count;

}

#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";

static NSString *cellIdentifierForFirstRow=@"UITableViewCellIdentifierKeyWithSwitch";

//首先根据标示去缓存池取

UITableViewCell *cell;

if (indexPath.row==0) {

cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifierForFirstRow];

}else{

cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];

}

//如果缓存池没有取到则重新创建并放到缓存池中

if(!cell){

if (indexPath.row==0) {

cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierForFirstRow];

UISwitch *sw=[[UISwitch alloc]init];

[sw addTarget:self action:@selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];

cell.accessoryView=sw;


}else{

cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];

cell.accessoryType=UITableViewCellAccessoryDetailButton;

}

}


if(indexPath.row==0){

((UISwitch *)cell.accessoryView).tag=indexPath.section;

}


cell.textLabel.text=[contact getName];

cell.detailTextLabel.text=contact.phoneNumber;

NSLog(@"cell:%@",cell);


return cell;

}

#pragma mark 返回每组头标题名称
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{

NSLog(@"生成组(组%i)名称",section);

KCContactGroup *group=_contacts[section];

return group.name;

}

#pragma mark 返回每组尾部说明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{

NSLog(@"生成尾部(组%i)详情",section);

KCContactGroup *group=_contacts[section];

return group.detail;

}

#pragma mark 返回每组标题索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{

NSLog(@"生成组索引");

NSMutableArray *indexs=[[NSMutableArray alloc]init];

for(KCContactGroup *group in _contacts){

[indexs addObject:group.name];

}

return indexs;

}

#pragma mark - 代理方法
#pragma mark 设置分组标题内容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

if(section==0){

return 50;

}

return 40;

}

#pragma mark 设置每行高度(每行高度可以不一样)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
      return 45;
}

#pragma mark 设置尾部说明内容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
      return 40;
}

#pragma mark 点击行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

_selectedIndexPath=indexPath;

KCContactGroup *group=_contacts[indexPath.section];

KCContact *contact=group.contacts[indexPath.row];

//创建弹出窗口

UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];

alert.alertViewStyle=UIAlertViewStylePlainTextInput; //设置窗口内容样式

UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框

textField.text=contact.phoneNumber; //设置文本框内容

[alert show]; //显示窗口

}

#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];//后面的参数代码更新时的动画

}

}


#pragma mark 重写状态样式方法
-(UIStatusBarStyle)preferredStatusBarStyle{

return UIStatusBarStyleLightContent;

}


#pragma mark 切换开关转化事件
-(void)switchValueChange:(UISwitch *)sw{

NSLog(@"section:%i,switch:%i",sw.tag, sw.on);

}
@end


最终运行效果:




意:

  1. 由于此时我们需要两种UITableViewCell样式,考虑到性能我们需要在缓存池缓存两种Cell。 
  2. UISwitch继承于UIControl而不是UIView(当然UIControl最终也是继承于UIView),继承于UIControl的控件使用addTarget添加对应事件而不是代理,同时有“是否可用”、“是否高亮”、“是否选中”等属性; 
  3. 上面代码中如果有些UITableViewCell的UISwitch设置为on当其他控件重用时状态也是on,解决这个问题可以在模型中设置对应的属性记录其状态,在生成cell时设置当前状态(为了尽可能简化上面的代码这里就不再修复这个问题);

2.自定义UITableViewCell

虽然系统自带的UITableViewCell已经够强大了,但是很多时候这并不能满足我们的需求。例如新浪微博的Cell就没有那么简单:

接下来就定义一个KCStatusTableViewCell实现UITableViewCell,一般实现自定义UITableViewCell需要分为两步:第一初始化控件;第二设置数据,重新设置控件frame。原因就是自定义Cell一般无法固定高度,很多时候高度需要随着内容改变。此外由于在单元格内部是无法控制单元格高度的,因此一般会定义一个高度属性用于在UITableView的代理事件中设置每个单元格高度。

1.首先看一下微博模型KCStatus,这个模型主要的方法就是根据plist字典内容生成微博对象:

KCStatus.h

#import <Foundation/Foundation.h>

@interface KCStatus : NSObject

#pragma mark - 属性
@property (nonatomic,assign) long long Id;//微博id
@property (nonatomic,copy) NSString *profileImageUrl;//头像
@property (nonatomic,copy) NSString *userName;//发送用户
@property (nonatomic,copy) NSString *mbtype;//会员类型
@property (nonatomic,copy) NSString *createdAt;//创建时间
@property (nonatomic,copy) NSString *source;//设备来源
@property (nonatomic,copy) NSString *text;//微博内容



#pragma mark - 方法
#pragma mark 根据字典初始化微博对象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic;

#pragma mark 初始化微博对象(静态方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic;
@end


KCStatus.m

#import "KCStatus.h"

@implementation KCStatus

#pragma mark 根据字典初始化微博对象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic{

if(self=[super init]){

self.Id=[dic[@"Id"] longLongValue];

self.profileImageUrl=dic[@"profileImageUrl"];

self.userName=dic[@"userName"];

self.mbtype=dic[@"mbtype"];

self.createdAt=dic[@"createdAt"];

self.source=dic[@"source"];

self.text=dic[@"text"];

}

return self;

}

#pragma mark 初始化微博对象(静态方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic{

KCStatus *status=[[KCStatus alloc]initWithDictionary:dic];

return status;

}

-(NSString *)source{
        return [NSString stringWithFormat:@"来自 %@",_source];
}
@end

2.然后看一下自定义的Cell

KCStatusTableViewCell.h

#import <UIKit/UIKit.h>
@class KCStatus;

@interface KCStatusTableViewCell : UITableViewCell

#pragma mark 微博对象
@property (nonatomic,strong) KCStatus *status;

#pragma mark 单元格高度
@property (assign,nonatomic) CGFloat height;

@end


KCStatusTableViewCell.m

#import "KCStatusTableViewCell.h"
#import "KCStatus.h"
#define KCColor(r,g,b) [UIColor colorWithHue:r/255.0 saturation:g/255.0 brightness:b/255.0 alpha:1] //颜色宏定义
#define kStatusTableViewCellControlSpacing 10 //控件间距
#define kStatusTableViewCellBackgroundColor KCColor(251,251,251)
#define kStatusGrayColor KCColor(50,50,50)
#define kStatusLightGrayColor KCColor(120,120,120)

#define kStatusTableViewCellAvatarWidth 40 //头像宽度
#define kStatusTableViewCellAvatarHeight kStatusTableViewCellAvatarWidth
#define kStatusTableViewCellUserNameFontSize 14
#define kStatusTableViewCellMbTypeWidth 13 //会员图标宽度
#define kStatusTableViewCellMbTypeHeight kStatusTableViewCellMbTypeWidth
#define kStatusTableViewCellCreateAtFontSize 12
#define kStatusTableViewCellSourceFontSize 12
#define kStatusTableViewCellTextFontSize 14


@interface KCStatusTableViewCell(){
UIImageView *_avatar;//头像
UIImageView *_mbType;//会员类型
UILabel *_userName;
UILabel *_createAt;
UILabel *_source;
UILabel *_text;
}

@end

@implementation KCStatusTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {

self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

if (self) {

[self initSubView];

     }
    return self;
}

#pragma mark 初始化视图
-(void)initSubView{

//头像控件

_avatar=[[UIImageView alloc]init];

[self.contentView addSubview:_avatar];

//用户名

_userName=[[UILabel alloc]init];

_userName.textColor=kStatusGrayColor;

_userName.font=[UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize];

[self.contentView addSubview:_userName];

//会员类型

_mbType=[[UIImageView alloc]init];

[self.contentView addSubview:_mbType];

//日期

_createAt=[[UILabel alloc]init];

_createAt.textColor=kStatusLightGrayColor;

_createAt.font=[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize];

[self.contentView addSubview:_createAt];

//设备

_source=[[UILabel alloc]init];

_source.textColor=kStatusLightGrayColor;

_source.font=[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize];

[self.contentView addSubview:_source];

//内容

_text=[[UILabel alloc]init];

_text.textColor=kStatusGrayColor;

_text.font=[UIFont systemFontOfSize:kStatusTableViewCellTextFontSize];

_text.numberOfLines=0;

// _text.lineBreakMode=NSLineBreakByWordWrapping;

[self.contentView addSubview:_text];

}

#pragma mark 设置微博
-(void)setStatus:(KCStatus *)status{

//设置头像大小和位置

CGFloat avatarX=10,avatarY=10;

CGRect avatarRect=CGRectMake(avatarX, avatarY, kStatusTableViewCellAvatarWidth, kStatusTableViewCellAvatarHeight);

_avatar.image=[UIImage imageNamed:status.profileImageUrl];

_avatar.frame=avatarRect;



//设置会员图标大小和位置

CGFloat userNameX= CGRectGetMaxX(_avatar.frame)+kStatusTableViewCellControlSpacing ;

CGFloat userNameY=avatarY;

//根据文本内容取得文本占用空间大小

CGSize userNameSize=[status.userName sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]}];

CGRect userNameRect=CGRectMake(userNameX, userNameY, userNameSize.width,userNameSize.height);

_userName.text=status.userName;

_userName.frame=userNameRect;



//设置会员图标大小和位置

CGFloat mbTypeX=CGRectGetMaxX(_userName.frame)+kStatusTableViewCellControlSpacing;

CGFloat mbTypeY=avatarY;

CGRect mbTypeRect=CGRectMake(mbTypeX, mbTypeY, kStatusTableViewCellMbTypeWidth, kStatusTableViewCellMbTypeHeight);

_mbType.image=[UIImage imageNamed:status.mbtype];

_mbType.frame=mbTypeRect;



//设置发布日期大小和位置

CGSize createAtSize=[status.createdAt sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]}];

CGFloat createAtX=userNameX;

CGFloat createAtY=CGRectGetMaxY(_avatar.frame)-createAtSize.height;

CGRect createAtRect=CGRectMake(createAtX, createAtY, createAtSize.width, createAtSize.height);

_createAt.text=status.createdAt;

_createAt.frame=createAtRect;



//设置设备信息大小和位置

CGSize sourceSize=[status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]}];

CGFloat sourceX=CGRectGetMaxX(_createAt.frame)+kStatusTableViewCellControlSpacing;

CGFloat sourceY=createAtY;

CGRect sourceRect=CGRectMake(sourceX, sourceY, sourceSize.width,sourceSize.height);

_source.text=status.source;

_source.frame=sourceRect;



//设置微博内容大小和位置

CGFloat textX=avatarX;

CGFloat textY=CGRectGetMaxY(_avatar.frame)+kStatusTableViewCellControlSpacing;

CGFloat textWidth=self.frame.size.width-kStatusTableViewCellControlSpacing*2;

CGSize textSize=[status.text boundingRectWithSize:CGSizeMake(textWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]} context:nil].size;

CGRect textRect=CGRectMake(textX, textY, textSize.width, textSize.height);

_text.text=status.text;

_text.frame=textRect;


_height=CGRectGetMaxY(_text.frame)+kStatusTableViewCellControlSpacing;

}

#pragma mark 重写选择事件,取消选中
-(void)setSelected:(BOOL)selected animated:(BOOL)animated{

}
@end


这是我们自定义Cell这个例子的核心,自定义Cell分为两个步骤:首先要进行各种控件的初始化工作,这个过程中只要将控件放到Cell的View中同时设置控件显示内容的格式(字体大小、颜色等)即可;然后在数据对象设置方法中进行各个控件的布局(大小、位置)。在代码中有几点需要重点提示大家:

  • 对于单行文本数据的显示调用- (CGSize)sizeWithAttributes:(NSDictionary *)attrs;方法来得到文本宽度和高度。 
  • 对于多行文本数据的显示调用- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context ;方法来得到文本宽度和高度;同时注意在此之前需要设置文本控件的numberOfLines属性为0。 
  • 通常我们会在自定义Cell中设置一个高度属性,用于外界方法调用,因为Cell内部设置Cell的高度是没有用的,UITableViewCell在初始化时会重新设置高度。

3.最后我们看一下自定义Cell的使用过程:

KCStatusViewController.m


#import "KCStatusCellViewController.h"
#import "KCStatus.h"
#import "KCStatusTableViewCell.h"

@interface KCStatusCellViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
UITableView *_tableView;
NSMutableArray *_status;
NSMutableArray *_statusCells;//存储cell,用于计算高度
}
@end

@implementation KCStatusCellViewController
- (void)viewDidLoad {

[super viewDidLoad

//初始化数据

[self initData];

//创建一个分组样式的UITableView

_tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];

//设置数据源,注意必须实现对应的UITableViewDataSource协议

_tableView.dataSource=self

//设置代理

_tableView.delegate=self;

[self.view addSubview:_tableView]

}


#pragma mark 加载数据
-(void)initData{
        NSString *path=[[NSBundle mainBundle] pathForResource:@"StatusInfo" ofType:@"plist"];
        NSArray *array=[NSArray arrayWithContentsOfFile:path];
        _status=[[NSMutableArray alloc]init];
        _statusCells=[[NSMutableArray alloc]init];
        [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [_status addObject:[KCStatus statusWithDictionary:obj]];
        KCStatusTableViewCell *cell=[[KCStatusTableViewCell alloc]init];
        [_statusCells addObject:cell];
        }];
}
#pragma mark - 数据源方法
#pragma mark 返回分组数
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        return 1;
}

#pragma mark 返回每组行数
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

        return _status.count;
}

#pragma mark返回每行的单元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

           static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";

           KCStatusTableViewCell *cell;

           cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier]

           if(!cell){

          cell=[[KCStatusTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];

}

//在此设置微博,以便重新布局

      KCStatus *status=_status[indexPath.row];

     cell.status=status;

      return cell;

}


#pragma mark - 代理方法
#pragma mark 重新设置单元格高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
           KCStatusTableViewCell *cell= _statusCells[indexPath.row];
           cell.status=_status[indexPath.row];
          return cell.height;
}

#pragma mark 重写状态样式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
         return UIStatusBarStyleLightContent;
}
@end


这个类中需要重点强调一下:Cell的高度需要重新设置(前面说过无论Cell内部设置多高都没有用,需要重新设置),这里采用的方法是首先创建对应的Cell,然后在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;方法中设置微博数据计算高度通知UITableView。

最后我们看一下运行的效果:



常用操作:

UITableView和UITableViewCell提供了强大的操作功能,这一节中会重点讨论删除、增加、排序等操作。为了方便演示我们还是在之前的通讯录的基础上演示,在此之前先来给视图控制器添加一个工具条,在工具条左侧放一个删除按钮,右侧放一个添加按钮:

#pragma mark 添加工具栏
-(void)addToolbar{
           CGRect frame=self.view.frame;
          _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, kContactToolbarHeight)];
          // _toolbar.backgroundColor=[UIColor colorWithHue:246/255.0 saturation:246/255.0 brightness:246/255.0 alpha:1];
          [self.view addSubview:_toolbar];
          UIBarButtonItem *removeButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self              action:@selector(remove)];
          UIBarButtonItem *flexibleButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
          UIBarButtonItem *addButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];
           NSArray *buttonArray=[NSArray arrayWithObjects:removeButton,flexibleButton,addButton, nil];
           _toolbar.items=buttonArray;
}


1.删除

#pragma mark 删除
-(void)remove{
//直接通过下面的方法设置编辑状态没有动画
//_tableView.editing=!_tableView.isEditing;

[_tableView setEditing:!_tableView.isEditing animated:true];
}


用过iOS的朋友都知道,一般这种Cell如果向左滑动右侧就会出现删除按钮直接删除就可以了。其实实现这个功能只要实现代理-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;方法,只要实现了此方法向左滑动就会显示删除按钮。只要点击删除按钮这个方法就会调用,但是需要注意的是无论是删除还是添加都是执行这个方法,只是第二个参数类型不同。下面看一下具体的删除实现:

#pragma mark 删除操作
//实现了此方法向左滑动就会显示删除按钮

-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
          KCContactGroup *group =_contacts[indexPath.section];
          KCContact *contact=group.contacts[indexPath.row];
          if (editingStyle==UITableViewCellEditingStyleDelete) {
          [group.contacts removeObject:contact];
         //考虑到性能这里不建议使用reloadData
         //[tableView reloadData];
         //使用下面的方法既可以局部刷新又有动画效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];

       //如果当前组中没有数据则移除组刷新整个表格
       if (group.contacts.count==0) {
        [_contacts removeObject:group];
        [tableView reloadData];
       }
   }
}

2.添加

添加和删除操作都是设置UITableView的编辑状态,具体是添加还是删除需要根据代理方法 -(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath; 的返回值来确定。因此这里我们定义一个变量来记录点击了哪个按钮,根据点击按钮的不同在这个方法中返回不同的值。

#pragma mark 取得当前操作状态,根据不同的状态左侧出现不同的操作按钮
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
          if (_isInsert) {
          return UITableViewCellEditingStyleInsert;
         }
return UITableViewCellEditingStyleDelete;
}

#pragma mark 编辑操作(删除或添加)
//实现了此方法向左滑动就会显示删除(或添加)图标

-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
         KCContactGroup *group =_contacts[indexPath.section];
         KCContact *contact=group.contacts[indexPath.row];
         if (editingStyle==UITableViewCellEditingStyleDelete) {
         [group.contacts removeObject:contact];

         //考虑到性能这里不建议使用reloadData
         //[tableView reloadData];
         //使用下面的方法既可以局部刷新又有动画效果
         [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];

        //如果当前组中没有数据则移除组刷新整个表格
       if (group.contacts.count==0) {
       [_contacts removeObject:group];
         [tableView reloadData];
   }
   }else if(editingStyle==UITableViewCellEditingStyleInsert){
         KCContact *newContact=[[KCContact alloc]init];
         newContact.firstName=@"first";
         newContact.lastName=@"last";
         newContact.phoneNumber=@"12345678901";
         [group.contacts insertObject:newContact atIndex:indexPath.row];
         [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];//注意这里没有使用reladData刷新
        }
}

3.排序

只要实现-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;代理方法当UITableView处于编辑状态时就可以排序。


#pragma mark 排序
//只要实现这个方法在编辑状态右侧就有排序图标
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{

KCContactGroup *sourceGroup =_contacts[sourceIndexPath.section];

KCContact *sourceContact=sourceGroup.contacts[sourceIndexPath.row];

KCContactGroup *destinationGroup =_contacts[destinationIndexPath.section];

[sourceGroup.contacts removeObject:sourceContact];

if(sourceGroup.contacts.count==0){

[_contacts removeObject:sourceGroup];

[tableView reloadData];

}

             [destinationGroup.contacts insertObject:sourceContact atIndex:destinationIndexPath.row];

}



最后给大家附加上原网址(大神的网址):http://www.cnblogs.com/kenshincui/p/3931948.html






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值