こんにちは!Popoです。
今回は、カメラ画面を作成してみたいと思います。
こんな方へのお勧めの記事です。
- カメラアプリを作成したい
- カスタマイズ可能なカメラ画面を作成したい
- 「AVFoundation」を学習したい
昔、書籍を購入してobjective-cの勉強をしていたころ。
学習項目の中に必ず「カメラ画面作成のページがあったなぁ」と思い出しています。
定番の学習項目といったところですね。
「AVFoundation」フレームワークを利用する理由
書籍等では「UIImagePickerController」を利用しているものが多いです。
私がなぜ「AVFoundation」フレームワークを利用しているか?
「AVFoundation」フレームワークのメリット、デミリットをあげてみますと!(ザックリですが)
メリット | デメリット |
---|---|
カメラ画面に色々なカスタマイズ可能 | ロジックが「UIImagePickerController」より複雑 |
私が「AVFoundation」を選択する一番の理由は「カメラ画面に色々なカスタマイズが可能」という点です。
「UIImagePickerController」では、フレームワークのお決まり画面になってしまうため自由度がありません。
その反面、ロジックがかさむというデミリットもあります。(私の開発方針自体ロジックがかさみますが😅)
私は、メリットの方が効果が大きいと判断して「AVFoundation」を利用しています。
アプリの動作環境
アプリの開発環境です。
項目 | バージョン |
Xcode | Version 14.3.1 (14E300c) |
Swift | Swift version 5.8.1 |
MacOS | macOS Ventura バージョン13.4(22F66) |
アプリ開発の前提条件
アプリを開発する上で、コーディング以外に必要な設定や作業を上げておきます。
カメラロールアクセス
カメラロールへのアクセスについては、前記事の「カメラロールへのアクセス前提条件」項目を参照してください。
カメラロールへ画像データ追加
アプリからデバイスのカメラロールに画像データを追加するためには「info.plist」にNSPhotoLibraryAddUsageDescriptionキーを追加する必要があります。
Valueには、アプリ初回起動時に表示されるダイアログのメッセージを入力しておきます。
カメラへのアクセス
アプリからデバイスのカメラにアクセスをするためには「info.plist」にNSCameraUsageDescriptioキーを追加する必要があります。
こちらも同様に、Valueにはダイアログに表示されるメッセージを設定します。
「AVFoundation」フレームワーク
「AVFoundation」フレームワークでは、Appleプラットフォーム上でオーディオビジュアルメディアを検査、再生、キャプチャするための幅広いAPIが用意されています。
今回は下記の4つを利用していきます。
AVCaptureSession
キャプチャ動作を構成し、入力デバイスからキャプチャ出力までのデータ処理を調整します。
AVCaptureDevice
カメラ等、仮想キャプチャデバイスのオブジェクトです。
AVCapturePhotoOutput
キャプチャーの出力を管理します。
AVCaptureVideoPreviewLayer
カメラからビデオを表示するためのレイアになります。
アプリのソースコード全体
ソースコードについて解説していきます。
OneViewController
メインのUIViewControllerの解説です。
主な機能
- AVCaptureSession生成
- AVCaptureDeviceの生成
- AVCapturePhotoOutputの生成
- AVCaptureVideoPreviewLayerの生成
- カメラロールアクセスの判定とエラーメッセージ設定
- シャッターボタンの生成
- シャッターボタンタッチイベント
- AVCapturePhotoCaptureDelegateデリゲートメソッド
- カメラロールへ画像データ出力
import UIKit
import AVFoundation
class OneViewController: UIViewController {
// デバイスからの入力と出力を管理するオブジェクトの作成
fileprivate var captureSession = AVCaptureSession()
// カメラデバイスそのものを管理するオブジェクトの作成
// メインカメラの管理オブジェクトの作成
fileprivate var mainCamera: AVCaptureDevice?
// インカメの管理オブジェクトの作成
fileprivate var innerCamera: AVCaptureDevice?
// 現在使用しているカメラデバイスの管理オブジェクトの作成
fileprivate var currentDevice: AVCaptureDevice?
// キャプチャーの出力データを受け付けるオブジェクト
fileprivate var photoOutput : AVCapturePhotoOutput?
// プレビュー表示用のレイヤ
fileprivate var cameraPreviewLayer : AVCaptureVideoPreviewLayer?
//cameraボタン---------------------------------------
fileprivate var shutterButton : UIButton!
fileprivate var shutterImageView = UIImageView()
//View Size取得
fileprivate var camerabaseRect = UIScreen.main.bounds
//シャッターボタン重複チェックフラグ
fileprivate var shutterFlag : Bool = true
//カメラ使用許可フラグ
fileprivate var errorFlag : Bool = true
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black
if (self.cameraFirstAuthorization())
{
//カメラ使用許可
self.errorFlag = true
//カメラ生成
self.setupCaptureSession()
self.setupDevice()
self.setupInputOutput()
self.setupPreviewLayer()
} else {
//カメラ使用許可
self.errorFlag = false
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.shutterFlag = true
//エラーチェック
if (!self.errorFlag)
{
//エラーメッセージ表示
self.cameraUseError()
} else {
//カメラ画面
self.captureSession.startRunning()
}
//--ボタン生成-----------------------------
self.initButton()
//---------------------------------------
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//MARK: 画面UI
extension OneViewController
{
//Layer,totalボタン生成
fileprivate func initButton() {
//シャッターボタン------------------------------------------------
self.shutterButton = UIButton()
self.shutterButton.backgroundColor = UIColor.clear
self.shutterButton.addTarget(self, action: #selector(OneViewController.shutterButtonPush(_:)), for: UIControl.Event.touchUpInside)
self.shutterButton.layer.cornerRadius = min(self.shutterButton.frame.width, self.shutterButton.frame.height) / 2
let cameraButtonSize = CGRect(x:(SCREEN_WIDTH/2) - 40, y: SCREEN_HEIGHT - 115, width: 80, height: 80)
self.shutterButton.frame = cameraButtonSize
self.shutterButton.isEnabled = true
self.view.addSubview(self.shutterButton)
//シャッターimageView------------------------------------------------
let headerImage :UIImage? = UIImage(named:"shutterButton")
self.shutterImageView = UIImageView(image:headerImage)
self.shutterImageView.frame = CGRect(x:(SCREEN_WIDTH/2) - 40, y: SCREEN_HEIGHT - 115, width: 80, height: 80)
self.shutterImageView.backgroundColor = UIColor.black
self.shutterImageView.contentMode = UIView.ContentMode.scaleAspectFit
self.shutterImageView.isUserInteractionEnabled = false
self.view.addSubview(self.shutterImageView)
}
//カメラ アクセス確認
fileprivate func cameraFirstAuthorization() ->Bool
{
var checkFlag:String = "0"
switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
case .authorized:
print("cameraFirstAuthorization authorized")
break
// キャプチャー開始など
case .denied:
print("cameraFirstAuthorization denied")
checkFlag = "1"
break
// 拒否されてる。設定画面への遷移示唆など
case .notDetermined:
print("cameraFirstAuthorization notDetermined")
AVCaptureDevice.requestAccess(for: .video, completionHandler: {accessGranted in
if (!accessGranted)
{
checkFlag = "1"
}
})
break
// 初回。確認ダイアログをここで出すとハンドリングが楽。
case .restricted:
print("cameraFirstAuthorization Restricted")
// 利用が制限されている。ペアレント・コントロール?
checkFlag = "1"
break
@unknown default:
print("@unknown default")
break
}
if checkFlag == "0"
{
return true
} else {
return false
}
}
//カメラ アクセスエラーメッセージ
fileprivate func cameraUseError()
{
let alertVC = UIAlertController(title: "カメラにアクセスする権限がありません。アプリケーションの権限設定を変更してください。", message: nil, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
}))
self.present(alertVC, animated: true, completion: nil)
}
}
//MARK: ボタンイベント
extension OneViewController{
//カメラシャッターボタン------------------------------------
@objc func shutterButtonPush(_ sender: UIButton)
{
if self.shutterFlag
{
self.shutterFlag = false
// フラッシュの設定
let settings = AVCapturePhotoSettings()
//flashサポートしてるかの確認
let device = AVCaptureDevice.default(
AVCaptureDevice.DeviceType.builtInWideAngleCamera,
for: AVMediaType.video, // ビデオ入力
position: AVCaptureDevice.Position.back)
if !device!.hasFlash{
settings.flashMode = .off
} else {
settings.flashMode = .auto
}
// 撮影された画像をdelegateメソッドで処理
self.photoOutput?.capturePhoto(with: settings, delegate: self as AVCapturePhotoCaptureDelegate)
}
}
}
//MARK: AVCapturePhotoCaptureDelegateデリゲートメソッド
extension OneViewController: AVCapturePhotoCaptureDelegate{
// 撮影した画像データが生成されたときに呼び出されるデリゲートメソッド
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let imageData = photo.fileDataRepresentation() {
// Data型をUIImageオブジェクトに変換
let image = UIImage(data: imageData)
//画像データを写真に登録
self.imagePhotoSave(image)
}
}
}
//MARK: カメラ設定メソッド
extension OneViewController{
// カメラの画質の設定
fileprivate func setupCaptureSession() {
// sessionPreset: キャプチャ・クオリティの設定
//session.sessionPreset = AVCaptureSession.Preset.high
// session.sessionPreset = AVCaptureSessionPresetPhoto
// session.sessionPreset = AVCaptureSessionPresetHigh
// session.sessionPreset = AVCaptureSessionPresetMedium
// session.sessionPreset = AVCaptureSessionPresetLow
self.captureSession.sessionPreset = AVCaptureSession.Preset.photo
//captureSession.sessionPreset = AVCaptureSession.Preset.medium
}
// デバイスの設定
fileprivate func setupDevice() {
// カメラデバイスのプロパティ設定
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
// プロパティの条件を満たしたカメラデバイスの取得
let devices = deviceDiscoverySession.devices
for device in devices {
if device.position == AVCaptureDevice.Position.back {
self.mainCamera = device
} else if device.position == AVCaptureDevice.Position.front {
self.innerCamera = device
}
}
// 起動時のカメラを設定
self.currentDevice = self.mainCamera
}
// 入出力データの設定
fileprivate func setupInputOutput() {
do {
// 指定したデバイスを使用するために入力を初期化
let captureDeviceInput = try AVCaptureDeviceInput(device: self.currentDevice!)
// 指定した入力をセッションに追加
self.captureSession.addInput(captureDeviceInput)
// 出力データを受け取るオブジェクトの作成
self.photoOutput = AVCapturePhotoOutput()
// 出力ファイルのフォーマットを指定
if #available(iOS 11.0, *)
{
self.photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format:[AVVideoCodecKey:AVVideoCodecType.jpeg])], completionHandler: nil)
} else {
self.photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format:[AVVideoCodecKey:AVVideoCodecJPEG])], completionHandler: nil)
}
self.captureSession.addOutput(self.photoOutput!)
} catch {
print(error)
}
}
// カメラのプレビューを表示するレイヤの設定
fileprivate func setupPreviewLayer() {
// 指定したAVCaptureSessionでプレビューレイヤを初期化
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
// プレビューレイヤが、カメラのキャプチャーを縦横比を維持した状態で、表示するように設定
//self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
// プレビューレイヤの表示の向きを設定
self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
self.cameraPreviewLayer?.frame = CGRect(x: 0, y: 80, width: self.camerabaseRect.width, height: self.camerabaseRect.width + 100)
self.view.layer.insertSublayer(self.cameraPreviewLayer!, at: 0)
}
}
//MARK: カメラ設定メソッド
extension OneViewController{
//写真ライブラリに保存
fileprivate func imagePhotoSave(_ image:UIImage!)
{
//カメラ画面停止
self.captureSession.stopRunning()
guard let cameraImage:UIImage = image?.fixOrientation() else{
return
}
//写真ライブラリに保存
UIImageWriteToSavedPhotosAlbum(cameraImage, nil, nil, nil)
}
}
アプリの画面動作
アプリの画面動作を動画で見てみましょう!
各ロジック解説
それでは、各クラスの詳細箇所の解説をします。
Import
「AVFoundation」をインポートします。
import UIKit
import AVFoundation
viewDidLoad、viewWillAppear
- viewDidLoad
画面の背景色は黒色にします。
「cameraFirstAuthorization」メソッドでカメラアクセスの判定を行い、許可状態であれば「AVCaptureSession」「AVCaptureDevice」「AVCapturePhotoOutput」「AVCaptureVideoPreviewLayer」を生成します。
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black
if (self.cameraFirstAuthorization())
{
//カメラ使用許可
self.errorFlag = true
//カメラ生成
self.setupCaptureSession()
self.setupDevice()
self.setupInputOutput()
self.setupPreviewLayer()
} else {
//カメラ使用許可
self.errorFlag = false
}
}
- viewWillAppear
「viewDidLoad」でのカメラアクセス判定結果から、エラーメッセージ表示かカメラ起動を行います。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.shutterFlag = true
//エラーチェック
if (!self.errorFlag)
{
//エラーメッセージ表示
self.cameraUseError()
} else {
//カメラ画面
self.captureSession.startRunning()
}
//--ボタン生成-----------------------------
self.initButton()
//---------------------------------------
}
カメラアクセスが許可されていない場合は、下記のエラーメッセージが表示されます。
ボタン生成
私は、基本「UIButton」の「setImage」は使用しません。「UIImageView]を使用します。
「UIImageView」の「contentMode」で綺麗に画像を表示することができるためです。
//Layer,totalボタン生成
fileprivate func initButton() {
//シャッターボタン------------------------------------------------
self.shutterButton = UIButton()
self.shutterButton.backgroundColor = UIColor.clear
self.shutterButton.addTarget(self, action: #selector(OneViewController.shutterButtonPush(_:)), for: UIControl.Event.touchUpInside)
self.shutterButton.layer.cornerRadius = min(self.shutterButton.frame.width, self.shutterButton.frame.height) / 2
let cameraButtonSize = CGRect(x:(SCREEN_WIDTH/2) - 40, y: SCREEN_HEIGHT - 115, width: 80, height: 80)
self.shutterButton.frame = cameraButtonSize
self.shutterButton.isEnabled = true
self.view.addSubview(self.shutterButton)
//シャッターimageView------------------------------------------------
let headerImage :UIImage? = UIImage(named:"shutterButton")
self.shutterImageView = UIImageView(image:headerImage)
self.shutterImageView.frame = CGRect(x:(SCREEN_WIDTH/2) - 40, y: SCREEN_HEIGHT - 115, width: 80, height: 80)
self.shutterImageView.backgroundColor = UIColor.black
self.shutterImageView.contentMode = UIView.ContentMode.scaleAspectFit
self.shutterImageView.isUserInteractionEnabled = false
self.view.addSubview(self.shutterImageView)
}
カメラロールアクセス許可
「AVCaptureDevice.authorizationStatus」を使用してカメラアクセスの許可判定を行っています。
//カメラ アクセス確認
fileprivate func cameraFirstAuthorization() ->Bool
{
var checkFlag:String = "0"
switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
case .authorized:
print("cameraFirstAuthorization authorized")
break
// キャプチャー開始など
case .denied:
print("cameraFirstAuthorization denied")
checkFlag = "1"
break
// 拒否されてる。設定画面への遷移示唆など
case .notDetermined:
print("cameraFirstAuthorization notDetermined")
AVCaptureDevice.requestAccess(for: .video, completionHandler: {accessGranted in
if (!accessGranted)
{
checkFlag = "1"
}
})
break
// 初回。確認ダイアログをここで出すとハンドリングが楽。
case .restricted:
print("cameraFirstAuthorization Restricted")
// 利用が制限されている。ペアレント・コントロール?
checkFlag = "1"
break
@unknown default:
print("@unknown default")
break
}
if checkFlag == "0"
{
return true
} else {
return false
}
}
「AVCaptureDevice.authorizationStatus」のマニュアルになります。
「viewDidLoad」で呼ばれているメソッドです。
シャッターボタンタッチイベント
デバイスにフラッシュ機能があるかチェックを行い、delegateメソッドをCallしてキャプチャを取得します。
flash機能の無いデバイスに「.auto」の設定でdelegateメソッドをCallするとクラッシュしてしまいます。
//カメラシャッターボタン------------------------------------
@objc func shutterButtonPush(_ sender: UIButton)
{
if self.shutterFlag
{
self.shutterFlag = false
// フラッシュの設定
let settings = AVCapturePhotoSettings()
//flashサポートしてるかの確認
let device = AVCaptureDevice.default(
AVCaptureDevice.DeviceType.builtInWideAngleCamera,
for: AVMediaType.video, // ビデオ入力
position: AVCaptureDevice.Position.back)
if !device!.hasFlash{
settings.flashMode = .off
} else {
settings.flashMode = .auto
}
// 撮影された画像をdelegateメソッドで処理
self.photoOutput?.capturePhoto(with: settings, delegate: self as AVCapturePhotoCaptureDelegate)
}
}
AVCapturePhotoCaptureDelegateデリゲートメソッド
カメラで撮影した画像データを取得します。
// 撮影した画像データが生成されたときに呼び出されるデリゲートメソッド
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let imageData = photo.fileDataRepresentation() {
// Data型をUIImageオブジェクトに変換
let image = UIImage(data: imageData)
//画像データを写真に登録
self.imagePhotoSave(image)
}
}
カメラ生成
カメラの生成ロジックになります。
カメラの画質の設定
色々と試したため、他のプロパティはコメントにしています。
「sessionPreset」でカメラの品質レベル、ビートレートを設定します。
fileprivate func setupCaptureSession() {
// sessionPreset: キャプチャ・クオリティの設定
//session.sessionPreset = AVCaptureSession.Preset.high
// session.sessionPreset = AVCaptureSessionPresetPhoto
// session.sessionPreset = AVCaptureSessionPresetHigh
// session.sessionPreset = AVCaptureSessionPresetMedium
// session.sessionPreset = AVCaptureSessionPresetLow
self.captureSession.sessionPreset = AVCaptureSession.Preset.photo
//captureSession.sessionPreset = AVCaptureSession.Preset.medium
}
詳細は下記を参照ください。
プロパティ | 内容 |
high | 高品質の出力をキャプチャするのに適したプリセット。 |
medium | 中品質の出力をキャプチャするのに適したプリセット。 |
low | 低品質の出力をキャプチャするのに適したプリセット。 |
photo | 高解像度の写真品質の出力をキャプチャするのに適したプリセット。 |
inputPriority | キャプチャ セッションのオーディオおよびビデオ出力設定を指定しないプリセット。 |
qHD960x540 | クォーター HD 品質 (960 x 540 ピクセル) ビデオ出力のキャプチャに適したプリセット。 |
hd1280x720 | 720p 品質 (1280 x 720 ピクセル) ビデオ出力のキャプチャに適したプリセット。 |
hd1920x1080 | 1080p 品質 (1920 x 1080 ピクセル) ビデオ出力のキャプチャに適したプリセット。 |
hd4K3840x2160 | 2160p 品質 (3840 x 2160 ピクセル) のビデオ出力のキャプチャに適したプリセット。 |
qvga320x240 | 320 x 240 ピクセルのビデオ出力のキャプチャに適したプリセット。 |
vga640x480 | VGA 品質 (640 x 480 ピクセル) ビデオ出力のキャプチャに適したプリセット。 |
iFrame960x540 | AAC オーディオを使用して、960 x 540 品質の iFrame H.264 ビデオを約 30 Mbit/秒でキャプチャするのに適したプリセット。 |
iFrame1280x720 | AAC オーディオを使用して、約 40 Mbit/秒で 1280 x 720 品質の iFrame H.264 ビデオをキャプチャするのに適したプリセット。 |
cif352x288 | CIF 品質 (352 x 288 ピクセル) ビデオ出力のキャプチャに適したプリセット。 |
デバイスの設定
「AVCaptureDevice.Position.back」(デバイスの被写体に面する側の位置)のオブジェクトを取得します。
fileprivate func setupDevice() {
// カメラデバイスのプロパティ設定
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
// プロパティの条件を満たしたカメラデバイスの取得
let devices = deviceDiscoverySession.devices
for device in devices {
if device.position == AVCaptureDevice.Position.back {
self.mainCamera = device
} else if device.position == AVCaptureDevice.Position.front {
self.innerCamera = device
}
}
// 起動時のカメラを設定
self.currentDevice = self.mainCamera
}
Appleのドキュメントです。
入出力データの設定
入出力の情報を「AVCaptureSession」に設定します。
fileprivate func setupInputOutput() {
do {
// 指定したデバイスを使用するために入力を初期化
let captureDeviceInput = try AVCaptureDeviceInput(device: self.currentDevice!)
// 指定した入力をセッションに追加
self.captureSession.addInput(captureDeviceInput)
// 出力データを受け取るオブジェクトの作成
self.photoOutput = AVCapturePhotoOutput()
// 出力ファイルのフォーマットを指定
if #available(iOS 11.0, *)
{
self.photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format:[AVVideoCodecKey:AVVideoCodecType.jpeg])], completionHandler: nil)
} else {
self.photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format:[AVVideoCodecKey:AVVideoCodecJPEG])], completionHandler: nil)
}
self.captureSession.addOutput(self.photoOutput!)
} catch {
print(error)
}
}
カメラのプレビューを表示するレイヤの設定
fileprivate func setupPreviewLayer() {
// 指定したAVCaptureSessionでプレビューレイヤを初期化
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
//
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
// プレビューレイヤの表示の向きを設定
self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
self.cameraPreviewLayer?.frame = CGRect(x: 0, y: 80, width: self.camerabaseRect.width, height: self.camerabaseRect.width + 100)
self.view.layer.insertSublayer(self.cameraPreviewLayer!, at: 0)
}
//View Size取得
fileprivate var camerabaseRect = UIScreen.main.bounds
上記の画面サイズの定義は入れています。
カメラロール出力
カメラを停止後、カメラロールに画像データを出力します。
//写真ライブラリに保存
fileprivate func imagePhotoSave(_ image:UIImage!)
{
//カメラ画面停止
self.captureSession.stopRunning()
guard let cameraImage:UIImage = image?.fixOrientation() else{
return
}
//写真ライブラリに保存
UIImageWriteToSavedPhotosAlbum(cameraImage, nil, nil, nil)
}
まとめ
「AVFoundation」フレームワークを利用してカメラ画面を作成してみました。
このフレームワークのメリットは、
カメラ画面に色々なカスタマイズが可能
パターンを掴めば、かさむロジックも大した事はないと思っています。
それよりも色々なカスタマイズができる事の方が有益性が高いです。
私はもう「UIImagePickerController」には戻れません。
それではまた!