在本课程中,您将创建应用程序FoodTracker的主屏幕。您将创建第二个,表视图为主场景,列出了用户的菜谱。你会设计定制表格单元格显示每一个菜谱,它是这样的:
学习目标
在课程结束时,你将能够:
创建第二个storyboard 场景
了解table view这个关键组件
创建和设计自定义table view单元格
了解table view委托和数据源
使用数组来存储和处理数据
在table view中显示动态数据
创建一个场景
到目前为止,FoodTracker有一个单一的场景通过View Controller来管理一个屏幕,它能添加和评级一个新的菜谱:这个菜谱场景。现在是时候创建一整个菜谱的列表。幸运的是,iOS有一个强大的内置类叫table view(UITableView)用来显示具体的滚动列表.
一个table view通过UITableViewController来管,它是UIViewControllrt的子类,用来处理table view相关的逻辑。你将基于一个table view controller创建新的场景。
添加一个table view场景到storyboard
1.打开storyboard,Main.storyboard
2.打开Object library
3.在Object library中,找到一个Table View Controller对象
4.拖动Table View Controller到画布中
请注意不要混淆table view和table view controller,请选择table view controller
你现在有两个场景,一个用来显示菜谱列表,一个用来显示新的菜谱。用户启动app时,首先看到的是菜谱列表,所以告诉Xcode你的table view controller作为第一个场景。
设置table view controller作为初始场景
1.从原先的菜谱场景中,拖动storyboard入口点到table view controller
table view controller作为在storyboard中的初始view,使它成为app启动后的第一个场景。
检查站:执行你的app。你现在是能看到一个空的table view。有一些水平的分割线来分隔成行,但里面没有内容
接着,你需要改变一些table view的设置。
配置table view
1.在storyboard中,打开outline view
2.在outline view中,选中Table View
table view嵌套在Table View Controller Scene>Table View Controller中。您可能需要点击旁边的三角形来显示嵌套视图。
3.选中table view的情况下,打开Size inspector
4.在Size inspector中,找到Row Height标签,然后输入90,按下Return
你将返回到工作,接下来我们需要设置table view cell的界面
设计自定义Table View Cells
这table view中的每一行,通过UITableViewCell来管理,用来响应绘制它们的内容。 Table view cells伴随各种预定义的行为和默认cell风格。默认cell风格大多数情况下适用,但你有更多的内容,需要显示类每一个cell中,你需要自定义cell的风格,这个类似android的listvew中的item。
创建UITableViewCell的子类
1.选择File > New > File (或按Command-N)
2.在对话框的左边,选择iOS下的Source
3.选择Cocoa Touch Class,点击Next
4.在Class标签旁,输入Meal
5.在Subclass of标签旁,选择UITableViewCell
6.确保语言为Swift
7.点击Next
确保app已选,tests未选择
8.点击Create
Xcode会创建一个文件MealTableViewCell.swift
现在打开你的storyboard
如果你看到table view controller场景,你注意到它仅显示一个单一的cell
这个cell是其他cells的原型,你只要设计和给定这个cell的行为,那么所有的table view中的cell都会一样。但首先,你需要做一些配置。连接table view cell到int刚创建的自定义cell子类
为你的table view配置一个自定义的cell
1.在outline view中, 选择able View Cell
cell嵌套在Table View Controller Scene > Table View Controller > Table View下。
2.选中table view cell的情况下,打开Attributes inspector
3.在Attributes inspector中,找到Identifier标签,然后输入MealTableViewCell,按下return
4.在Attributes inspector中,找到Selection标签,选后选择None。
当你选择这个时,表示用户点击cell时,不会高亮(highlight)
5.打开 Size inspector
6.在 Size inspector中,找到Row Height标签,输入93,按下回车。
确保Custom复选框被选中
7.打开Identity inspector。
8.在Identity inspector中,找到标签Class然后选择MealTableViewCell
cell配置完毕,接下来我们要在storyboard中设计它的自定义UI。它包含菜谱的名称,照片,评级,看起来如下图:
所以,我们需要一个label,image view,一个rating control这三个UI控件。这些控件我们都是在前面章节用过的。
设计自定义table cell的界面
1.选择 Editor > Canvas > Show Bounds Rectangles来显示UI元素边界,在table cell中使元素更容易对齐
2.在Object library中,找到Image View对象,然后拖动到table cell中
3.拖动和调整image view,让它变成一个方形,使其cell的左部,顶部,底部平齐。
4.确认添加先前章节的默认图片是否在项目中,如果没有你需要重新添加进来。
5.选中image view的情况下,打开Attributes inspector
6.在Attributes inspector中,找到标签Image选择defaultPhoto
7.在Object library找到label对象,并拖动到table cell中
8.拖动lable,让它靠经image view的右边,并且让顶部边距对齐
9.调整label的大小,让右边距延伸到table cell的右边距
10.在Object library中,找到View对象,并拖动到table cell中
11.选择View的情况下,打开 Size inspector
12.在Size inspector中,输入44的Height和240的Width。按下return
13.拖动View到cell中,让它位于标签的底部,并和标签左边对齐
14.选中View的情况下,打开Identity inspector
15.在Identity inspector中,找到Class标签,并选择RatingControl
16.在View选中的情况下,打开Attributes inspector
17.Attributes inspector找到标签Interaction,然后在User Interaction Enabled取消选中
对于我们先前设计的rating control类是可以交互的,但由于此场景中,我们只用来显示,所以不允许交互。根据使用的场景不同,我们需要做出一些响应的调整。
现在我们的UI看起来如下:
检查站:运行我们的App。table view cells现在看起来很高。即使我们添加了UI元素到table view cells,为什么他们还是空的?
在storyboard中,一个table view用于配置和显示静态数据(storyboard中提供的)或动态数据(table view controller逻辑代码中)。table view默认使用动态数据,所以我们在storyboard配置的静态数据,是显示不出来的。直到我们实现了它背后的数据模型。接下来我们需要使用assistant editor了。
预览界面
1.在Xcode工具栏上点击assistant editor
2.如果你想要更多工作空间,可以展开项目导航和实用工具区域
3.在 editor selector bar,从Automatic切换到Preview > Main.storyboard (Preview),接下来你的Xcode窗口应该看起来如下图:
如果你发现了不对劲,请确保你选择的是 table view场景
添加Images到你的项目
接下来我们需要添加简单的图片到项目中。当载入初始化菜谱数据到你的app中时,我们会使用这些图像。你能找到简单的Images,可以点击这里下载或使用你自己的图片
添加图像
1.返回到 standard editor
2.选择Images.xcassets
3.在底部左下角,点击(+)号,然后从弹出的菜单中选中New Folder
4.双击文件夹,重命名为Sample Images
5.选择文件夹的情况下,底部左下角,点击(+)号,然后选择New Image Set
6.双击这个图片,重命名为你记得的名字
7.在电脑中,选择你想要拖动的图片
8.拖动在2x槽中。
重复5-8步骤,你可以添加很多图片,只要你喜欢,本例中只添加三张图片
连接Table Cell UI到代码中
在table view cells显示动态数据之前,在 MealTableViewCell.swift中
你需要在views和代码之间创建一个outlet连接,用来展示table view cell
连接views到MealTableViewCell.swift代码中
1.在storyboard中,选中table view cell中的标签
2.打开assistant editor
3.如果你想要更多的工作空间,可以展开项目导航和实用区域
4.在e editor selector bar中,从Preview切换到Automatic > MealTableViewCell.swift
5.在MealTableViewCell.swift中。找到class这行:
class MealTableViewCell: UITableViewCell {
6.在class下,添加如下代码:
// MARK: Properties
7.拖动画布中的标签到代码中,在刚添加的注释下方,松开:
8.在出现的对话框中,Name标签旁,键入nameLabel
9.点击Connect
10.在storyboard中,选择table view cell下的Image View
11.从画布中拖动Image View到代码中,然后在MealTableViewCell.swift中nameLabel属性下方松开
12.在出现的对话框中,的Name标签旁边,键入photoImageView,然后Connect
13.打开storyboard,选择table view cell中的rating control
14.拖动rating control到代码中,在photoImageView
属性下方松开
15.在对话框中的Name标签旁边,输入ratingControl,然后点击Connect
在MealTableViewCell.swift中,你的outlets,如下:
@IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var photoImageView: UIImageView! @IBOutlet weak var ratingControl: RatingControl!
载入初始化数据
为了显示table cells中真实的数据,你需要写代码来载入这个数据。在这一点上,你有一个菜谱的数据模型:Meal类。你需要一个菜谱列表的数据。view controller将管理显示的菜谱列表view,在显示UI之前,需要一个数据模型的引用。首先创建一个custom table view controller的子类来管理菜谱列表场景。
创建UITableViewController的子类
1.选择 File > New > File(或按下Command+N)
2.在弹出的对话框中,选择iOS下的Source,然后选择Cocoa Touch Class
3.点击Next
4.在Class标签旁,输入Meal
5.在Subcalss of旁,选择UITableViewController
6.确保Also create XIB file选项未勾选
7.确保语言为Swift
8.点击Next
9.点击Create
Xcode会创建一个MealTableViewController.swift类
在自定义的子类中,你现在能自定义属性来存储Meal对象列表。Swift标准库中,包含了Array这个结构体(structure)。这里我们需要用到它
载入初始化数据
1.返回到standard editor
2.打开 MealTableViewController.swift
3.在MealTableViewController.swift中的class这行下方添加如下代码:
// MARK: Properties var meals = [Meal]()
这个代码是声明一个属性并初始化默认值为一个空的Meal对象数组(array)。使用var表示数据是可变的,因为我们会改变这个数据中的元素。
4.在MealTableViewController.swift中,viewDidLoad()方法下方,添加如下代码:
func loadSampleMeals() {
}
这是一个辅助方法用来来加载样本数据到app中。
5.在loadSampleMeals()方法中,添加以下代码,来创建一些Meal对象,你能命名和评级这些菜谱样本,下面是一些例子代码:
let photo1 = UIImage(named: "meal1.jpg")! let meal1 = Meal(name: "Caprese Salad", photo: photo1, rating: 4)! let photo2 = UIImage(named: "meal2.jpg")! let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5)! let photo3 = UIImage(named: "meal3.jpg")! let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3)!
确保图像的名字和你项目中的名字匹配
6.在创建Meal对象后,添加它们到meals数组中
meals += [meal1, meal2, meal3]
7.找到 viewDidLoad()方法,模版代码实现如下:
override func viewDidLoad() { super.viewDidLoad() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem() }
这个方法的模版实现,包含了注释。这样的代码注释提供有用的提示和源代码文件的上下文信息,但本例中我们不需要这些注释
8.在viewDidLoad()方法中,删除注释,在super.viewDidLoad()下方添加以下代码来载入样本菜谱数据:
// Load the sample data. loadSampleMeals()
这个方法中,当view载入时,我们需要载入数据。我们写一个载入方法,使代码更加模块化和可读性。
我们的viewDidLoad()方法看起来如下:
override func viewDidLoad() { super.viewDidLoad() // Load the sample data. loadSampleMeals() }
我们的loadSampleMeals()方法,看起来如下:
func loadSampleMeals() { let photo1 = UIImage(named: "meal1.jpg")! let meal1 = Meal(name: "Caprese Salad", photo: photo1, rating: 4)! let photo2 = UIImage(named: "meal2.jpg")! let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5)! let photo3 = UIImage(named: "meal3.jpg")! let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3)! meals += [meal1, meal2, meal3] }
显示数据
在这一点上,我们自定义的table view controller子类MealTableViewController,有一个可变的数组,里面预置一些样本菜谱,现在你需要在UI中显示数据
为了显示动态数据,一个table view需要两个重要的帮助:1个数据源和一个委托。table view数据源,就如它的名字一样,用来提供table view需要显示的数据。一个table view委托帮助table view管理cell选择,行高,以及显示数据的其他方法。默认UITableViewController和他的子类采用必要的协议来使table view controller的子类,既是一个数据源(UITableViewDataSource协议)又是一个委托(
UITableViewDelegate协议
)。你的工作就是实现相应的协议方法,来让你的table view有正确的行为。
一个table view功能需求两个table view数据源方法
func numberOfSectionsInTableView(tableView: UITableView) -> Int func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
numberOfSectionsInTableView()方法是告诉table view显示多少部件。部件是table views中可见的一组cells,但table view中有大量数据时,特别有用。但本例中实现起来非常简单。
显示table view中的部件
1.在MealTableViewController.swift中,找到numberOfSectionsInTableView()数据源方法。模版实现如下:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections return 0 }
2.改变返回值为1,并移除注释
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
上面的代码,使table view显示一个部件,而不是0。
下面的数据源方法tableView(_:numberOfRowsInSection:),告诉table view中的一个部件显示显示多少行。一个table view中默认有一个单一的部件,每一个菜谱在部件中都占一行。意思就是数组中有多少的Meal对象就会显示多少行。
在table view中返回行数
1.在 MealListTableViewController.swift中,找到tableView(_:numberOfRowsInSection:)数据源方法。模版实现如下:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return 0 }
你想要返回菜谱的数量,可以使用Array中count属性。
2.改变tableView(_:numberOfRowsInSection:)数据源方法来返回合适的函数,并移除警告注释
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return meals.count }
最后的数据源方法tableView(_:cellForRowAtIndexPath:),用来配置和提供一个cell来显示给定的行数。每一行都有一个cell,该cell确定内容和布局
对于table views只有小部分行数的情况,所有行数可能只在屏幕上显示一次。所以这个方法在 table view中的每行调用。但如果table view有很多很多行需要显示,那么在给定的时间内,只显示总数的一小部分。
对于在table view任意给定的行数,你通过获取相应的meals数组中的Meal来配置cell,然后设置cell的属性和Meal类中相应的值
配置和显示cells
1.在 MealListTableViewController.swift中,找到并取消tableView(_:cellForRowAtIndexPath:)数据源函数的注释。模版方法实现如下:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as! UITableViewCell // Configure the cell... return cell }
模版执行了几个任务。它通过placeholder的标识符访问table view中的cell ,添加注释 接下来我们需要配置cell,然后return cell。为了确保你的app正常工作,你需要改变placeholder标识符,到你早期在MealTableViewCell中设置的cell原型,然后添加代码类配置cell
2.继续添加以下代码到方法中
// Table view cells are reused and should be dequeued using a cell identifier. let cellIdentifier = "MealTableViewCell"
这是创建一个标识符常量,就是你在storyboard设置的那个
3.更新placeholder标识符到你在storyboard设置的标识符。我们应该修改上述方法中第二行的代码:
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! UITableViewCell
4.因为你已经创建了一个自定义的cell类,改变cell的类型为你自定义的cell子类,把UITableViewCell改成MealTableViewCell,继续修改方法的第二行代码
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealTableViewCell
5.接着在下面添加如下代码
// Fetches the appropriate meal for the data source layout. let meal = meals[indexPath.row]
这行代码用来获取合适的meal
6.删除// Configure the cell注释,添加如下代码:
cell.nameLabel.text = meal.name cell.photoImageView.image = meal.photo cell.ratingControl.rating = meal.rating
为每一个UI元素赋值
最终tableView(_:cellForRowAtIndexPath:)函数看起来如下:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // Table view cells are reused and should be dequeued using a cell identifier. let cellIdentifier = "MealTableViewCell" let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealTableViewCell // Fetches the appropriate meal for the data source layout. let meal = meals[indexPath.row] cell.nameLabel.text = meal.name cell.photoImageView.image = meal.photo cell.ratingControl.rating = meal.rating return cell }
最后一步是在UI中显示数据,并连接定义在MealTableViewController.swift中的代码到storyboard中的菜谱列表场景
table view controller场景指向MealTableViewController.swift
1.打开你的storyboard
2.选择table view controller(可以用过scene dock选择)
3.打开Identity inspector
4.在Identity inspector中,在Class标签旁,选择MealTableViewController
检查站:运行你的应用程序。你会发现 table view cells 和状态栏有一些重叠,我们将在下一章修复
准备Meal场景的导航
当你准备实现应用程序FoodTracker的导航时,你需要删除和替换一些代码和UI,因为你将不再需要。
清理项目中未使用的部分
1.打开你的storyboard,然后看着meal场景,你的场景应该和下面差不多:
2.在meal场景,选中Meal Nam这个标签
,然后按下Delete键删除它
3.打开ViewController.swift
4.在ViewController.swift
, 找到textFieldDidEndEditing(_:)方法
func textFieldDidEndEditing(textField: UITextField) { mealNameLabel.text = textField.text }
5.删除文本属性设置代码
mealNameLabel.text = textField.text
很快你将替换新的实现
6.在ViewController.swift
, 找到mealNameLabel
outlet并删除它
@IBOutlet weak var mealNameLabel: UILabel!
因为你现在有2个view controllers,我们需要为ViewController.swift取一个更有意义的名字
重命名 ViewController.swift文件
1.在项目导航中,选择ViewController.swift,然后按下回车
Xcode会让你输入一个新的名字
2.重命名这个文件为MealViewController.swift。然后按下Return
3.在 MealViewController.swift,找到类声明
class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
4.改变类名为MealViewController
class MealViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
5.在文件顶部的注释中,把以前的ViewController.swift
修改名字为MealViewController.swift
6.打开storyboard
7.选择meal场景
8.在选中meal场景的情况下,打开Identity inspector
9.在 Identity inspector中,找到Class标签,把ViewController修改为MealViewController
检查站:运行你的应用程序。一切都应该像以前一样工作。你可能会在Xcode中看到警告,说没有方法到达meal场景,不要担心我们将在下一章讲解