第5章 设置注册页面的用户头像
本章我们要实现注册页面的用户头像设置功能,当用户在注册页面单击顶部的Image View时,会弹出照片选择器,进而设置用户的头像。
5.1 为Image View添加单击手势识别
Image View控件在默认情况下是不具备交互功能的,这与控制器视图相同,但我们同样可以将它的交互功能开通。
步骤1 在项目导航中打开SignUpVC.swift文件,在viewDidLoad()方法底部添加下面的代码:
let imgTap = UITapGestureRecognizer(target: self, action:#selector(loadImg)) imgTap.numberOfTapsRequired = 1 avaImg.isUserInteractionEnabled = true avaImg.addGestureRecognizer(imgTap)
与上一章隐藏键盘的代码类似,只是这里将手势识别对象添加到了Image View对象上。
手势识别(UIGestureRecognizer)类可以将低级别的事件处理代码转换成高级别的动作。它们被绑定到视图上,这些对象允许视图对特定手势动作进行响应,就像控件一样。手势识别对象把触摸解析成一个确定的手势,例如划动(swip)、捏合(pinch)或者旋转。如果它们识别出了特定手势,则会发送一条动作消息(UITapGestureRecognizer初始化方法的第二个参数)给一个目标对象(UITapGestureRecognizer初始化方法的第一个参数)。目标对象一般来说是视图的控制器。这种设计模式简单而又强大:我们能够动态地决定一个视图要响应哪个动作,并且能够给一个视图加上手势识别器而不用创建视图的子类。
在设计应用程序的时候,需要考虑识别手势的类型。表5-1中列出了系统预定义的手势识别类型。
表5-1 系统预定义的手势识别类型
应用程序应该只以用户期望的方式对手势进行识别。例如,一个捏合操作应该只负责视图的放大和缩小,一个单击操作应该会选择某样东西。
手势不是离散的就是连续的。一个离散的手势,例如单击(tap),只发生一次,而且不可以取消。一个连续的手势,例如捏合(pinching),发生在一个时间段内。对于离散的手势,手势识别器发送给它的目标一个独立的动作消息。而连续手势的手势识别器会持续发送给目标动作消息,直到触摸序列停止。
5.2 创建照片获取器
一般情况下,如果应用中需要获取照片库中的照片,则需要借助系统内置的照片获取器。
步骤1 在SignUpVC类中新建loadImg()方法:
func loadImg(recognizer: UITapGestureRecognizer) { let picker = UIImagePickerController() picker.delegate = self picker.sourceType = .photoLibrary picker.allowsEditing = true present(picker, animated: true, completion: nil) }
loadImg()方法携带一个UITapGestureRecognizer类型的对象作为参数,该对象封装了Tap事件的相关信息。当用户单击Image View后会调用该方法。
在loadImg()方法中,我们首先创建UIImagePickerController类型的对象,它是照片获取器对象,用户可以通过它从摄像头或者相册中选择一张照片。当我们第一次创建UIImagePickerController对象时,iOS会自动询问用户是否允许应用程序访问照片库。
在方法中有三个地方需要说明一下:
首先,将self(当前的SignUpVC类的对象,也就是当前的视图控制器对象)赋值给照片获取器的delegate属性,这需要SignUpVC类必须符合UIImagePickerControllerDelegate协议,另外还要让SignUpVC类符合UINavigationControllerDelegate协议,这个协议是必须的,因为前面的协议用到了后面的协议。不添加后者,Xcode会报错!
UIImagePickerControllerDelegate协议的用处在于,可以说明用户是否选择了一张照片或者是否取消了选择。而第二个UINavigationControllerDelegate在这里并没有什么实际意义,仅仅用它来确保Xcode不报错,其更深层的意义我们就不再细究了。
其次,我们设置了获取器的sourceType为.photoLibrary,也就是告诉获取器从照片库中获取照片。这个枚举对象一共包含三种情况:
❑photoLibrary:将设备的照片库作为获取源。
❑camera:将设备内置的摄像头作为获取源,如果要确定使用前置还是后置摄像头,则需要通过cameraDevice属性进行设置。
❑savedPhotosAlbum:将设备中的相机胶卷相册作为获取源。
最后,设置获取器的allowsEditing属性为true,它允许用户可以对选择的照片进行剪裁。
步骤2 在SignUpVC类的声明部分,添加上面的两个协议:
class SignUpVC: UIViewController, UIImagePickerControllerDelegate, UINavigation- ControllerDelegate {
此时,picker.delegate = self所呈现的代码错误消失。
步骤3 在SignUpVC类中添加imagePickerController(_:didFinishPickingMediaWithInfo:)协议方法:
//关联选择好的照片图像到image view func imagePickerController(_ picker: UIImagePickerController, didFinishPicking- MediaWithInfo info: [String : Any]) { avaImg.image = info[UIImagePickerControllerEditedImage] as? UIImage self.dismiss(animated: true, completion: nil) }
其实,当我们在SignUpVC类中添加UIImagePickerControllerDelegate协议后,项目是不会报任何错误的,因为协议中的所有方法都是可选的。但是,如果我们真这样做的话, UIImagePickerController就失去了真正的意义,因为需要通过上面的协议方法获取用户所选择的照片。
在imagePickerController(_:didFinishPickingMediaWithInfo:)方法中,我们需要做下面几件事情:
❑从参数传递进来的info字典中提取image。
❑将提取出来的image赋值给avaImg对象。
❑关闭照片获取器。
首先,当用户在获取器中选择好照片后,会将相关信息以字典(Dictionary类型)的方式作为参数发送给我们。接下来,就需要我们通过各种键名来获取到这些信息,下面介绍几个相关的键名:
❑UIImagePickerControllerEditedImage:特指被用户编辑后的图像。
❑UIImagePickerControllerOriginalImage:特指用户选择的原始图像,未经过剪裁过的。
❑UIImagePickerControllerMediaURL:特指文件系统中影片的URL。
❑UIImagePickerControllerCropRect:特指应用到原始图像上的剪裁的矩形。
❑UIImagePickerControllerMediaType:特指用户选择的图像的类型。它包括kUTTypeImage (图像)和kUTTypeMovie(影片)类型。
还有一个问题就是,我们并不清楚获取到字典中的值是否是UIImage类型,所以不能直接使用它。我们需要使用类型转换的可选方法as?来获取UIImage对象。与as!强制转换不同,as?是可选转换,它意味着转换后的结果可能是具体的对象,也可能是nil。
步骤4 在SignUpVC类中添加下面的协议方法:
// 用户取消获取器操作时调用的方法 func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { self.dismiss(animated: true, completion: nil) }
如果用户在照片获取器中单击取消按钮,那么就关闭它。因为照片获取器默认时会占据整个屏幕,在单击取消按钮以后我们需要销毁它并返回之前调用它的SignUpVC控制器中。
构建并运行项目,在登录视图中单击注册按钮,然后在注册视图中单击Image View,理论上应该弹出照片选择器视图,因为这时会调用loadImg()方法。但如果你是在Xcode 8 Beta中运行的话,应用程序会崩溃并退出。这是为什么呢?下面将详细讲解。
5.3 访问照片库的前期准备
要想成功访问照片库,在Xcode 8(就目前的beta版)中我们还需要在info.plist文件中添加两条配置信息,这样才可以防止调出照片获取器时候应用程序崩溃退出,希望苹果在Xcode 8正式版的时候修复这个Bug。
步骤1 在项目导航中打开info.plist文件,在编译区域中选择最下面一行的配置信息,如图5-1所示。
图5-1 在info.plist中添加新的配置条目
步骤2 确保最后一行的配置信息为收缩状态(头部的灰色三角指向自己),单击配置信息右侧的圆圈灰色加号,此时会添加一行新的配置信息。
注意
如果你在扩展状态下单击配置信息的灰色加号,则会在该配置信息的内部添加一行子配置信息。
步骤3 在新添加的配置信息行中,设置Key为Privacy-Media Library Usage Description, Type为String, Value为Instagram需要使用该设备的媒体库。如法炮制,再添加一个配置信息,设 置Key为Privacy-Photo Library Usage Description, Type为String, Value为Instagram需要使用该设备的照片库,如图5-2所示。
图5-2 在info.plist中添加两个隐私相关配置条目
再次构建并运行项目,当项目启动以后会出现访问照片库的允许警告框。再次单击Image View则会正常弹出照片获取器,如图5-3所示。
图5-3 在模拟器中显示的隐私访问对话框
注意
需要注意的是,当我们在获取器中编辑照片时(将照片拖曳拉大)依然会导致程序崩溃。这应该是Xcode 8 Beta 1版本自身的问题,如果你使用的是最新版本的Xcode Beta版本则没有任何问题。
接下来,让我们直接选取照片,如图5-4所示。
图5-4 在照片获取器中选择头像
从照片库选择好照片后,回到注册页面,便看到已经添加到Image View中的头像了,如图5-5所示。
图5-5 返回到注册页面
如果你还不清楚如何将自己的照片添加到模拟器中的话,在模拟器中打开照片应用,然后直接将图片拖曳到照片应用中即可。
5.4 将Image View的外观设置为圆形
在本章的最后,我们要改变头像视图的外观,使它成为一个圆形。
步骤 在SignUpVC类中的viewDidLoad()方法中,添加下面的代码:
// 改变avaImg的外观为圆形 avaImg.layer.cornerRadius = avaImg.frame.width / 2 avaImg.clipsToBounds = true
通过image view的layer属性,可以设置视图的矩形圆角值,如果将它的矩形圆角值设置为自身宽度的一半(avaImg是80×80的正方形),那它就变成了圆形。第二行代码是剪裁掉多余的部分。
再次构建并运行项目,效果如图5-6所示,给用户的感受上立即提升了一个档次。
图5-6 将用户头像剪裁为圆形
本章小结
本章我们使用了照片获取器类(UIImagePickerController)从系统的照片库中获取指定的照片,除了编写相关代码以外,我们还需要在项目的info.plist文件中添加两个关键的配置选项,否则在运行的时候会发生崩溃。