UIImagePickerController是UIKit提供的一個提供系統拍照、攝像以及訪問本機媒體資源的ViewController工具。因為他是一個服務性的ViewController,所以其基本使用就是"present"一個ViewController。
這里我們先介紹如何用UIImagePickerController提供的界面進行照片的拍攝。由于是UIImagePickerController提供的一個系統服務,那么自然的在UI上就會有些固定的地方,不過UIImagePickerController也提供了一些自定義的可能。來看一個常用的拍照界面。
0. 設置UIImagePickerController進行拍照
UIImagePickerController在獲取圖像資源,首先需要指定一個資源源,有三類:
- photoLibrary : 照片庫
- camera : 攝像頭
- savedPhotosAlbum : 照片集
其實就是從攝像頭采集還是從手機系統里面的圖片進行選取。
在選擇了圖像資源后,還需要指定那種資源:
- kUTTypeImage: 圖片
- kUTTypeMovie :視頻
這里我們是要拍照,當然是選擇圖片資源了。
要拍照的話,首先檢查攝像頭資源是否可用:
if UIImagePickerController.isSourceTypeAvailable(.camera) {
...
}
通過調用“UIImagePickerController”的類方法:
class func isSourceTypeAvailable(UIImagePickerControllerSourceType)
檢查其是否支持從攝像頭采集資源。
然后再設置資源為攝像頭:
picker.sourceType = .camera
設置var sourceType: UIImagePickerControllerSourceType
實例變量為攝像頭。
設置了攝像頭資源還不夠,因為iPhone通常有兩個攝像頭:前置攝像頭和后置攝像頭。
這里先判斷對應的攝像頭是否可用。如果可用優先選擇后置攝像頭:
if UIImagePickerController.isCameraDeviceAvailable(.rear) {
picker.cameraDevice = .rear
} else if UIImagePickerController.isCameraDeviceAvailable(.front) {
picker.cameraDevice = .front
} else {
}
通過調用“UIImagePickerController”的類方法:
class func isCameraDeviceAvailable(UIImagePickerControllerCameraDevice)
判斷是否支持后置攝像頭。
最后如上面所說,要把類型設置成照片類型:
let types = UIImagePickerController.availableMediaTypes(for: .camera)
if let ts = types {
if ts.contains(kUTTypeImage as String) {
picker.mediaTypes = [kUTTypeImage as String]
Confirm(title: "kUTTypeImage", message: "設置為采集照片模式")
}
}
首先調用“UIImagePickerController”的類方法:
`class func availableMediaTypes(for: UIImagePickerControllerSourceType)`
查詢攝像頭支持的所有類型媒體,然后再判斷圖片類型kUTTypeImage是否包含在其中,如果在的話,就設置可以采集的類型的var mediaTypes: [String]
為 [kUTTypeImage as String]
。這樣就完成設置采集源以及采集資源的類型操作了。
1. 進行拍照
在進行了設置后,就可以準備進行拍照了。用UIImagePickerController拍照的過程,實際上是拉起一個包含拍照功能的ViewController,那么只要“present”這個ViewController出來就可以了:
self.present(picker, animated: true) {
//
}
如果一切正常的話,這時候應該就可以看到彈出來的拍照界面了:
這里就和操作系統應用“相機”里面的拍照一樣,但是如果拍照完,點擊“使用照片”,會發現雖然拍照的界面消失了,但是系統“照片”里面卻沒有照片,同時也沒有其他提示。
因為使用UIImagePickerController拍照的過程是個系統服務,是異步的,當沒有為其注冊Delegate的時候,就會像上面一樣,沒法獲得拍照的結果。Delegate需要實現協議:
@protocol UIImagePickerControllerDelegate<NSObject>
@optional
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
@end
一個是點擊保存的時候的回調,一個是用戶點擊取消時候的回調。無論哪個回調被執行,要想回到之前的用戶界面上面都需要對picker做“dismiss”操作,記住UIImagePickerController的本質是拉起一個系統的ViewController來拍照,所以不需要的時候自然要取消它:
picker.dismiss(animated: true) {
//
}
在用戶點擊保存是,我們可以通過info來獲得采集到的圖片的信息其中Key就是所存對象的類名
Key | 值類型 | 描述 |
---|---|---|
UIImagePickerControllerEditedImage | UIImage | 表示用戶在拍攝的照片上面修改后圖片數據 |
UIImagePickerControllerMediaMetadata | NSDictionary | 只在使用攝像頭并且拍攝圖片的時候才會有,表示圖片的描述數據 |
UIImagePickerControllerMediaType | NSString | kUTTypeImage表示圖片,kUTTypeMovie表示視頻 |
UIImagePickerControllerCropRect | NSValue | 實際上是CGRect對象 |
UIImagePickerControllerOriginalImage | UIImage | 表示拍攝時圖片數據 |
通過這類枚舉的Info數據,可以獲得拍照后的數據結果,比如想將拍攝后的結果保存到“照片”中。可以調用:
func UIImageWriteToSavedPhotosAlbum(_ image: UIImage,
_ completionTarget: Any?,
_ completionSelector: Selector?,
_ contextInfo: UnsafeMutableRawPointer?)
將照片進行保存。
注意:Swift接口只有在iOS8+才有這個接口,其Objective-C版本則從iOS2.0+就開始有了,所以如果是用Swift寫的App(Swift支持iOS7+),那么需要對iOS7做一個OC的橋接處理。
如下代碼:
func onSave(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
if let error = error {
// we got back an error!
Confirm(title: "Save Failed", message: error.localizedDescription)
} else {
Confirm(title: "Save Succfull", message: "Your altered image has been saved to your photos.")
}
}
/** UIImagePickerControllerDelegate **/
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
//
let img = info[UIImagePickerControllerOriginalImage]
UIImageWriteToSavedPhotosAlbum(img as! UIImage, self, #selector(onSave(_:didFinishSavingWithError:contextInfo:)), nil)
picker.dismiss(animated: true) {
//
}
}
這里對Swift不熟悉可能會有些疑惑,首先這里需要一個selector
回調函數,原型為
(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer)
而在Apple的官方文檔中卻并沒有這個函數原型的描述,而是其對應的OC版本:
- (void) image: (UIImage *) image
didFinishSavingWithError: (NSError *) error
contextInfo: (void *) contextInfo;
由于保存文件需要進行文件寫磁盤操作,所以保存文件是一個異步的操作,當保存完成或者出錯的時候,會通過上面的回調進行通知,由error來表示,另外回調還可以通過contextInfo傳遞一個用戶自定義數據。
UIImageWriteToSavedPhotosAlbum的第一個參數是要保存的圖片,第二個參數是回調的宿主對象,第三個則是上面說的回調,最后一個就是回調中的用戶自定義數據了。
這里我們僅僅用了一個Alert提示框。完整的Demo可以參考GitHub
2. 自定義拍照界面
UIImagePickerController雖然使用起來非常簡單,不過其也提供了一些自定界面的的方式,比如是否可以修改拍照后的照片(主要就是放大縮小);拍照、取消按鈕;閃光燈設置等。
不過經過各種實驗,攝像的界面基本外形就是下面一個大黑框,上面是拍照界面這個基本結構改不了,而且黑框大小沒法改變。這個基本是事實了,如果還想更多的自定義,還是得用AVFoundation.framework進行定制。
比如設置:
picker.showsCameraControls = false
此時得到界面就變成了:
空空的一塊板了,這時候需要調用:
func takePicture()
才能達到正常情況下按拍照鍵的效果。那要如何布局UI呢?
答案是:
var cameraOverlayView: UIView?
為其賦值一個自定義的View,就可以在自定義的View上進行交互設計了。比如這樣的效果:
參考代碼:
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
cameraView.frame = CGRect(x: 0, y: self.view.bounds.size.height-100, width: self.view.bounds.size.width, height: 100)
cameraView.backgroundColor = UIColor.blue
cameraView.alpha = 0.5
// ... set for buttons
picker.cameraOverlayView = cameraView
}
// action for buttons
@IBAction func onTakePhoto () {
picker.takePicture()
}
@IBAction func onSwitchCamera() {
if cameraMode == .rear {
picker.cameraDevice = .front
cameraBtn.setTitle("Rear", for: .normal)
cameraMode = .front
} else {
picker.cameraDevice = .rear
cameraBtn.setTitle("Front", for: .normal)
cameraMode = .rear
}
}
@IBAction func onFlashMode () {
if flashMode == .auto || flashMode == .on {
picker.cameraFlashMode = .off
flashMode = .off
flashBtn.setTitle("FlashON", for: .normal)
} else {
picker.cameraFlashMode = .on
flashMode = .on
flashBtn.setTitle("FlashOFF", for: .normal)
}
}
因為UIImagePickerController是被“present”出來的,他還實現了“UINavigationControllerDelegate”來感知被“present”的結果,所以我們可以在
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
里面設置自定義View的效果。
這里在這個自定義的View上面做一些UI交互,比如做閃光燈的模式選擇:
設置cameraFlashMode
為:
public enum UIImagePickerControllerCameraFlashMode : Int { case off // 關閉
case auto // 自動
case on // 打開
}
又比如說上面設置為背部攝像頭時,給一個UI控制選擇前置攝像頭:
picker.cameraDevice = .front
?
總的來說,自定義的范圍還是非常有限的。
3.總結
使用UIImagePickerController按照設置數據源;設置采集端;設置采集數據類型之后,就可以通過系統提供的界面進行照片拍攝,并獲得拍照后的圖片數據了。雖然也可以對拍照界面進行自定義設置,但是幅度有限。如果對拍照要求不高,可以使用這個方法來進行圖片采集,如果需要更多的控制,還是需要使用AVFoundation.framework提供的功能來實現。