和View Controllers一起工作
在这一课中,你会继续在FoodTracker菜谱的场景工作。你会重新安排现有的UI元素并使用图像采集器添加到照片用户界面。当你完成,你的应用程序将是这个样子:
学习目标
在课程结束时,你将能够:
了解视图控制器的生命周期,如viewDidLoad,viewWillAppear和viewDidAppear这些回调函数
在视图控制器之间传递数据
让一个视图控制器消失
使用手势识别生成一个事件
预测的基于UIView/ UIControl类层次的对象行为
使用asset目录,添加图像资源到项目中
理解视图控制器的生命周期
到目前为止,FoodTracker应用程序有一个单一的场景,其用户界面是由一个单一视图控制器管理。为了打造更复杂的应用程序,你会要处理更多的场景,并需要管理载入和卸载在屏幕上来来去去的Views
UIViewController类(及其子类)的对象提供了一组方法,这些方法管理其视图层次结构。当一个视图控制器的状态在转变时,iOS会在适当的时间自动调用这些方法。当你创建一个视图控制器子类,它继承了的UIViewController中定义的方法,让你为每种方法添加自己的自定义行。重要的是要了解,当这些方法被调用,这样你就可以在适当的步骤下设置或拆卸你正在展示的Views,将来你会需要做这些事情。这里可以和Android中的Activity的生命周期做对比
UIViewController的方法调用如下:
viewDidLoad()----当视图控制器的内容视图(顶层视图)被创建并且从一个storyboard载入后调用。这个方法适用于初始化设置,然而,因为由于APP中的资源限制可能会让views被清除,不能保证这个方法只被调用一次
viewWillAppear()---在view可见之前发生的行为。因为一个view的可见度能开关或被其他Views遮蔽,在内容视图出现在屏幕之前,这个方法会立即调用
viewDidAppear()---用于view一可见你就想要发生的操作,如获取数据或显示动画。因为一个view的可见度能开关或被其他Views遮蔽,所以这个方法在内容视图出现在屏幕后会立即调用
另外与之对应的还有一套对立的方法存在,如上面状态图所示。
你会在FoodTracker APP中适当的时间内使用一些方法来载入和显示view数据。实际上,如果你有印象,我们已经写过viewDidLoad()方法中的一些处理
override func viewDidLoad() { super.viewDidLoad() // Handle the text field’s user input through delegate callbacks. nameTextField.delegate = self }
在你的views和数据模型之间,视图控制器作为通信管道, APP这种设计风格被称为MVC(模型-视图-控制器)。在这个模式中,模型跟踪你的app数据,视图显示你的用户界面,并组成一个内容,控制器管理你的views。通过相应用户的动作并且从数据模型得来的内容以填充view。对于任意iOS app来说MVC是一个至关重要的好的设计,到目前为止,FoodTracker一直沿用MVC规则构建。在app剩余的设计中,在头脑里保持MVC模式,是时候把基础的UI带到下一水平了,我们会为菜谱场景创建最终的布局
添加一张菜谱的图片
接下来的是完成菜谱场景的UI,我们添加一个方法来显示一张具体的菜谱照片。为此,我们将使用Image View(弄过android的是不是很熟悉?),这里iOS中使用的是UIImageView类,这是一个UI元素用来显示一张图片,步骤如下:
1.打开storyboard,Main.stroyboard
2.打开实用区域的对象库(也可以选择View > Utilities > Show Object Library)
3.在对象库中,输入image view来快速过滤出你想要的对象
4.拖动Image View到场景中,在我们先前按钮控件的下方
5.选中ImageView,打开Size inspector,你可以调整ImageView的大小和位置
6.在下方的Intrinsic Size字段,选择Placeholder
7.在上方View和下方的Placeholder中都Width和Height中输入320,然后按下Return,因为一个空的iamge view没有内在大小,你给定image view的placeholder大小,然后可以在界面中指定适当的约束
8.在画布的底部右边,点击Pin按钮
9.选择Aspect Ratio复选框,如下所示:
10.在Pin菜单中,点击Add 1 Constraints
你的图片现在是1:1的长宽比,所以他看起来会是个正方形
11.选择image view,打开Attributes inspector
12.在Attributes inspector中,找到Mode然后点击下拉列表,选择Aspect Fill
这个选项有助于确保不同尺寸的图像在image view中不会失真。
13.在Attributes inspector中,找到Interaction ,然后选中User Interaction Enabled复选框
稍后,你将需要此功能,让用户与image view交互。现在的UI应该如下所示:
显示一个默认照片
用户需要一个指示,让他们知道可以和image view交互,并能选择一张照片。要做到这一点,需要添加一个默认placeholder图像,来传达给用户,他们能选择一个照片
你可以点击这里下载我们这章所需要的图片。下面让我们来添加图片到项目中
1.在项目导航中,选择Assets.xcassets(不同Xcode版本,名字可能不一样,有的也许是Images.xcassets)进入asset目录,这个目录是存储你图片的地方
2.在顶部角落,点击+按钮并选择New Image Set
3.然后在出现的Image上,双击,重命名为defaultPhoto
4.在你的电脑中,找到这个图片。
5.然后拖动到x2位置出,放下
2x是 iPhone 6模拟器下的分辨率,图片放在这个位置最佳
接下来我们要在image view中显示默认图片
a.打开你的storyboard
b.在storyboard中选择image view
c.选中image view的情况下打开Attributes inspector
d.在Attributes inspector中找到Image字段,然后选择defaultPhoto
运行你的APP,效果如下:
将Image View连接到代码
现在,你需要实现必要的功能,来在运行时改变图片。你希望能从代码中改变图片,为此,你首先需要在ViewController.swift中连接image view到代码:
1.点击Assistant打开assistant editor
2.如果你想要更多的工作空间,你可以折叠 project navigator和utility area
3.在storyboard中,选择image view
4.按住Control键拖动image view画布到右边的代码编辑器中,选择outlets,如下图:
5.在弹出的对话框中,Name:photoImageView:
6.点击Connect。Xcode会添加必要的代码到ViewController.swift中
你现在能访问iamge view了,从代码上来改变图像,但你如何知道何时改变图片?你需要给用户一个方式来告诉他们可以改变图片。例如,点击image view。然后你要定义一个动作方法来处理点击事件。
views和controls之间有微妙的区别,view有一个特定的版本来响应用户的动作。一个视图显示内容,然而control会以某种方式来修改它,一个control(UIControl)是UIView的子类。实际上,你已经使用过了,例如Views(标签,image view),controls(文本框,按钮)
创建一个手势识别
一个image view不是一个控制,所以它不会像按钮那样被设计为能响应输入。例如,当一个用户点击image view时你不能简单的创建一个动作方法来触发一个事件。(如果你试图按住Control键拖动iamge view到你的代码中,你会发现,在Connection字段后面,它不能选择Action)
幸运的是,它可以很容易的给定一个view添加一个手势识别,作为和控制的能力。手势识别是一个可以附加到view上的对象,view可以响应动作。手势识别可以解释触摸来确定它们是否对应于特定的手势,诸如滑动,挤压或旋转。你能写一个动作方法,用来处理当手势识别发现它对应的手势时,这正是你需要为image view做的
我们可以附加一个点击手势识别(UITapGestureRecognizer)到image view中,当一个用户轻击image view时,它将会识别。你能在storyboard轻松搞定这件事。步骤如下:
1.打开Object library
2.输入tap gesture快速过滤出我们想要的识别事件对象
3.拖动Tap Gesture Recognizer到场景中,放在 image view的上方一点
这个Tap Gesture Recognizer会出现在菜谱的场景Dock中
将手势识别连接到代码中
现在连接手势识别到一个动作方法中。步骤如下:
1.按住Control键拖动手势识别到代码编辑器,然后在//MARK:Action放下
2.在弹出的对话框中,Connection字段旁,选择Action
3.Name字段旁,输入selectImageFromPhotoLibrary
4.Type旁,选择UITapGestureRecognizer
5.点击Connect或按下Return
创建一个图片选择器来响应用户的轻击事件
那么什么时候用户会轻击image view,据推测,用户应该能用照片集中选择一个照片。幸运的是,UIImagePickerController已经把这种行为内置到里面了。一个图片选择器控制者管理一个UI,用于拍照并选择保存图像。正如你在文本框工作时需要一个文本框委托一样,你需要一个图片选择控制器委托来帮助你图片选择控制器的工作。这个委托的是一个协议(UIImagePickerControllerDelegate),你在ViewController定义这个图片选择控制器委托对象。首先
ViewController需要采用
UIImagePickerControllerDelegate协议。因为ViewController将掌管图片选择控制器的展示,同时我们还需要采用UINavigationControllerDelegate协议,该协议只是让ViewController获取一些基本的导航职责。接下来让我们添加这两个协议:
1.返回standard editor
2.在项目导航中,选择ViewController.swift
3.在ViewController.swift中,找到class这行:
class ViewController: UIViewController, UITextFieldDelegate {
4.然后添加如下代码:
class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
接下来我们需要实现selectImageFromPhotoLibrary()方法了
a .在ViewController.swift中找到selectImageFromPhotoLibrary()动作方法
@IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
}
b.在方法体中,添加如下代码:
// Hide the keyboard. nameTextField.resignFirstResponder()
该方法是确保,用户在点击image view时,如果软键盘正输入打开状态,需要关闭它
c.添加代码,来创建一个图片选择控制器
// UIImagePickerController is a view controller that lets a user pick media from their photo library. let imagePickerController = UIImagePickerController()
d.然后添加这行代码:
// Only allow photos to be picked, not taken. imagePickerController.sourceType = .PhotoLibrary
这行代码是设置图片选择控制器的源,或者是它获取图片的地方。.PhotoLibrary选项表示使用模拟器的相机胶卷,这个imagePickerController.sourceType是UIImagePickerControllerSourceType中的一个类型。它是一个枚举。意思是你能写缩写形式.PhotoLibrary来代替UIImagePickerControllerSourceType.PhotoLibrary
e.添加如下代码设置图片选择控制器的委托为ViewController:
// Make sure ViewController is notified when the user picks an image. imagePickerController.delegate = self
f.最后加上这一行
presentViewController(imagePickerController, animated: true, completion: nil)
presentViewController(_:animated:completion:)是ViewController中调用的方法,虽然这个方法没有显式的写入,但方法会在隐式的self对象上执行。改方法询问
ViewController
to出示imagePickerController定义的视图控制器,animated为true表示图像选择控制器是否以动画呈现。
completion表示当此方法执行完后,执行一个关包。因为不需要做任何事,所以这个参数传nil即可。你完整的selectImageFromPhotoLibrary()方法应该如下所示:
@IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) { // Hide the keyboard. nameTextField.resignFirstResponder() // UIImagePickerController is a view controller that lets a user pick media from their photo library. let imagePickerController = UIImagePickerController() // Only allow photos to be picked, not taken. imagePickerController.sourceType = .PhotoLibrary // Make sure ViewController is notified when the user picks an image. imagePickerController.delegate = self presentViewController(imagePickerController, animated: true, completion: nil) }
在图片选择控制器展现完后,它的行为会交给委托。给用户选择图片的能力,你需要实现UIImagePickerControllerDelegate中的两个委托方法
func imagePickerControllerDidCancel(picker: UIImagePickerController)
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])
其中第一项,imagePickerControllerDidCancel(_:),让你得到当用户点击图片选择器的取消按钮的调用。这种方法给你一个机会让UIImagePickerController消失。实现如下:
func imagePickerControllerDidCancel(picker: UIImagePickerController) { // Dismiss the picker if the user canceled. dismissViewControllerAnimated(true, completion: nil) }
第二项,imagePickerController,当用户选择一个照片时调用。这种方法可以让你有机会在用户从选择器中选择一个或多个图像时做些什么。在本例中,我们会采取选定的图像,并在UI中显示。实现如下
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { // The info dictionary contains multiple representations of the image, and this uses the original. let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage // Set photoImageView to display the selected image. photoImageView.image = selectedImage // Dismiss the picker. dismissViewControllerAnimated(true, completion: nil) }
第一行代码表示info字典包含你选中原始图像,如果存在,是已编辑的图片版本。为了简单,我们将使用原始的,未经编辑的图片作为菜谱照片。我们把这个照片存储在selectedImage常量中。
第二行代码表示我们给前面的场景中的image view设置它的image
第三行代码当然是让图片选择器消失啦
检查点:运行你的应用程序。你应该可以点击Image View 查看图像选择器。系统会有一个权限警告,询问许可给FoodTracker应用程序访问照片,点击OK即可。然后,您可以点击取消按钮关闭选择器,或打开相机胶卷并单击图像来选择它,并在Image View中显示
如果你通过在模拟器中发现没有任何照片的话。你可以直接添加自己的图像到模拟器来测试。前面Image View中的默认图文件夹中,附带了一张菜谱的图片。你把图片直接从电脑上拖入模拟器即可