Tutorial: Add Data

教程:添加数据


本教程基于你在第二个教程(Tutorial: Storyboards)中建立的工程。你将使用你已经学到的关于使用设计模式、在Foundation中操作、和编写自定义类来为你的ToDoList app添加动态数据提供支持。

本教程会教你如何完成:

使用常用的Foundation类

创建自定义数据类

实现代理和元数据协议

在视图控制器之间传递数据


在你完成本教程的所有步骤后,你将得到一个看上去如下的app:


Create a Data Class

创建一个数据类

首先,在Xcode中打开已经存在的工程。

现在,你有一个使用故事板得到的接口和导航方案给你的ToDoList app。现在,是时候添加数据存储和模型对象的行为。

你的app的目的是常见一个待办事宜项目的列表,所以首先你将创建一个自定义类,XYZToDoItem,来代表个人的待办事宜项。回忆一下,XYZToDoItem类在Writing a Custom Class中已经讨论过了。

创建XYZToDoItem类

1.选择 File > New > File (或者Command+N).
出现一个对话框提示你为你的新文件选择模板。

2.在左侧,在iOS下选择Cocoa Touch 。

3.选择Objective-C类,点击Next。

4.在Class字段,在XYZ前缀后面键入ToDoItem。

5.Choose NSObject from the “Subclass of” pop-up menu. 在 “Subclass of”弹出菜单中选择NSObject。
如果你完全跟随教程,在本步骤中Class标题显示
XYZToDoItemViewController。但你在“Subclass of”中选择NSObject,Xcode知道你制作一个一般的类,并且移除之前被添加的 ViewController文本。

6.点击 Next.

7.默认保存位置为你的工程目录。

8.Group选项默认为你的app名,ToDoList。

9.Targets选项默认为你的app被选择而测试没有被选择。

10.点击 Create.


XYZToDoItem类是简单的实现。它有名字、创建日期、和项目是否完成等属性。继续添加属性到XYZToDoItem类接口文件。

配置XYZToDoItem类

1.在工程导航器中选择 XYZToDoItem.h.

2.向接口文件添加如下属性:

.@interfaceXYZToDoItem : NSObject

.

.

.

.@propertyNSString*itemName;

.

.@propertyBOOLcompleted;

.

.@property(readonly)NSDate*creationDate;

.

.

.

.@end

3.

4.

Checkpoint: 通过选择 Product > Build (或者Command+B). 构建你的工程。你的使用新类做任何事情,但你构建它可一个给编译器一个选择来核实你没有做任何错误的输入。如果你有,通过阅读编译器提供的警告或者错误来修改它们,并且回顾所有本教程的指令来确保每件事都如所描述的样子。


Load the Data

加载数据

现在你有了一个类来让你创建和存储个人的列表项的数据。你还需要保持这些项的清单。跟踪这件事情的最自然的地方是在XYZToDoListViewController类——视图控制器是负责协调模型和视图之间的关系,所以他们需要参考模型。

Foundation框架包括一个类,NSMutableArray,它能很好的跟踪项目的列表。使用可变数组对于用户添加项目到数组非常重要。在不可变数组中,不允许你在初始化后添加项目。

使用一个数组要同时声明和创建它。你通过分配和初始化数组来做到这一点:

分配和初始化数组

1.在工程导航器中选择XYZToDoListViewController.m.
因为项目的数组是你的表视图控制器的具体实现,你要在.m文件中声明而不是.h文件中。只是你的私有自定义类。

2.添加如下的属性到接口类别,Xcode在你自定义的视图控制器创建。声明如下所示:

.@interfaceXYZToDoListViewController()

.

.

.

.@propertyNSMutableArray*toDoItems;

.

.

.

.@end

3.

4.

5.viewDidLoad方法中分配和初始化toDoItems数组:

.-(void)viewDidLoad

.

.{

.

.[superviewDidLoad];

.

.self.toDoItems=[[NSMutableArrayalloc]init];

.

.}

6.

7.

viewDidLoad的实际代码包括一些额外的行——在XYZListViewController创建的时候被Xcode插入——被添加了注释。不用管它们。

现在,你有了一个数组,你能往里面添加项。你会在一个单独的方法,loadInitialData,中做这一点。你将从viewDidLoad中调用。这段代码运行在自己的方法中是因为它是一个模块化的任务,你通过方法单独实现来能提高代码的可读性。在真实的app中,这个方法将从某些持久化存储中加载数据,就像文件一样。现在,我们的目标是看表视图控制器如何操作自定义数据项目,所以你将创建一些测试数据来试验。

创建一个项目到到你创建的数组中:分配和初始化。然后给这个项目起名。这个名字将被显示在表视图中。多做几个这样的项目。

加载初始化数据

1.添加一个方法,loadInitialData,在 @implementation行的下面。

.-(void)loadInitialData{

.

.}

2.

3.

4.在这个方法中,创建几个清单项目,然后添加它们到数组中。

.-(void)loadInitialData{

.

.XYZToDoItem*item1=[[XYZToDoItemalloc]init];

.

.item1.itemName=@"Buy milk";

.

.[self.toDoItemsaddObject:item1];

.

.XYZToDoItem*item2=[[XYZToDoItemalloc]init];

.

.item2.itemName=@"Buy eggs";

.

.[self.toDoItemsaddObject:item2];

.

.XYZToDoItem*item3=[[XYZToDoItemalloc]init];

.

.item3.itemName=@"Read a book";

.

.[self.toDoItemsaddObject:item3];

.

.}

5.

6.

7.在viewDidLoad方法中调用loadInitialData。

.-(void)viewDidLoad

.

.{

.

.[superviewDidLoad];

.

.self.toDoItems=[[NSMutableArrayalloc]init];

.

.[selfloadInitialData];

.

.}

8.

9.

Checkpoint: 通过选择Product > Build来构建你的工程。你会看在loadInitialData方法中看到很多错误。关键是第一行的错误,它应该是说 “Use of undeclared identifier XYZToDoItem.”它的意思是说编译器在编译XYZToDoListViewController的时候不知道关于你的XYZToDoItem。编译器是非常特殊的,它需要被确切的告知要去注意什么。

告诉编译器注意你自定义的清单项目类

1.在XYZToDoListViewController.m文件的顶部附近找到#import "XYZToDoListViewController.h"

2.在他下面添加如下行:

.#import "XYZToDoItem.h"

3.

4.

Checkpoint: 构建你的工程,你将发现没有错误了。


Display the Data

显示数据

现在,你的视图有了一个可变数组,它被一些简单的待办事宜项目填充。现在你需要在表视图上显示这些数据。

你将通过制作表视图XYZToDoListViewController数据源来做到这一点。为了使用表视图数据源,需要实现UITableViewDataSource委托。这些你需要实现的方法原来在第二个教程中是被注释掉得。要有一个功能表需要三个方法。第一个方法numberOfSectionsInTableView:,它告诉表视图有多少区域需要被显示。在这个app中,你只需要显示一个区域在表视图中,所以这个实现非常简单。

在你的表中显示区域

1.在工程导航器中选择 XYZToDoListViewController.m.

2.如果你注释了这些方法,现在移除掉注释。

3.找到区域的实现模板,如下所示:

.-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

.

.{

.

.#warning Potentially incomplete method implementation.

.

.// Return the number of sections.

.

.return0;

.

.}

4.

5.

你要的是一个区域,所以你移除警告行并且改变返回值从0到1.

6.改变numberOfSectionsInTableView:数据源方法的返回值为单一区域,如下所示:

.-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

.

.{

.

.// Return the number of sections.

.

.return1;

.

.}

7.

8.

下一个方法,tableView:numberOfRowsInSection:,告诉表视图在给定的区域中显示多少行。在你的表中有一个区域,并且每个to-do 项应该在表视图中有自己的行。这意味着行数应该是在toDoItems数组中的XYZToDoItem对象数。

在你的表中返回行数

1.在工程导航器中选择 XYZToDoListViewController.m.

2.找到模板实现的部分如下所示:

.-(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section

.

.{

.

.#warning Incomplete method implementation.

.

.// Return the number of rows in the section.

.

.return0;

.

.}

3.

4.

你需要返回你有的清单项目的数目。幸运的是,
NSArray有一个方便的方法叫做count,它能返回在数组中醒目的数目,所以行数就是[self.toDoItems count]

5.改变tableView:numberOfRowsInSection数据源方法来返回适当的行数。

.-(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section

.

.{

.

.// Return the number of rows in the section.

.

.return[self.toDoItemscount];

.

.}

6.

7.

最后一个方法,tableView:cellForRowAtIndexPath:,请求一个表视图单元来显示给定的行。到现在为止,你只是用代码进行工作,但是表单元显示是你接口文件的一部分。幸运的是,Xcode可以在IB中非常容易的设计自定义表单元。第一个任务是设计你的表单元并且告诉表视图而并非使用静态内容,它将动态的使用表单元的属性。

配置表视图

1.打开故事板。

2.在大纲视图中选择表视图。

3.在表视图选中的情况下,在工具区打开属性检查器

4.在属性检查器中,改变表视图的Content 属性,从静态表单元(Static Cells)到动态原型(Dynamic Prototypes)。


IB让你的静态表单元全部配置为原形。原形表单元,顾名思义,是表单元被配置文本样式、颜色、图片、或者其他你想要的属性用来显示,但它们的数据是在运行的时候由数据源提供。数据源加载每个行的原形表单元并且配置他们在行中的显示。

加载当前的表单元,数据源需要知道它被叫什么,并且它的名字必须在故事板中配置。

当你设置原形表单元的名称时,你也配置另外的表单元的选择样式属性,它决定表单元在被用户选中的时候如何显示。设置表单元选择样式为None,所以表单元的项目将不会在用户选中的时候高亮显示。这是你想要的行为,想要你的表单元在用户在to-do list中点击项目来标记是否完成——在本教程下面的内容中实现——所呈现的样子。

配置原形表单元(prototype cell)

1.在表中选择第一个表视图单元。

2.在属性检查其中,定位标识字段和类型ListPrototypeCell。

3.在属性检查其中,定位Selection字段并选择None。


你也能改变更改原形表单元的字体或者其他属性。最基本的配置很容易,所以你将保存它。

下一步是告诉你的数据源通过tableView:cellForRowAtIndexPath:如何在给定的行上配置表单元。这个数据源方法通过表视图调用当它项要显示给定的行时。如果表视图只有少数几个行,那么所有行立即都显示在屏幕上。但是如果表视图有很多行但是在同一事件只能显示其中的某些部分。对于表视图要求哪些行需要被显示是非常有效的,这就是tableView:cellForRowAtIndexPath允许表视图做什么。

对于任何给定的表中的行,在待办事宜项数组中获取相应的条目,然后把项的名字设置到表单元的文本标签上。

在你的表中显示表单元

1.在工程导航其中选择XYZToDoListViewController.m.

2.找到tableView:cellForRowAtIndexPath:数据源方法。想如下显示那样实现:

.-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath

.

.{

.

.staticNSString*CellIdentifier=@"Cell";

.

.UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifierforIndexPath:indexPath];

.

.

.

.// Configure the cell...

.

.

.

.returncell;

.

.}

3.

4.

模板执行多个任务。它创建变量来保存表单元的标识,询问表视图带有标识的表单元,在关于设置表单元的地方添加注释,然后返回表单元。
想要使用这段代码,你需要在故事板中设置标识符,然后添加代码来配置表单元。

5.表单元标识符改变为故事板中你的设置。为了避免打字,从故事板中复制并粘贴到实现文件中。表单元标识符线现在看起来如下所示:

.staticNSString*CellIdentifier=@"ListPrototypeCell";

6.

7.

8.在返回语句之前,添加如下代码:

.XYZToDoItem*toDoItem=[self.toDoItemsobjectAtIndex:indexPath.row];

.

.cell.textLabel.text=toDoItem.itemName;

9.

10.

你的tableView:cellForRowAtIndexPath方法应该如下所示:


.-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath

.

.{

.

.staticNSString*CellIdentifier=@"ListPrototypeCell";

.

.UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifierforIndexPath:indexPath];

.

.XYZToDoItem*toDoItem=[self.toDoItemsobjectAtIndex:indexPath.row];

.

.cell.textLabel.text=toDoItem.itemName;

.

.returncell;

.

.}

.

Checkpoint:运行你的app。你添加在loadInitialData中的事项列表应该被显示在表视图的表单元上。

continue...