庖丁UIKit之用UIImagePickerController拍照

UIImagePickerController是UIKit提供的一個提供系統拍照、攝像以及訪問本機媒體資源的ViewController工具。因為他是一個服務性的ViewController,所以其基本使用就是"present"一個ViewController。

這里我們先介紹如何用UIImagePickerController提供的界面進行照片的拍攝。由于是UIImagePickerController提供的一個系統服務,那么自然的在UI上就會有些固定的地方,不過UIImagePickerController也提供了一些自定義的可能。來看一個常用的拍照界面。

camera_sample

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

如果一切正常的話,這時候應該就可以看到彈出來的拍照界面了:

camera

這里就和操作系統應用“相機”里面的拍照一樣,但是如果拍照完,點擊“使用照片”,會發現雖然拍照的界面消失了,但是系統“照片”里面卻沒有照片,同時也沒有其他提示。

因為使用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

此時得到界面就變成了:

no_control

空空的一塊板了,這時候需要調用:

func takePicture()

才能達到正常情況下按拍照鍵的效果。那要如何布局UI呢?

答案是:

var cameraOverlayView: UIView?

為其賦值一個自定義的View,就可以在自定義的View上進行交互設計了。比如這樣的效果:

camera_custom

參考代碼:

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提供的功能來實現。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容