Swiftで写真を撮影して保存するiPhoneアプリを作成

iPhoneのカメラ機能を利用する入門的なアプリをサクッと作る。 フレームワークの各関数の使い方をよく忘れるのでメモ代わりに記録として残しておく。

画面

画面は1つのみで、プレビュー用のView(cameraView)とシャッター用のButton(take)の2つの部品だけを配置。 下図ではViewを正方形に配置しているが、実際に保存されるアスペクト比に近い形の方が良いとは思う。

f:id:Shoto:20170920011910p:plain

コード

以下のコードを実装して、iPhone繋いでBuild&Runして、takeボタン押せば、 プレビュー画面が写真としてカメラロールに保存される。 コードの解説については、コメントを参照。

import UIKit
import AVFoundation

class FirstViewController: UIViewController, AVCapturePhotoCaptureDelegate {
    // カメラ等のキャプチャに関連する入出力を管理するクラス
    var session: AVCaptureSession!
    // 写真データを取得するクラス
    var outout: AVCapturePhotoOutput?
    // カメラでキャプチャした映像をプレビューするクラス
    var previewLayer: AVCaptureVideoPreviewLayer?

    // カメラでキャプチャした映像をプレビューするエリア
    @IBOutlet weak var cameraView: UIView!

    // ロードされた直後に呼び出される(初回に一度のみ)
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    // 画面が表示される直前に呼び出される
    override func viewWillAppear(_ animated: Bool) {
        // キャプチャー入出力と写真データ取得のクラスの初期化
        session = AVCaptureSession()
        outout = AVCapturePhotoOutput()

        // 解像度の設定
        //session.sessionPreset = AVCaptureSessionPreset1920x1080
        session.sessionPreset = AVCaptureSessionPreset3840x2160

        // カメラの選択(背面・前面など)
        let camera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)

        do {
            // 指定したデバイスをセッションに入力
            let input = try AVCaptureDeviceInput(device: camera)

            // 入力
            if (session.canAddInput(input)) {
                session.addInput(input)

                // 出力
                if (session.canAddOutput(outout)) {
                    session.addOutput(outout)
                    session.startRunning() // カメラ起動

                    // プレビューレイヤーを生成
                    previewLayer = AVCaptureVideoPreviewLayer(session: session)
                    // cameraViewの境界をプレビューレイヤーのフレームに設定
                    previewLayer?.frame = cameraView.bounds
                    // アスペクト比変更とレイヤー収納の有無
                    previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill  // アスペクト比を変ない。レイヤーからはみ出した部分は隠す。
                    //previewLayer?.videoGravity = AVLayerVideoGravityResizeAspect  // アスペクト比を変えない。はみ出さないようにレイヤー内に収める。

                    // cameraViewのサブレイヤーにプレビューレイヤーを追加
                    cameraView.layer.addSublayer(previewLayer!)
                }
            }
        } catch {
            print(error)
        }
    }

    // ボタンをタップした時に呼ばれる
    @IBAction func takePhoto(_ sender: Any) {
        // 撮影設定
        let settingsForMonitoring = AVCapturePhotoSettings()
        settingsForMonitoring.flashMode = .auto  // フラッシュのモード
        settingsForMonitoring.isAutoStillImageStabilizationEnabled = true  // 手振れ補正
        settingsForMonitoring.isHighResolutionPhotoEnabled = false  // 最高解像度で撮影するか否か

        // シャッターを切る
        outout?.capturePhoto(with: settingsForMonitoring, delegate: self)
    }

    // AVCapturePhotoCaptureDelegateのデリゲート
    // カメラで撮影が完了した後呼ばれる。撮影データを加工したり、アルバムに保存したりする。
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?,
                 previewPhotoSampleBuffer: CMSampleBuffer?,
                 resolvedSettings: AVCaptureResolvedPhotoSettings,
                 bracketSettings: AVCaptureBracketedStillImageSettings?,
                 error: Error?) {

        if let photoSampleBuffer = photoSampleBuffer {
            // JPEG形式で画像データを取得
            let photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
            // UIImage型に変換
            let image = UIImage(data: photoData!)

            // フォトライブラリに保存
            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
        }
    }

    // 警告を受け取ったときに呼ばれる
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

トピックス

解像度の設定

// 解像度の設定
//session.sessionPreset = AVCaptureSessionPreset1920x1080
session.sessionPreset = AVCaptureSessionPreset3840x2160

もう少し写真の解像度を上げたいなどの場合は、ここで設定を行う。 指定できる解像度の種類は以下の通り。

Symbol Description
AVCaptureSessionPresetPhoto 高解像度の写真品質出力
AVCaptureSessionPresetHigh 高品質のビデオおよびオーディオ出力
AVCaptureSessionPresetMedium WiFi経由での共有出力ビデオおよびオーディオビットレート
AVCaptureSessionPresetLow 3G経由での共有出力ビデオおよびオーディオビットレート
AVCaptureSessionPreset320x240 320x240ピクセルビデオ出力
AVCaptureSessionPreset352x288 CIF画質(352x288ピクセル)ビデオ出力
AVCaptureSessionPreset640x480 VGA画質(640x480ピクセル)ビデオ出力
AVCaptureSessionPreset960x540 クオータHD品質(960x540ピクセル)ビデオ出力
AVCaptureSessionPreset1280x720 720p画質(1280x720ピクセル)のビデオ出力
AVCaptureSessionPreset1920x1080 1080p品質(1920x1080ピクセル)ビデオ出力
AVCaptureSessionPreset3840x2160 2160p(UHDまたは4Kとも呼ばれる)画質(3840x2160ピクセル)ビデオ出力
AVCaptureSessionPresetiFrame960x540 AACオーディオで960x540の高品質iFrame H.264ビデオを約30Mbits/secで実現する設定
AVCaptureSessionPresetiFrame1280x720 1280x720の高品質iFrame H.264ビデオをAACオーディオで約40 Mbits/secで実現する設定
AVCaptureSessionPresetInputPriority キャプチャセッションがオーディオおよびビデオ出力設定を制御しないことを指定

[iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた - Developers.IO より

アスペクト比変更とレイヤー収納の有無

// アスペクト比変更とレイヤー収納の有無
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill  // アスペクト比を変ない。レイヤーからはみ出した部分は隠す。
//previewLayer?.videoGravity = AVLayerVideoGravityResizeAspect  // アスペクト比を変えない。はみ出さないようにレイヤー内に収める。

プレビューの変更を行う。上記の設定ではcameraViewに実際に撮影される写真の一部しかプレビューで表示されないが、 設定によっては全部表示させることも可能。以下の3種類がある。

Symbol Description
AVLayerVideoGravityResize アスペクト比を変えてレイヤーに収める。
AVLayerVideoGravityResizeAspect アスペクト比を変えない。はみ出さないようにレイヤー内に収める。
AVLayerVideoGravityResizeAspectFill アスペクト比を変ない。レイヤーからはみ出した部分は隠す。

AVCaptureVideoPreviewLayer の Video Gravity3種 - 木木木 より

参考文献