iOS 使用相机之 UIImagePickerController

iOS 开发中不可避免要调用系统相机完成照片或者视频的拍摄,使用相机有两种方式:AVFoundationUIImagePickerController,如果需求比较简单并且对UI没有特殊要求可以使用 UIImagePickerController,本文就介绍一下UIImagePickerController 的使用

UIImagePickerController 基本使用

UIImagePickerController 是苹果官方提供的一套相机、相册处理的方式,使用 UIImagePickerController你可以调用相机拍摄照片视频、调用相册功能,它提供了非常简单的拍照方法,支持所有的基本功能,比如切换到前置摄像头、开关闪光灯、点击屏幕区域实现对焦和调整曝光等

UIImagePickerController 的基本使用比较简单,主要有以下两步;

  • 初始化 UIImagePickerController 类的实例对象,并且设置该实例对象的相关属性

  • 实现 UIImagePickerController 的相关代理方法

先看一下 UIImagePickerController 的相关属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 数据来源类型,决定要跳转到哪个界面(相机拍照、照片图库、相册)
// camera(相机) photoLibrary(图库) savedPhotosAlbum(相册)
var sourceType: UIImagePickerControllerSourceType
// UIImagePickerController 根据这个的值来决定选择或拍摄的媒体类型,
// KUTTypeImage(图片,默认)、KUTTypeMovie(视频)、kUTTypeLivePhoto(LivePhoto)
var mediaTypes: [String]
// 选择或者拍摄的照片或者视频是否可编辑,默认NO
var allowsEditing: Bool
// 以下是视频的相关属性,设置它们必须保证mediaTypes 包含 kUTTypeMovie
// 视频最大录制时间,默认 10分钟
var videoMaximumDuration: TimeInterval
// 录制视频的质量,默认typeMedium
// 有六种质量可选(typeHigh,typeMedium,typeLow,type640x480,typeIFrame1280x720,typeIFrame960x540)
var videoQuality: UIImagePickerControllerQualityType
// sourceType 必须为相机才可用的设置
// 设置相机的捕获模式 有 Photo(默认)和 Video ,mediaTypes 必须包含 kUTTypeMovie
var cameraCaptureMode: UIImagePickerControllerCameraCaptureMode
// 设置前置摄像头还是后置摄像头,front 和 rear(默认)
var cameraDevice: UIImagePickerControllerCameraDevice
// 闪光灯设置,有off、auto(默认) 和 on
var cameraFlashMode: UIImagePickerControllerCameraFlashMode

以上就是使用 UIImagePickerController 用到的大部分属性了,下面代码演示具体使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//判断设备是否支持数据来源
if UIImagePickerController.isSourceTypeAvailable(.camera) {
// 实例化 UIImagePickerController 对象
let picker = UIImagePickerController()
// 设置数据来源类型
//相机
picker.sourceType = .camera
// 数据来源类型设置为图库,UIImagePickerController 就变成了一个照片和视频选择器,视频以及camera相关属性不可用,否则会 crash
picker.sourceType = .photoLibrary
//设置可用的媒体类型,需要导入 MobileCoreServices
picker.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String];
picker.allowsEditing = true
//视频相关设置
//设置拍摄视频的最长时间 20s
picker.videoMaximumDuration = 20.0
//设置拍摄视频的质量
picker.videoQuality = .typeHigh
// sourceType 必须为相机才可用的设置
// mediaTypes 包含 kUTTypeMovie
picker.cameraCaptureMode = .video
//设置为前置或者后置摄像头
picker.cameraDevice = .front
//摄像头闪光灯设置
picker.cameraFlashMode = .off
picker.delegate = self
self.present(picker, animated: true, completion: nil)
}else {
let alertVc = UIAlertController(title: "提示", message: "相机不可用", preferredStyle: .alert)
let sureAction = UIAlertAction(title: "确定", style: .default, handler: nil)
alertVc.addAction(sureAction)
self.present(alertVc, animated: true, completion: nil)
}

UIImagePickerControlle 既可以调用相机拍摄照片和视频,也可以设置 sourceType 获取相册或者图库中的照片和视频(本文不做讨论),除了UIImagePickerControlle iOS 还有另外两个框架:AssetsLibraryPhotoKit 也可以访问 iOS 设备的照片和视频,以后有机会讨论一下这三者的用法

在使用 UIImagePickerController 的时候的时候,最好先判断一下相机或者相关 sourceType 是否可用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 当前 SourceType 类型是否可用
class func isSourceTypeAvailable(_ sourceType: UIImagePickerControllerSourceType) -> Bool
// 判断前置或者后置摄像头是否可用
class func isCameraDeviceAvailable(_ cameraDevice: UIImagePickerControllerCameraDevice) -> Bool
// 摄像头闪光灯是否可用
class func isFlashAvailable(for cameraDevice: UIImagePickerControllerCameraDevice) -> Bool
// 返回某个 sourceType 下可用的 MediaType 数组
class func availableMediaTypes(for sourceType: UIImagePickerControllerSourceType) -> [String]?
// 返回某个 摄像头下可用的 CaptureMode 数组
class func availableCaptureModes(for cameraDevice: UIImagePickerControllerCameraDevice) -> [NSNumber]?

注意:使用相机注意要在 Info.plist 中添加以下隐私权限的申明:

1
2
3
4
5
6
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>

上面的工作完成之后,用户就可以在 UIImagePickerController 中选择照片或者拍摄的照片和视频了,用户选择的照片或者视频可以在 UIImagePickerController 的代理方法里面获取到,因为 UIImagePickerController 继承自 UINavigationController, 所以要实现代理方法必须遵守 UINavigationControllerDelegateUIImagePickerControllerDelegate 两个代理协议:

1
2
3
4
5
// 用户选择完照片或者视频之后调用
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
// 用户点击 UIImagePickerController 上的取消按钮之后调用
func imagePickerControllerDidCancel(_ picker: UIImagePickerController)

看如何从代理方法里面获取用户选择好的照片和视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// MARK: - UINavigationControllerDelegate, UIImagePickerControllerDelegate
// 选择完图片或者视频后调用此代理方法(此方法不管是 sourceType 是什么都会调用)
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
//info 里面是选中的图片或者视频的信息的字典,根据不同情况可能包含以下key
// UIImagePickerControllerMediaType 用户选择的媒体类型
// UIImagePickerControllerOriginalImage 用户选中的图像的原始 UIImage
// UIImagePickerControllerEditedImage 用户选中的图像(编辑剪裁之后的 UIImage)
// UIImagePickerControllerCropRect 编辑裁剪区域的Frame
// UIImagePickerControllerMediaURL 用户选中的媒体文件的临时 URL地址
// UIImagePickerControllerReferenceURL 用户选中的媒体文件的AssetsLibrary 地址
// UIImagePickerControllerMediaMetadata 用户选中的图片的元数据,sourceType 是相机的时候才可用
// UIImagePickerControllerLivePhoto 用户选中的LivePhoto
//获取媒体的类型
let mediaType = info[UIImagePickerControllerMediaType] as! CFString
if mediaType == kUTTypeImage {//选择的是照片
// 如果过 allowsEditing 为 NO,UIImagePickerControllerEditedImage不存在,只有UIImagePickerControllerOriginalImage
if let img = info[UIImagePickerControllerEditedImage] {
UIImageWriteToSavedPhotosAlbum(img as! UIImage, self, #selector(image(_:didFinishSavingWithError:contextInfo:)),nil)
}else {
if let img = info[UIImagePickerControllerOriginalImage] {
UIImageWriteToSavedPhotosAlbum(img as! UIImage, self, #selector(image(_:didFinishSavingWithError:contextInfo:)),nil)
}
}
}
if mediaType == kUTTypeMovie {//选择的是视频
//获取到视频的临时路径
if let urlStr = info[UIImagePickerControllerMediaURL] as? URL {
if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr.path) {//判断视频路径是否可以被保存的相册
//保存视频到图库中
DispatchQueue.global().async {
UISaveVideoAtPathToSavedPhotosAlbum(urlStr.path, self, #selector(self.video(_ :didFinishSavingWithError:contextInfo:)), nil)
}
}
}
}
if mediaType == kUTTypeLivePhoto {
if let livePhoto = info[UIImagePickerControllerLivePhoto] as? PHLivePhoto {
// 把选中的livephoto显示出来
// livephoto 需要在 PHLivePhotoView 上才能显示,需要导入Photos 和 PhotosUI 两个库
self.livePhotoView?.livePhoto = livePhoto
}
}
picker.dismiss(animated: true, completion: nil)
}
// 用户点击取消后的代理方法,默认点击取消会 dissmiss PickerController
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
// 保存照片之后的回调,判断照片是否保存成功,方法名必须这样写
func image(_ image: UIImage,
didFinishSavingWithError error: NSError?,
contextInfo: UnsafeRawPointer) {
if let error = error {
// we got back an error!
let ac = UIAlertController(title: "警告", message: error.localizedDescription, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "确定", style: .default))
present(ac, animated: true)
} else {
let ac = UIAlertController(title: "提示", message: "照片成功保存到相册", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "确定", style: .default))
present(ac, animated: true)
}
}
// 保存视频之后的回调,判断视频是否保存成功,方法名必须这样写
func video(_ videoPath: String,
didFinishSavingWithError error: NSError?,
contextInfo: UnsafeRawPointer) {
if let error = error {
// we got back an error!
let ac = UIAlertController(title: "警告", message: error.localizedDescription, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "确定", style: .default))
present(ac, animated: true)
} else {
let ac = UIAlertController(title: "提示", message: "视频成功保存到相册", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "确定", style: .default))
present(ac, animated: true)
}
}

上面把用户选择的照片或者视频保存在相册,实际开发中可能是把照片或者视频上传到服务器,上面就是 UIImagePickerController 中使用相机拍照或者拍摄视频的用法,sourcceTypephotoLibrary 或者 savedPhotosAlbum 时可以作为系统的相册选择器,从代理方法里获取选择的照片或者视频上面是一样的

自定义 UIImagePickerController

UIImagePickerControllersourcceType 为相机的时候可以简单的自定义UI,自定义UI需要用到下面的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 是否显示自带的UI,自定义UI的时候选择 NO
var showsCameraControls: Bool
// 自定义的UI视图,会覆盖在相机预览试图的上面
var cameraOverlayView: UIView?
// 设置相机预览视图 transform 变化,调整预览试图的大小和拉伸
var cameraViewTransform: CGAffineTransform
// 调用相机拍照的方法
func takePicture()
// 调用相机开始录制视频
func startVideoCapture() -> Bool
// 调用相机停止录制视频
func stopVideoCapture()

以下是我简单的自定义UI的 UIImagePickerController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
func showCustomPickerController() {
//判断设备是否支持数据来源
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.mediaTypes = [kUTTypeMovie as String, kUTTypeImage as String];
picker.videoMaximumDuration = 20.0
picker.videoQuality = .typeHigh
picker.cameraCaptureMode = .photo
picker.cameraDevice = .rear
picker.cameraFlashMode = .off
// 自定义拍照界面
let customCameraView = UIView(frame: UIScreen.main.bounds)
//拍照按钮
let takePicButton = UIButton(type: .custom)
takePicButton.frame = CGRect(x: (UIScreen.main.bounds.size.width - 100) * 0.5, y: 600, width: 100, height: 44)
takePicButton.setTitle("拍照",for: .normal)
takePicButton.addTarget(self, action: #selector(takePic), for: .touchUpInside)
self.takePicButton = takePicButton
customCameraView.addSubview(takePicButton)
let margin = (UIScreen.main.bounds.size.width - 2 * 100) / 3
// 开始摄像按钮(如果是拍照,则不需要此按钮)
let startButton = UIButton(type: .custom)
startButton.frame = CGRect(x: margin, y: 600, width: 100, height: 44)
startButton.setTitle("开始视频",for: .normal)
startButton.addTarget(self, action: #selector(startCapture), for: .touchUpInside)
self.startButton = startButton
customCameraView.addSubview(startButton)
// 停止摄像按钮(如果是拍照,则不需要此按钮)
let stopButton = UIButton(type: .custom)
stopButton.frame = CGRect(x: 2 * margin + 100, y: 600, width: 100, height: 44)
stopButton.setTitle("停止视频",for: .normal)
stopButton.addTarget(self, action: #selector(stopCapture), for: .touchUpInside)
self.stopButton = stopButton
customCameraView.addSubview(stopButton)
// 切换拍照和视频的按钮
let switchButton = UIButton(type: .custom)
switchButton.frame = CGRect(x: UIScreen.main.bounds.size.width - 100 - 20, y: 20, width: 100, height: 44)
switchButton.setTitle("拍照/视频", for: .normal)
switchButton.addTarget(self, action: #selector(swithCaptureMode), for: .touchUpInside)
self.switchButton = switchButton
customCameraView.addSubview(switchButton)
self.startButton?.isHidden = true
self.stopButton?.isHidden = true
//取消
let cancelButton = UIButton(type: .custom)
cancelButton.frame = CGRect(x: 20, y: 20, width: 50, height: 44)
cancelButton.setTitle("取消", for: .normal)
cancelButton.addTarget(self, action: #selector(dissmisController), for: .touchUpInside)
customCameraView.addSubview(cancelButton)
//sourceType 必须是 camera
picker.cameraOverlayView = customCameraView
// 是否显示 UIImagePickerController 底部控制部分的UI,默认true,需要定制底部UI的时候设置为false 隐藏默认UI
picker.showsCameraControls = false
// 当 showsCameraControls 为 NO 的时候,照片或者视频不可编辑 allowsEditing 设置无效
picker.allowsEditing = true
//设置拍照时 预览界面大小
picker.cameraViewTransform = CGAffineTransform(scaleX: 1.2, y: 1.2)
picker.delegate = self
self.pickerController = picker
self.present(picker, animated: true, completion: nil)
}else {
let alertVc = UIAlertController(title: "提示", message: "相机不可用", preferredStyle: .alert)
let sureAction = UIAlertAction(title: "确定", style: .default, handler: nil)
alertVc.addAction(sureAction)
self.present(alertVc, animated: true, completion: nil)
}
}
// 开始录制视频
func startCapture() {
self.pickerController?.stopVideoCapture()
self.pickerController?.startVideoCapture()
}
// 停止录制视频
func stopCapture() {
self.pickerController?.stopVideoCapture()
}
func swithCaptureMode() {
if self.pickerController?.cameraCaptureMode == UIImagePickerControllerCameraCaptureMode.video {
self.pickerController?.cameraCaptureMode = .photo
self.startButton?.isHidden = true
self.stopButton?.isHidden = true
self.takePicButton?.isHidden = false
}else {
self.pickerController?.cameraCaptureMode = .video
self.startButton?.isHidden = false
self.stopButton?.isHidden = false
self.takePicButton?.isHidden = true
}
}
func takePic() {
self.pickerController?.takePicture()
}

自定义UI情况下,allowsEditing 不可用,所以拍照完成或者录制视频完成之后不会跳转编辑页面而是直接进入代理方法,在代理方法里获取照片或者视频和正常情况是一样的,就不再赘述,详细请参考 Demo 代码

上面就是 UIImagePickerController 的使用,可以发现 UIImagePickerController 只能很简单的使用系统的相机进行拍照和视频,下篇文章将介绍如何使用 AVFoundation 自定义相机,使用 AVFoundation 自定义相机可以控制硬件的参数、更加细致进行图像捕捉等

以上です

本人刚开始写博客,主要是为了给自己的知识点做一个笔记,方便自己以后查阅,如果能让别人有所启发也是荣幸之至!如有错误,欢迎指正!