马上着手开发 iOS 应用程序 (五) - 使用视图控制器

重要:这是针对于正在开发中的API或技术的预备文档(预发布版本)。苹果提供这份文档的目的是帮助你按照文中描述的方式对技术的选择及界面的设计开发进行规划。这些信息有可能发生变化,因此根据本文档的软件开发应当基于最终版本的操作系统和文档进行测试。该文档的新版本或许会随着API或相关技术未来的发展而进行更新。

翻译自苹果官网:

https://developer.apple.com/library/prerelease/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson4.html#//apple_ref/doc/uid/TP40015214-CH6-SW1

本课中,继续构建食物界面并使用 image picker 往界面中添加照片。完成的 app 应该像这样:

[图片上传失败...(image-474146-1608214824102)]

学习目标:

在课程的最后,你将学会:

  • 理解视图控制器生命周期回调,如 viewDidLoad、viewWillAppear 和 viewDidApper
  • 在控制器间传递数据
  • 关闭视图控制器
  • 使用手势识别器
  • 基于 UIViewController 类层次结构预期对象功能
  • 使用 asset catalog 向项目中添加图片

理解视图控制器生命周期

到目前为止, app 只有单个场景, 相应的 UI 由单个控制器管理着。随着 app 越来越复杂,你会需要处理更多的场景,当在屏幕上加载这些场景需要管理视图的加载和卸载。

UIViewController 和它的子类伴随着一系列视图层次的方法。当控制器状态转换时候 iOS 自动调用这些方法。当创建控制器子类(例如 ViewController),它继承了 UIViewController 类的方法能让你为每个方法实现自定义的行为。理解这些方法什么时候调用很重要,这样可以在适当的时候设置或移除正在显示的视图。

[图片上传失败...(image-a2390b-1608214824102)]

UIViewController 方法像如下被调用:

  • viewDidLoad() - 在控制器内容视图(视图层次最顶层)从 storyboardy 中创建和加载时被调用。此方法用于初始设置。然而,由于视图可能因为资源有限而被清除,所以不能保证只被调用一次。
  • viewWillAppear() - 它总是在内容视图出现在屏幕前立刻被调用。
  • viewDidAppear() - 它在内容视图出现在屏幕后立即执行,在此方法中可以执行如获取数据或显示动画的操作。

看上面的状态转换图中,这三个方法都有对应的销毁方法。

回忆一下。之前已经在 ViewController 的 viewDidLoad() 方法中写了一些代码了:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Handle the text field’s user input through delegate callbacks.
    nameTextField.delegate = self
}

把控制器作为视图和数据模型间的通信管道,这种方式的 app 设计称为 MVC(模型 - 视图 - 控制器)。在此模式中,模型跟踪你的 app 数据,视图显示你的用户界面和 app 内容,而控制器管理视图并响应用户的行为,从数据模型中取出数据填充视图。MVC 是 iOS app 的核心设计模式。到目前为止, app 一直顺着 MVC 原理开发的。

在这之后的 app 开发中多多考虑使用它。是时候让你的基础界面上一个台阶了,为食物场景创建最后的布局。

添加食物照片

为完成食物场景界面,下一步,添加食物的照片。这就用到了 image View(UIImageView)这个显示图片的的控件。

添加 image view 到场景中
  1. 打开 Main.storyboard。

  2. 在实用工具区打开对象库(或者选择 View > Utilities > Show Object Library )

    [图片上传失败...(image-96895d-1608214824102)]

  3. 在对象库中的搜索框中输入 image view 迅速找到 Image View 对象。

  4. 从对象库拖动 Image View 到堆栈视图中按钮的下面。

    [图片上传失败...(image-c92db-1608214824102)]

  5. 选中 image view,在实用工具区中打开尺寸检查器。

    [图片上传失败...(image-9d5951-1608214824102)]

  6. 在 Intrinsic Size 区域,选择 Placeholder。(它在尺寸检查器的底部,需要滚动才能看到。)

  7. 宽度和高度区域都输入 320。回车确认。
    空的的 image view 没有固有内容尺寸。给它一个占位符尺寸这样就可以在界面中指定合适的约束了。

    [图片上传失败...(image-a19abf-1608214824102)]

  8. 在画板的底部,打开 Pin 菜单。

    [图片上传失败...(image-f6ea79-1608214824102)]

  9. 选择 Aspect Ratio 旁边的多选框。
    Pin 菜单应该像这样:

    [图片上传失败...(image-b5a925-1608214824102)]

  10. 在 Pin 菜单,点击 Add 1 Constraints 按钮;

    [图片上传失败...(image-9476df-1608214824102)]

  11. 选中 image view,打开属性检查器。

  12. 在属性检查器中,找到名叫 Interaction 的区域并选中 User Interaction Enabled 多选框。
    之后需要这个特性让用户与 image view 交互。

最后的 UI 应该像这样:

[图片上传失败...(image-6c30e4-1608214824102)]

显示默认的照片

用户需要指示来引导他们单击 image view 选择照片。通过添加默认的占位图片告诉用户可以选择照片。

[图片上传失败...(image-8c4bdd-1608214824102)]

在本课最终项目的 Images/ 文件夹中找到他们,或使用你自己的图片。

添加图片到你的项目中
  1. 在项目导航中,选择 Assets.xcassets 查看 asset catalog。
    asset catalog 是 app 中存放和组织图片资源的地方。

  2. 在左下角,点击 + 按钮并从弹出的菜单中选择 New Image Set。

    [图片上传失败...(image-bc56d4-1608214824102)]

  3. 双击 image set 修改名字为 defaultPhot。

  4. 从电脑中选择你想添加的图片。

  5. 拖动图片到 image set 的 2X 槽中。

    [图片上传失败...(image-8eb0ec-1608214824102)]

    2x 是 iPhone 6 模拟器显示的分辨率,在这个分辨率下图片看起来最好。

在 image view 中显示默认图片
  1. 打开 storyboard。
  2. 选中 image view。
  3. 选中后,在实用工具区打开属性检查器。
  4. 在属性检查器中,选择名为 Image 区域的 defaultPhoto。

检验:运行 app。默认的图片显示在 image view 中。

[图片上传失败...(image-ede098-1608214824102)]

连接 Image View 和代码

现在,需要实现必要的功能在运行时修改 image view 的图片。为了能在代码中修改图片,首先需要连接 image view 和 ViewController.swift 中的代码。

连接 image view 和 ViewController.swift 代码

  1. 点击 Xcode 右上角工具栏中 Assistant 按钮打开辅助编辑器。

    [图片上传失败...(image-222076-1608214824102)]

  2. 如果想要更多空间来工作,通过点击 Xcode 工具栏中的 Navigator 和 Utilities 按钮收缩项目导航和实用工具区。

    [图片上传失败...(image-e4ff4e-1608214824102)]

    同样可以收缩大纲视图。

  3. 选择 storyboard 中的 image view。

  4. 按住 Control 从画板的 image view 拖动到右边编辑器中的代码,在已经存在的 outlets 下面停止拖动。

    [图片上传失败...(image-e24e22-1608214824102)]

  5. 在弹出的对话框中,输入名字 photoImageView。
    忽略其他选项。对话框应该像这样:

    [图片上传失败...(image-7cd111-1608214824102)]

  6. 点击 Connect。
    Xcode 向 ViewController.swift 中添加必要的代码来存储 image view 的指针并配置 storyboard 设置这个连接。

     @IBOutlet weak var photoImageView: UIImageView!
    

虽然可以在代码中访问 image view 来修改它的图片,但你知道什么时候修改图片吗?你需要提供用户一种方式如单击 image view 来表达他们想要修改图片,定义一个动作当单击事件发生后修改图片。

views 和 controls(UIControl) 间有微妙的区别,controls 是能够响应用户动作的视图。control (UIControl) 是 UIView 的子类。实际上,在界面中已经使用了 views(labels, image views) 以及 controls(text fields, buttons)。

创建手势识别器

image view 不是 control 对象,所以无法像 button 这样的 control 对象一样响应用户输入。例如,你无法简单创建一个当用户点击 image view 时触发的动作方法。(如果尝试按住 Control 从 image view 往代码中拖动,你会发现 Connection 区域无法选择 Action。)

幸运的是,通过添加手势识别器让它具备和 control 一样的能力。手势识别器是关联到视图的对象允许视图像 control 一样响应动作。手势识别器中断交互来决定是否响应一个特殊的手势,如清扫,捏合或旋转。当手势识别器识别手势了相应动作方法执行,那正是你需要为 image view 做的。

给 image view 添加单击手势识别器(UITapGestureRecognizer),当用户点击 image view 手势会被识别。在 storyboard 中很简单就能实现这个功能。

给 image view 添加单击手势识别器
  1. 打开对象库。(选择 View > Utilities > Show Object Library 可以快速打开它)

  2. 在对象库的搜索框中输入 tap gesture 快速找到手势识别器对象。

  3. 从对象库中拖动手势识别器到场景中并把它放在 image view 的顶部。

    [图片上传失败...(image-4dce9c-1608214824102)]

    单击手势识别器会出现在场景 dock 中。

连接手势识别器和代码

连接手势识别器和 ViewController.swift 代码
  1. 按住 Control 从场景 dock 中的手势识别器拖动到右边编辑器的代码中,在 // MARK: Actions 注释行下面停止拖动。

    [图片上传失败...(image-605b56-1608214824102)]

  2. 在弹出的对话框的 Connection 区域选择 Action。

  3. Name 输入 selectImageFromPhotoLibrary。

  4. Type 选择 UITapGestureRecognizer。
    忽略其他选项,最后对话框应该像这样:

    [图片上传失败...(image-71864-1608214824102)]

  5. 点击 Connect。
    Xcode 向 ViewController.swift 添加必要的代码来设置这个 action(动作)。

     @IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
     }
    

创建 Image Picker 响应用户单击

当用户点击 image view,应该发生什么?据推测,用户应该能从照片集中选择一张照片或自己拍一张。幸运的是,UIImagePickerController 类内置支持这些操作。image picker 控制器为拍照和从 app 中选取照片都提供了基础界面。类似你需要使用 text field delegate 来处理文本框,你同样需要 delegate 来处理 image picker controller。这个代理协议就是 UIImagePickerControllerDelegate。

首先,ViewController 需要遵循 UIImagePickerControllerDelegate 协议。因为 ViewController 负责显示 image picker 控制器,所以它同样需要遵循 UINavigationControllerDelegate 协议,它只是简单的让 ViewController 承担一些基本的导航责任。

遵循 UIImagePickerControllerDelegate 和 UINavigationControllerDelegate 协议
  1. 点击 Standard 按钮返回标准编辑器。

    [图片上传失败...(image-5a60b4-1608214824102)]

    点击 Xcode 工具栏中的 Navigator 和 Utilities 按钮展开项目导航和实用工具区。

  2. 在项目导航中选择 ViewController.swift。

  3. 在 ViewController.swift 中,找到如下的 class 行。

     class ViewController: UIViewController, UITextFieldDelegate {
    
  4. 在 UIImagePickerControllerDelegate 后面添加 , 和 UIImagePickerControllerDelegate 来遵循这个协议。

     class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate {
    
  5. 在 UIImagePickerControllerDelegate 后面,添加 , 和 UINavigationControllerDelegate。

     class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
实现 selectImageFromPhotoLibrary(_:) 动作方法
  1. 在 ViewController.swift 中找到之前添加的 selectImageFromPhotoLibrary(_:) 动作方法。

     @IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
     }
    
  2. 在 {} 之间添加下行代码:

     // Hide the keyboard.
     nameTextField.resignFirstResponder()
    

    这行代码确保当用户在文本框输入的同时点击 image view,键盘会消失。

  3. 添加如下代码来创建 image picker 控制器:

     // UIImagePickerController is a view controller that lets a user pick media from their photo library.
     let imagePickerController = UIImagePickerController()
    
  4. 继续添加:

     // Only allow photos to be picked, not taken.
     imagePickerController.sourceType = .PhotoLibrary
    

    这一行,设置 image picker 控制器的数据源,就是图片来源。.PhotoLibrary 选项使用模拟器的照片册。
    sourceType 是 UIImagePickerControllerSourceType 枚举的类型。意味着可以使用缩写形式 .PhotoLibrary 取代 UIImagePickerControllerSourceType.PhotoLibrary。记住当枚举类型已知情况下可以使用缩写形式。

  5. 向 ViewController 中添加如下代码设置 image picker 控制器的代理:

     // Make sure ViewController is notified when the user picks an image.
     imagePickerController.delegate = self
    
  6. 继续添加代码:

     presentViewController(imagePickerController, animated: true, completion: nil)
    

    在 ViewController 中调用presentViewController(_:animated:completion:) 方法。尽管写的不是很明确,它有个隐式的前缀 self 对象。这个方法要求 ViewController 显示 imagePickerController 定义的视图控制器。给 animated 参数传递 true 让 image picker 控制器弹出的时候有动画。 compleation 参数是个 compleation handler,表示当方法执行完后执行的代码块。因为完成后不需要做什么事情,所以传递 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)
}

当 image picker 控制器打开了,它的行为就被转交给代理了。为了让用户能选择照片,需要你实现 UIImagePickerControllerDelegate 的两个代理方法:

func imagePickerControllerDidCancel(picker: UIImagePickerController)
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])

第一个 imagePickerControllerDidCancel(_:) 会在用户单击 image picker 的取消按钮时调用。此时你可以关掉 UIImagePickerController(或其他任何必要的清理)。

实现 imagePickerControllerDidCancel(_:) 方法
  1. // MARK: Actions 区域右上角,添加如下行:

     // MARK: UIImagePickerControllerDelegate
    

    此注释帮助你(或任何读你代码的人)浏览你的代码并识别哪个是 image picker 实现的代码区域。

  2. 在注释下方添加这个方法:

     func imagePickerControllerDidCancel(picker: UIImagePickerController) {
     }
    
  3. 在方法中添加如下代码行:

     // Dismiss the picker if the user canceled.
     dismissViewControllerAnimated(true, completion: nil)
    

    这行代码随着动画关掉 image picker 控制器。

最后的 imagePickerControllerDidCancel(_:) 方法如下:

func imagePickerControllerDidCancel(picker: UIImagePickerController) {
    // Dismiss the picker if the user canceled.
    dismissViewControllerAnimated(true, completion: nil)
}

第二个需要实现的 UIImagePickerControllerDelegate 的方法是 imagePickerController(_:didFinishPickingMediaWithInfo:),它会在用户选择照片的时候调用。这时获取选中的图片并显示在界面上。

实现 imagePickerController(_:didFinishPickingMediaWithInfo:) 方法
  1. 在 imagePickerControllerDidCancel(_:) 下面添加这个方法:

     func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    

    }

  2. 在方法中添加如下代码:

     // The info dictionary contains multiple representations of the image, and this uses the original.
     let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
    

    info 字典包含 picker 选中的原始图片,如果编辑后的图片存在的情况下也包含这张编辑的图片。简单起见,使用那张原始未编辑的图片来作为食物照片。将它存储到 selectedImage 常量中。

  3. 添加如下代码设置之前创建的 image view 图片。

     // Set photoImageView to display the selected image.
     photoImageView.image = selectedImage
    
  4. 添加如下代码关闭 image picker:

     // Dismiss the picker.
     dismissViewControllerAnimated(true, completion: nil)
    

最后的 imagePickerController(_:didFinishPickingMediaWithInfo) 方法应该像这样:

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)
}

检验:运行 app。此时应该能够点击 image view 打开 image picker。在弹出的授权对话框中点击 OK 授予 app 访问照片的权限。之后,点击 Cancel 按钮关闭 picker,或点击 Camera Roll(相机胶卷)选择一张照片作为 image view 的图片。

[图片上传失败...(image-d34285-1608214824102)]

你会注意到模拟器并不包含任何食物的照片。在项目文件的 Images/ 文件夹中找到示例图片并添加到模拟器。

添加图片到模拟器

  1. 如果需要,在模拟器中运行 app。

  2. 在电脑中选择想要添加的图片。

  3. 拖动图片到模拟器中。

    [图片上传失败...(image-b56cf3-1608214824102)]

    模拟器打开照片应用程序显示刚才添加的图片。

    [图片上传失败...(image-1d4f4-1608214824102)]

检验:运行 app。应该能够点击 image view 来打开 image picker。然后打开它的 Camera Roll(相机胶卷),选择刚才添加的图片来设置 image view 的图片。

[图片上传失败...(image-ca7556-1608214824102)]

注意:

下载完整示例项目并在 Xcode 中打开它。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容