1 创建一个UITableViewController并展示简单数据
1.1 问题
有很多移动客户端的应用都是采用表的形式来展示数据,因为表视图能使数据看起来更规整、更有调理,比如微信界面就是使用的表视图,如图-1所示:
图-1
在IOS中表视图是非常重要的视图,类型名称为UITabelViewController,是UIViewController的子类,本案例将学习如何使用UITableViewController来展示简单的数据,完成效果如图-2所示:
图-2
1.2 方案
首先创建一个SingleViewApplication项目,创建一个TRMyTableViewController类继承至UITableViewController,在TRAppDelegate的程序入口方法里面创建一个TRMyTableViewController对象做为根视图控制器。
UITableViewController类有一个UITabelView类型的属性tableView,该属性就是表视图控制器管理的表视图,类似UIViewController和属性view的关系。表视图由以下几个部分组成:
表头视图tableHeaderView:表视图最上边的视图,用于展示表视图的信息;
表脚视图tableFooterView:表视图最下边的视图;
单元格cell:是一个UITableViewCell对象,表视图每一行的单位视图;
分区section:由多个单元格cell组成,有分区头和分区脚。
表视图有两个委托协议一个是UITableViewDataSource用于给表视图加载数据,另一个是UITableViewDelegate用于实现其他事件的委托。
另外表视图还有两个属性id <UITableViewDataSource>类型的dataSource和id <UITableViewDelegate>类型的delegate,分别用于指定加载数据的对象和被委托对象。
然后在TRMyTableViewController类里面通过实现的UITableViewDataSource协议的方法给表视图加载简单的数据。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建TRMyTableViewController对象做为程序的根视图控制器
首先创建一个单视图应用项目命名为TRMyTableViewController,再创建一个带有xib的TRMyTableViewController类继承至UITableViewController,生成的xib文件如图-3所示:
图-3
然后在TRAppDelegate.m的程序入口方法中创建一个TRMyTableViewController对象做为程序的根视图,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- //创建一个TRTableViewController对象做为根视图控制器
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
在这里myTVC视图控制器对象管理的表视图就是程序展示的第一个界面视图。
步骤二:实现UITableViewDataSource协议的方法给表视图加载数据
UITabelViewController类遵守了两个协议一个是UITableViewDataSource用于给表视图加载数据,另一个是UITableViewDelegate用于实现委托,这里先学习通过实现UITableViewDataSource协议的方法来给表视图加载数据。
系统会自动将TRMyTableViewController指定为它所管理的的表视图的dataSource和delegate,可以在xib中查看,选中tableView点击右键,出现如图-4所示的窗口:
图-4
从图可见dataSource和delegate都是和File’s Owner关联上的,此时的File’s Owner即TRMyTableViewController。
TRMyTableViewController类是UITabelViewController的子类,所以TRMyTableViewController也遵守了以上两个协议,不用再额外写遵守协议代码,直接实现协议里面的方法即可,这里需要实现如下三个协议方法:
numberOfSectionsInTableView:可选方法,用于告诉表视图需要显示多少分区,不实现的时候默认返回1个分区;
tableView:numberOfRowsInSection:必须实现的方法,用于告诉表视图每组需要显示多少行;
tableView:cellForRowAtIndexPath:必须实现的方法,用于告诉表视图每一行显示的内容;
在这里先指定表视图显示1组,每组显示20行,代码如下所示:
- -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 1;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return 20;
- }
每一行用于显示内容的是一个UITableViewCell类型的对象,该方法里面需要先创建一个UITableViewCell类型的对象cell,使用初始化方法initWithStyle: reuseIdentifier:进行初始化,style参数是cell的样式,这里选择系统默认的样式即可,reuseIdentifier参数传递一个字符串@”Cell”,本案例先不对该参数的作用进行阐述,后面的案例会进行解释。
UITableViewCell有一个UILabel类型的属性textLabel,该属性用来展示文本内容,这里设置cell的textLabel的显示内容,最后将cell对象返回,代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //创建cell对象
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
- //设置cell的显示内容
- cell.textLabel.text = @"Hello World.";
- return cell;
- }
1.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRMyTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRMyFirstViewController.m文件中的完整代码如下所示:
- #import "TRMyTableViewController.h"
- @implementation TRMyTableViewController
- #pragma mark - Table view data source
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 1;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return 20;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
- cell.textLabel.text = @"Hello World.";
- return cell;
- }
- @end
2 使用IndexPath中的两个属性区别分区展示数据
2.1 问题
上一个案例已经学习了如何使用表视图控制器展现一组简单的数据,本案例将在上一个案例的基础上让表视图以多个分区的形式展示数据,如图-5所示:
图-5
2.2 方案
首先同上一个案例一样创建一个表视图控制器TRMytableViewController作为本应用的根视图控制器。
其次通过实现UITableViewDataSource协议的方法告诉tableView有多少分区、有多少行以及每一行的显示内容。
2.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建TRMyTableViewController对象做为程序的根视图控制器
首先同第一个案例一样创建一个单视图应用项目命名为TRMyTableViewController,再创建一个带有xib的TRMyTableViewController类继承至UITableViewController。
步骤二:实现UITableViewDataSource协议的方法给表视图加载数据
NSIndexPath用来管理存储路径,有两个属性section和row,section用来描述分区,row用来描述分区里面的某一行。
首先通过实现numberOfSectionsInTableView:方法让tabelView分成三个区,该方法不实现默认是返回一个分区,代码如下所示:
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 3;
- }
然后通过tableView:numberOfRowsInSection:方法确定每个分区的行数,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if(section == 0) return 2;
- else if(section == 1) return 3;
- else if(section == 2) return 1500;
- return 0;
- }
最后通过实现tableView:cellForRowAtIndexPath:确定不同分区的每一行的显示内容,本案例创建cell选择的是UITableViewCellStyleSubtitl类型,该类型除了显示textLabel的显示内容,还可以设置cell的另外两个属性,如下所示:
imageView属性:用来显示一张图片;
detailTextLabel属性:用来显示详细信息。
代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
- if(indexPath.section == 0){
- cell.textLabel.text = [NSString stringWithFormat:@"第0区的第%d行",indexPath.row];
- }else{
- cell.textLabel.text = [NSString stringWithFormat:@"%d区%d行",indexPath.section,indexPath.row];
- }
- cell.imageView.image = [UIImage imageNamed:@"a.png"];
- cell.detailTextLabel.text = @"这是详细信息";
- return cell;
- }
2.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRMyTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRMyTableViewController *myTVC = [[TRMyTableViewController alloc]initWithNibName:@"TRMyTableViewController" bundle:nil];
- self.window.rootViewController = myTVC;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRMyTableViewController.m文件中的完整代码如下所示:
- #import "TRMyTableViewController.h"
- @implementation TRMyTableViewController
- #pragma mark - Table view data source
- //如果不实现此方法,默认为1个分区
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 3;
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- if(section == 0) return 2;
- else if(section == 1) return 3;
- else if(section == 2) return 1500;
- return 0;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
- if(indexPath.section == 0){
- cell.textLabel.text = [NSString stringWithFormat:@"第0区的第%i行",indexPath.row];
- }else{
- cell.textLabel.text = [NSString stringWithFormat:@"%i区%i行",indexPath.section,indexPath.row];
- }
- cell.imageView.image = [UIImage imageNamed:@"a.png"];
- cell.detailTextLabel.text = @"这是详细信息";
- return cell;
- }
- @end
3 展示Cell重用时的数据处理
3.1 问题
表视图在展现数据的时候,超出界面的单元格cell对象并不会被释放,而是放入了tableView的用于管理单元格的队列中,所以需要在创建cell对象之前先试着从此队列中去拿已经存在的cell对象,如果能拿到就不需要再创建cell对象,而是重用此Cell对象,以减少对内存的占用,本案例将演示cell的重用过程。
3.2 方案
UITableViewCell的重用机制:
1)当tableView在创建每一个cell的时候会对cell做一个重用标记,该标记就是初始化方法initWithStyle:reuseIdentifier:的reuseIdentifier参数,是一个NSString类型;
2)当表视图的单元格超出界面之后会将该单元格加入的tableView的单元格队列中,因此每次创建cell之前都会做一个判断,判断tableView的单元格队列中是否存在cell对象,如果有就直接使用,如果没有就创建一个新的cell对象。
3.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义一个重用标记
在tableView:cellForRowAtIndexPath:方法里面首先创建一个NSString类型的对象cellIdentifier用做重用标记,该对象是一个static类型,保证在程序结束之前不会被销毁,代码如下所示:
- static NSString *cellIdentifier = @"MyCell";
步骤二:判断tableView的单元格队列中是否存在cell对象
通过tabelView的dequeueReusableCellWithIdentifier:方法获取 cell对象,如果该方法返回的cell对象不为空,说明有可重用的cell,identifier参数就是上一步所创建的重用标记cellIdentifier,代码如下所示:
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
步骤三:如果没有可重用的cell则创建一个新的cell对象
通过上一步获取到的cell对象如果为空,说明没有可以重用的cell对象,则需要创建一个新的cell对象以供使用,代码如下所示:
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
3.4 完整代码
本案例中,TRTableViewController.m文件中的完整代码如下所示:
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- return cell;
- }
- @end
4 注册Cell并重用
4.1 问题
本案例学习使用注册cell的方式实现cell的重用。
4.2 方案
首先向tableView注册一个重用的Cell类;
然后向tableView索要队列中的Cell对象时,如果队列中没有Cell对象,tableView会自动创建一个并返回,这样可以确保dequeue方法一定会返回一个Cell对象供使用。
4.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:注册cell
在TRTableViewController里面首先创建一个NSString类型的对象cellIdentifier用做重用标记,该对象是一个static类型,保证在程序结束之前不会被销毁,代码如下所示:
- static NSString *cellIdentifier = @"MyCell";
在viewDidLoad方法里面使用registerClass: forCellReuseIdentifier:方法注册cell,代码如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //注册一个Cell类型到tableView
- [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellIdentifier];
- }
步骤二:创建cell对象
在tableView: cellForRowAtIndexPath:方法里面创建cell对象,使用dequeueReusableCellWithIdentifier: forIndexPath:方法创建cell对象时,如果没有可重用的cell会自动创建一个,因此能保证返回一个cell对象,代码如下所示:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //只要注册过Cell类型,此处tableView保证能返回一个Cell对象
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
- return cell;
- }
4.4 完整代码
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- //定义cell的重用标识
- static NSString *cellIdentifier = @"MyCell";
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //注册一个Cell类型到tableView
- [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellIdentifier];
- }
- //cellForRow方法里面的代码
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- //只要注册过Cell类型,此处tableView保证能返回一个Cell对象
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
- @end
5 使用TableView展示数据列表
5.1 问题
为了使tableView显示的数据更灵活,通常都会有一个NSArray类型的属性来管理表视图的数据,本案例在第三个案例(或者案例四)的基础上让表视图展示一组字符串数据(城市名称)。
5.2 方案
首先定义一个NSArray类型的属性用于存储表视图的数据;
其次在TRAppDelegate的程序入口方法里面初始化数组;
最后在协议方法里面给表视图加载数据。
5.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义一个存储数据的NSArray类型的属性
为了使tableView显示的数据更灵活,通常都会有一个NSArray类型的属性来管理表视图的数据,在TRTableViewController类里面定义一个公开的属性NSArray类型的属性data,代码如下所示:
- @property (nonatomic, strong) NSArray *data;
步骤二:初始化存储数据的数组
在TRAppdelegate的程序入口方法里面对该属性进行初始化,这里给一个模拟数据,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRTableViewController *tvc = [[TRTableViewController alloc]initWithNibName:@"TRTableViewController" bundle:nil];
- tvc.data = @[@"北京", @"上海", @"广州", @"深圳", @"济南",@"石家庄",@"武汉",@"郑州",@"成都",@"重庆",@"珠海",@"香港",@"澳门"];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:tvc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
步骤三:给表视图加载数据
首先在tableView:numberOfRowsInSection:方法里面返回表视图的行数,通常都是返回一个数组的count,本案例即self.data.count,代码如下所示:
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.data.count;
- }
然后在TRTableViewController类里面的tableView:cellForRowAtIndexPath:的方法里面给表视图加载数据,代码如下所示:
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
运行程序实现效果如图-6所示:
图-6
步骤四:点击某一行单元格
在点击表视图某一行时会调用tableView:didSelectRowAtIndexPath:方法,该方法是UITabelViewDelegate协议里面的方法,因此只需在TRTableViewController.m文件中实现该方法即可,indexPath参数就是被点击的单元格的路径,代码如下所示:
- //当用户点击了某一行Cell时被调用
- -(void)tableView:(UITableView *)tableView
- didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSLog(@"用户点击了%d区的%d行, %@", indexPath.section, indexPath.row, self.data[indexPath.row]);
- }
运行程序,点击某一行可见控制台会输出被点击的地区名称。
5.4 完整代码
本案例中,TRAppDeleagte.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRTableViewController.h"
- @implementation TRAppDelegate
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRTableViewController *tvc = [[TRTableViewController alloc]initWithNibName:@"TRTableViewController" bundle:nil];
- tvc.data = @[@"北京", @"上海", @"广州", @"深圳", @"济南",@"石家庄",@"武汉",@"郑州",@"成都",@"重庆",@"珠海",@"香港",@"澳门"];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:tvc];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRTableViewController.m文件中的完整代码如下所示:
- #import "TRTableViewController.h"
- @implementation TRTableViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"地区";
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.data.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"MyCell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
- if(cell==nil){
- cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
- }
- cell.textLabel.text = self.data[indexPath.row];
- return cell;
- }
- @end
6 项目友录中的朋友列表
6.1 问题
模拟系统的通讯录功能,使用表视图控制器展示联系人界面,并实现添加联系人功能,本案例表视图需要展示的是一组对象数据,实现效果如图-7、图-8所示:
图-7
图-8
6.2 方案
首先创建一个SingleViewApplication项目,再创建一个表视图控制器类TRContactTableViewController,该类有一个管理联系人的NSMutableArray类型的属性contacts。
在TRAppDelegate的程序入口方法里面创建一个带有导航的表视图控制器做为根视图控制器。
其次根据本案例的需求创建一个联系人类TRCantact类,用于保存联系人信息。
然后创建一个TRInputViewController类继承至UIViewController,用于管理编辑联系人界面。再给TRContactTableViewController的导航栏添加一个编辑按钮,点击按钮跳转到编辑界面,并实现表视图数据源协议方法。
最后在TRInputViewController类里面根据输入的信息创建TRContact对象,然后通过委托的方式将新建联系人对象传递给TRContactTableViewController类,并回退到联系人界面。TRContactTableViewController类里面通过实现协议方法给联系人页面加载和更新数据。
6.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建项目,设置根视图控制器
创建一个SingleViewApplication项目,然后再创建一个表视图控制器类TRContactTableViewController,并且定义一个用来管理联系人的NSMutableArray类型的属性contacts,代码如下所示:
- @interface TRContactTableViewController ()
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- //重写setter方法,初始化实例变量_contacts
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
在TRAppDelegate的程序入口方法里面创建一个带有导航的表视图控制器做为根视图控制器,代码如下所示:
- -(BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRContactTableViewController *contactTVC = [[TRContactTableViewController alloc]initWithNibName:@"TRContactTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:contactTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
步骤二:创建TRContact联系人类
创建一个TRContact类继承至NSObject,并且定义两个NSString类型的属性name和phoneNumber,分别用于存储联系人的姓名和手机号,代码如下所示:
- @property (nonatomic,copy)NSString *name;
- @property (nonatomic,copy)NSString *phoneNumber;
然后自定义一个初始化方法initWithName:andPhoneNumber:,方便外部创建联系人对象的时候进行初始化,代码如下所示:
- -(instancetype)initWithName:(NSString*)name
- andPhoneNumber:(NSString*)phoneNumber
- {
- self = [super init];
- if (self) {
- self.name = name;
- self.phoneNumber = phoneNumber;
- }
- return self;
- }
步骤三:创建TRInputViewController类,并实现页面跳转
创建一个TRInputViewController类,用于管理编辑联系人界面,在xib文件中拖放两个输入框(接受用户输入的联系人姓名和手机号),两个label和一个按钮,并在检查器中设置相关属性,这里需要注意将用于接收用户输入手机号的输入框,键盘类型选择NumberPad类型,完成效果如图-9所示:
图-9
然后给TRContactTableViewController的导航栏添加标题和编辑按钮,点击按钮实现页面的跳转,这里使用present的方式跳转页面,新页面会带有一个新的导航控制器,代码如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //设置导航栏标题
- self.title = @"通讯录";
- //给导航栏添加编辑按钮,点击按钮会调用addContact:方法
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- //实现addContact方法
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
实现表视图数据源协议方法,完成数据加载代码,这里只有一个分区,因此可不用实现分区方法默认返回1,行数直接返回self.contacts.count,在tableView: cellForRowAtIndexPath:方法里,让cell的textLabel显示联系人的姓名,cell的detailTextLabel显示联系人的手机号,代码如下所示:
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- //创建cell时,类型选择UITableViewCellStyleValue1,右边显示detailTextLabel
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- //通过indexPath.row在self.contacts数组里面找到当前cell对应的联系人对象
- TRContact *contact = self.contacts[indexPath.row];
- //cell.textLabel显示联系人姓名
- cell.textLabel.text = contact.name;
- //cell.detailTextLabel显示联系人的手机号
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
运行程序,界面效果如图-10所示:
图-10
从图中可见目前联系人页面没有任何信息,那是因为联系人数组里面还没有任何数据,下一步则是通过编辑页面给联系人数组添加数据。
步骤四:根据用户的输入创建联系人对象并更新联系人界面
将TRInputViewController界面上的两个输入框关联成私有属性nametextField和phoneNumberTextField,代码如下所示:
- @property (weak, nonatomic) IBOutlet UITextField *nameTextField;
- @property (weak, nonatomic) IBOutlet UITextField *phoneNumberTextField;
将确定按钮关联成私有方法submit,该方法的功能主要是根据用户输入的信息创建一个新的TRContact对象contact,并通过委托将contact对象传递给联系人界面,最后返回联系人界面。
因此首先需要在TRInputViewController.h文件里面定义一个协议TRInputViewDelegate和一个委托属性,代码如下所示:
- @class TRInputViewController;
- @protocol TRInputViewDelegate <NSObject>
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact;
- @end
- @interface TRInputViewController : UIViewController
- //在创建TRInputViewController对象时对delegate属性进行赋值确定被委托者
- @property (nonatomic, weak) id<TRInputViewDelegate> delegate;
- @end
然后实现submit方法,代码如下所示:
- - (IBAction)submit
- {
- [self.phoneNumberTextField resignFirstResponder];
- //创建TRContact联系人对象
- TRContact *contact = [[TRContact alloc]initWithName:self.nameTextField.text andPhoneNumber:self.phoneNumberTextField.text];
- //将contact对象传递给联系人界面
- [self.delegate inputViewController:self contact:contact];
- [self dismissViewControllerAnimated:YES completion:nil];
- }
最后TRContactTableViewController遵守TRInputViewDelegate协议,并实现协议方法inputViewController:contact:,该方法里主要是根据传递过来的联系人信息更新contacts数组和界面,这里使用reloadData方法刷新界面,代码如下所示:
- //遵守协议
- @interface TRContactTableViewController () <TRInputViewDelegate>
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- //添加联系人方法里面给inputVC.delegate赋值,指定当前对象为被委托者
- //添加联系人方法里面给inputVC.delegate赋值,指定当前对象为被委托者
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- //实现TRInputViewDelegate协议方法
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- //将传递过来的contact对象添加到联系人数组
- [self.contacts addObject:contact];
- //重新加载数据,更新界面
- [self.tableView reloadData];
- }
6.4 完整代码
本案例中,TRAppDelegate.m文件中的完整代码如下所示:
- #import "TRAppDelegate.h"
- #import "TRContactTableViewController.h"
- @implementation TRAppDelegate
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- self.window.backgroundColor = [UIColor whiteColor];
- TRContactTableViewController *contactTVC = [[TRContactTableViewController alloc]initWithNibName:@"TRContactTableViewController" bundle:nil];
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:contactTVC];
- self.window.rootViewController = navi;
- [self.window makeKeyAndVisible];
- return YES;
- }
- @end
本案例中,TRContactTableViewController.m文件中的完整代码如下所示:
- #import "TRContactTableViewController.h"
- #import "TRInputViewController.h"
- @interface TRContactTableViewController () <TRInputViewDelegate>
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- @implementation TRContactTableViewController
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
- //实现TRInputViewDelegate协议方法
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- //重新加载数据
- [self.tableView reloadData];
- }
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"通讯录";
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- TRContact *contact = self.contacts[indexPath.row];
- cell.textLabel.text = contact.name;
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
- @end
本案例中,TRInputViewController.h文件中的完整代码如下所示:
- #import <UIKit/UIKit.h>
- #import "TRContact.h"
- @class TRInputViewController;
- @protocol TRInputViewDelegate <NSObject>
- -(void)inputViewController:(TRInputViewController *)inputVC
- contact:(TRContact *)contact;
- @end
- @interface TRInputViewController : UIViewController
- @property (nonatomic, weak) id<TRInputViewDelegate> delegate;
- @end
本案例中,TRInputViewController.m文件中的完整代码如下所示:
- #import "TRInputViewController.h"
- @interface TRInputViewController ()
- @property (weak, nonatomic) IBOutlet UITextField *nameTextField;
- @property (weak, nonatomic) IBOutlet UITextField *phoneNumberTextField;
- @end
- @implementation TRInputViewController
- - (IBAction)submit
- {
- [self.phoneNumberTextField resignFirstResponder];
- TRContact *contact = [[TRContact alloc]initWithName:self.nameTextField.text andPhoneNumber:self.phoneNumberTextField.text];
- [self.delegate inputViewController:self contact:contact];
- [self dismissViewControllerAnimated:YES completion:nil];
- }
- - (IBAction)next {
- [self.nameTextField resignFirstResponder];
- [self.phoneNumberTextField becomeFirstResponder];
- }
- @end
本案例中,TRContact.h文件中的完整代码如下所示:
- #import <Foundation/Foundation.h>
- @interface TRContact : NSObject
- @property (nonatomic,copy)NSString *name;
- @property (nonatomic,copy)NSString *phoneNumber;
- -(instancetype)initWithName:(NSString*)name
- andPhoneNumber:(NSString*)phoneNumber;
- @end
本案例中,TRContact.m文件中的完整代码如下所示:
- #import "TRContact.h"
- @implementation TRContact
- -(instancetype)initWithName:(NSString*)name
- andPhoneNumber:(NSString*)phoneNumber
- {
- self = [super init];
- if (self) {
- self.name = name;
- self.phoneNumber = phoneNumber;
- }
- return self;
- }
- @end
7 增加一个新朋友
7.1 问题
上一个案例已经实现的通讯录的展示联系人、添加联系人功能,刷新界面的时候使用reloadData方法,本案例将使用另外一种局部更新界面的方式更新联系人界面。
7.2 方案
本案例直接在上一个案例上做修改,主要是重写TRContactTableViewController类里面更新界面的代码,reloadData方法更新整个界面,也就是会将整个表视图的数据重新加载一遍,本案例采用insertRowsAtIndexPath方法局部更新界面。
7.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:使用insertRowsAtIndexPath方法局部更新界面
insertRowsAtIndexPath方法主要用于在表视图里面插入一行单元格,indexPath参数是该单元格插入的位置,是一个NSIndexPath类型的参数,因此在inputViewController:contact:方法里面,首先同样的将传递过来的contact添加到数组,然后找到插入单元格的位置,使用insertRowsAtIndexPath方法在表视图里面插入单元格,代码如下所示:
- - (void)inputViewController:(TRInputViewController *)inputVC contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- //创建一个NSIndexPath对象,插入单元格的位置
- NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0];
- //表视图中插入单元格
- [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
- }
7.4 完整代码
本案例中,TRContactTableViewController.m文件中的完整代码如下所示:
- #import "TRContactTableViewController.h"
- #import "TRInputViewController.h"
- @interface TRContactTableViewController () <TRInputViewDelegate>
- @property(nonatomic, strong) NSMutableArray *contacts;
- @end
- @implementation TRContactTableViewController
- - (NSMutableArray *)contacts
- {
- if(!_contacts){
- _contacts = [@[] mutableCopy];
- }
- return _contacts;
- }
- //实现TRInputViewDelegate协议方法
- -(void)inputViewController:(TRInputViewController *)inputVC
- contact:(TRContact *)contact
- {
- [self.contacts addObject:contact];
- NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0];
- [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
- }
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"通讯录";
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact:)];
- }
- - (void)addContact:(UIBarButtonItem *)sender
- {
- TRInputViewController *inputVC = [[TRInputViewController alloc]initWithNibName:@"TRInputViewController" bundle:nil];
- inputVC.delegate = self;
- UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:inputVC];
- [self presentViewController:navi animated:YES completion:nil];
- }
- -(NSInteger)tableView:(UITableView *)tableView
- numberOfRowsInSection:(NSInteger)section
- {
- return self.contacts.count;
- }
- -(UITableViewCell *)tableView:(UITableView *)tableView
- cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
- }
- TRContact *contact = self.contacts[indexPath.row];
- cell.textLabel.text = contact.name;
- cell.detailTextLabel.text = contact.phoneNumber;
- return cell;
- }
- @end