Start Developing iOS Apps (Swift)->使用视图控制器(二)

创建手势识别器

image view不是控件,它没有被设计成像button或者slider那样对输入作出响应。例如,你不能简单的创建一个方法,让它能够在image view 被用户点击的时候被触发。(如果你在刚才拖拽image view的时候稍加留意,你会发现弹出对话框的Connection字段不能选择Action。)

幸运的是,只要通过添加手势识别器(gesture recognizers)就能让视图轻松的获得和控件一样的能力。手势识别器是你附加在视图上的对象,它可以让视图以控件的方式响应用户。手势识别器解释触摸,判断它们是否符合特殊的手势,例如滑动(swipe)、捏合(pinch)、或者旋转(rotation)。你能够写一个方法,当手势识别器识别到它被分配的手势时这个方法会被调用。这正是你想为image view做的。

附加一个轻拍(tap)手势识别器(UITapGestureRecognizer)给image view,它将识别用户对image view 的轻拍手势。在storyboard中你很容易做到这点。

添加轻拍手势识别器给你的image view

  1. 打开Object library
  2. 在Object library中,在过滤字段中输入tap gesture快速找到Tap Gesutre Recognizer 对象。
  3. 从Object library拖拽Tap Gesture Recognizer对象到你的场景,放到image view 的上面。


    image: ../Art/WWVC_gesturerecognizer_drag_2x.png

    Tap Gesture Recognizer对象出现在了菜品的场景dock中。


    image: ../Art/WWVC_scenedock_2x.png

连接手势识别器到代码

现在连接手势识别器到代码中的action方法。

连接手势识别器到ViewController.swift代码

  1. 按住Control键,从场景dock的手势识别器处拖拽一条线到右侧的编辑器的代码处,停在如图所示的位置。


    image: ../Art/WWVC_gesturerecognizer_dragaction_2x.png
  2. 在弹出的对话框中,在Connection字段选择Action。
  3. Name字段,填入selectImageFromPhotoLibrary。
  4. Type字段,选择UITapGestureRecognizer。
    你的对话框看起来像这样:


    image: ../Art/WWVC_gesturerecognizer_addaction_2x.png
    image: ../Art/WWVC_gesturerecognizer_addaction_2x.png
  5. 点击连接。
@IBAction func selectImageFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
}   

创建一个Image Picker来响应用户的点击

当用户点击image view的时候,他们应该可以从相册中选择一张照片,或者自己拍摄一张。幸运的是,UIImagePickerController类已经有了这些功能。一个image picker controller(图片拾取控制器) 管理用于拍摄照片和选择图片的用户界面,以便在你的应用中使用。就像你使用text field的时候需要text field delegate一样,你在使用image picker的时候也需要image picker controller delegate。这个委托协议的名字是UIImagePickerControllerDelegate,你要声明为image picker controller的委托的对象是ViewController。

首先,ViewController需要采用UIImagePickerControllerDelegate协议。由于ViewController将要承担显示image picker controller的责任,所以它也需要采用UINavigationControllerDelegate协议,这样就可以让ViewController承担一些基本的导航功能。

采用UIImagePickerControllerDelegate和UINavigationControllerDelegate协议

  1. 回到标准编辑器。


    image: ../Art/standard_toggle_2x.png
  2. 在project navigator,选择ViewController.swift。
  3. 在ViewController.swift中,找到class这行。
class ViewController: UIViewController, UITextFieldDelegate {
  1. 在UITextFieldDelegate后面,添加逗号和UIImagePickerControllerDelegate来采用这个协议。
class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate {
  1. 在UIImagePickerControllerDelegate后面,添加逗号和UINavigationControllerDelegate来采用这个协议。

现在,回到你定义的action方法,selectImageFromPhotoLibrary(_:),来完成它的实现。

实现名为selectImageFromPhotoLibrary(_:)的 action 方法

  1. 在ViewController.swift中,找到你之前添加的selectImageFromPhotoLibrary(_:)方法。
@IBAction func selectImageFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
}   
  1. 在这个方法的两个花括号({ })之间,加入如下代码:
// Hide the keyboard.
nameTextField.resignFirstResponder()

这个代码确保在text filed是第一响应者的时候用户点击了image view,键盘会消失(也就是text field注销了第一响应者)。This code ensures that if the user taps the image view while typing in the text field, the keyboard is dismissed properly.

  1. 添加下面这些代码来创建一个image picker controller。
// UIImagePickerController is a view controller that lets a user pick media from their photo library.
let imagePickerController = UIImagePickerController()   
  1. 添加代码。
// Only allow photos to be picked, not taken.
imagePickerController.sourceType = .photoLibrary

这行代码设置了image picker controller的源,或者说要从何处获取图片。这个.photoLibrary选项使用的是模拟器的相册。
imagePickerController.sourceType的类型是UIImagePickerControllerSourceType,它是一个枚举类型(enumeration)。这意味着你可以直接使用缩写形式.photoLibrary 来表示UIImagePickerControllerSourceType.photoLibrary。回想一下,只要知道是枚举值类型就可以采用这种缩写形式。

  1. 添加代码来设置image picker controller的委托为ViewController。
// Make sure ViewController is notified when the user picks an image.
imagePickerController.delegate = self

6紧接着,添加代码

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

present(_:animated:completion:)是一个在ViewController上调用的方法。虽然没有明确的写,但是这个方法是有一个隐式的self对象执行的。这个方法请求ViewController呈现由imagePickerController定义的一个视图控制器。把animated参数值设为true会以动画的方式来呈现image picker controller。completion参数是指完成处理程序(completion handle),是在这个方法执行完毕之后执行的一段代码。因为你还不需要做这些,所以把它设置为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
    present(imagePickerController, animated: true, completion: nil)
}

当image picker controller被呈现之后,你可以通过委托方法来和它互动。要想给用户选择照片的能力,你需要实现两个定义在UIImagePickerControllerDelegate:里的委托方法:

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

第一个方法,imagePickerControllerDidCancel(_:),会在用户点击图像选择器(image picker)的取消(Cancel)按钮的时候调用。这个方法可以让你有机会关闭UIImagePickerController(并且可以选择任何有必要的清理)。

实现imagePickerControllerDidCancel(_:)方法

  1. 在ViewController.swift中,在//MARK: Actions部分上面,添加:
//MARK: UIImagePickerControllerDelegate

这个注释帮助你导航到代码位置。

  1. 紧跟着注释,添加下面这个方法:
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
}
  1. 在这个方法中,输入下面代码:
// Dismiss the picker if the user canceled.
dismiss(animated: true, completion: nil)    

这个代码会带动画的移除image picker controller。

你的imagePickerControllerDidCancel(_:)方法看上去是这样的:

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

第二个方法需要实现的UIImagePickerControllerDelegate,imagePickerController(_:didFinishPickingMediaWithInfo:),在你选择照片的时候调用。这个方法让你有机会对从选择器来的图片做一些事。本例中,你将选择图片,然后显示在image view上。

实现imagePickerController(_:didFinishPickingMediaWithInfo:) 方法

  1. 在imagePickerControllerDidCancel(_:)方法下面添加方法:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
}       
  1. 在这个方法中添加如下代码:
// The info dictionary may contain multiple representations of the image. You want to use the original.
guard let selectedImage = info[UIImagePickerControllerOriginalImage] as? UIImage else {
        fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
}

这个info字典始终包含从拾取器获取的原图。它也可能包含一个原图的编辑版本,如果有的话。简单起见,你将使用没有修改的原图。
这段代码从info字典中访问原始未编辑的图片。它安全地解包由字典返回的可选对象,并把它转化为UIImage对象。期望是解包和转换都没有错误。如果有错,就相当于应用有bug,需要在设计的时候修补它。 fatalError()方法在控制台打印一个错误信息,包括info字典的内容,然后使应用终——防止应用继续处于无效状态。

  1. 添加下面这行代码,它把选中的图片设置到image view上。
// Set photoImageView to display the selected image.
photoImageView.image = selectedImage    
  1. 添加下面代码来移除image picker。
// Dismiss the picker.
dismiss(animated: true, completion: nil)    

你的imagePickerController(_:didFinishPickingMediaWithInfo)方法看上去是这样的:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
            
    // The info dictionary may contain multiple representations of the image. You want to use the original.
    guard let selectedImage = info[UIImagePickerControllerOriginalImage] as? UIImage else {
          fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
          }
            
    // Set photoImageView to display the selected image.
    photoImageView.image = selectedImage
            
    // Dismiss the picker.
    dismiss(animated: true, completion: nil)
}

检查点:运行应用。当你点击image view的时候发生了什么?

image: ../Art/WWVC_app_abort_2x.png

应用以SIGABRT信号终止。这意味着发生了一个足以导致应用终止的严重错误。在本例中,当你尝试显示image picker的时候这个问题会发生。系统在访问photo library之前必须询问用户是否允许。在iOS 10之后,你必须提供photo library使用描述。这个描述解释了你为什么想要访问photo library。

添加一个photo library使用描述

  1. 在project navigator,选择Info.plist。
    Xcode在编辑器区域显示属性列表(property list)。属性列表是一个结构化的文本文件,它包含了关于应用的必要配置信息。属性列表的根是一个字典,它保存一组预定义的键和它们的值。


    image: ../Art/WWVC_Property_List_Editor_2x.png

进一步探索
更多关于info.plist的信息,详见Information Property List Key Reference。

  1. 如果属性列表最后一个项目是数组,确保它是折叠的。如果你在一个展开的数组上添加项目,它会添加为一个子项目。如果你添加项目到折叠的数组,那么就会添加一个兄弟数组。
  2. 添加新项目,鼠标悬停在属性列表的最后一个项目上,当出现添加按钮(Add button)的时候,点击它(或者选择Editor > Add Item)。


    image: ../Art/WWVC_addInfoPlistItem_2x.png
  3. 在弹出的菜单中,滚动然后选择 Privacy - Photo Library Usage Description。
    image: ../Art/WWVC_addphotolibrarydescription_2x.png
  4. 在新行中,确保Type (类型)设置的是String。然后,双击值区域,并键入Allows you to add photos to your meals.


    image: ../Art/WWVC_addingDescriptionString_2x.png
  5. 当你输入完描述文件后,按下回车键。

检查点:再次运行应用。这次你应该能够点击image view来显示一个image picker。你需要在弹出的警告框上点击OK,这个警告框询问是否给FoodTracker应用访问Photos的许可。然后,你可以点击Cancel 按钮来移除picker,或者打开相册然后点击一张图片让它显示在image view上。

image: ../Art/WWVC_sim_imagepicker_2x.png

如果你看遍了模拟器中照片,你会发现它没有食品的照片。你可以直接添加自己的图片到模拟器,以便使用合适的内容来测试FoodTracker应用。你可以在下载文件的Images文件夹里找到图片,下载地址在本课最后,或者使用你自己的图片。

添加图片到iOS模拟器

  1. 如有必要,在模拟器中运行应用。
  2. 在你的电脑中,选择你想要添加的图片。
  3. 拖拽图片到模拟器。


    image: ../Art/WWVC_sim_dragphoto_2x.png

模拟器打开Photos应用,并且显示你添加的图片。

image: ../Art/WWVC_sim_choosephoto_2x.png

检查点:运行应用。你应该能轻拍image view来显示一个image picker。打开相册,点击你添加到模拟器中的图片,选择它来设置iamge view的图片。

image: ../Art/WWVC_sim_selectedphoto_2x.png

小结

在本课中,你学到了关于视图控制器生命周期方法,并且使用它们配置了你的视图控制器内容。你也学习了如何给视图添加手势识别器,并且知道如何从photo library中选择照片。场景开始看上去像一个真实的应用了。在下一课中,你将添加自定义的控件到场景。

注意
想看本课的完整代码,下载这个文件并在Xcode中打开。

下载文件

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

推荐阅读更多精彩内容