【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法

この記事からわかること

  • Swift/UIKitカメラアプリ実装方法
  • AVFoundation使い方特徴メリット
  • カスタマイズできるカメラビュー作り方
  • AVCaptureSessionとは?
  • AVCaptureDeviceInputAVCapturePhotoOutputとは?
  • UIImagePickerControllerとの違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

AVFoundationとは?

公式リファレンス:AVFoundation Framework

AVFoundationとはAppleプラットフォームにおける画像や動画、音声などのオーディオビジュアル(AV)メディアの再生や操作、デバイスカメラの制御といった機能を包括するフレームワークです。

AVFoundationを活用することでアプリ内から写真や動画の撮影、再生、編集、検査、再生だけでなく、派生してバーコードの読み取りなども可能になります。

おすすめ記事:【Swift/AVFoundation】バーコード読取機能の作り方!JANやISBNコード

カメラを起動して写真を撮影する方法

Swiftを使って自作したアプリ内からカメラを起動して写真を撮影する方法は2つ存在します。

方法1:UIImagePickerControllerクラス

1つ目はUIImagePickerControllerクラスを使用した方法です。フレームワークなどを導入せずに実装でき、コードもシンプルなので実装の手軽さでは圧倒的に軍配が上がります。

またiOSの「カメラ」アプリのようなUIをそのまま使用できるのもメリットの1つです。

【Swift UIKit】アプリ内からカメラで写真を撮影する方法!

おすすめ記事:【Swift/UIImagePickerController】アプリ内からカメラで写真を撮影する方法!

方法2:AVFoundation

2つ目はAVFoundationを使用した方法です。実装するのはなかなか手間がかかりますがカスタマイズ性が高く柔軟なカメラアプリを開発することができます。

撮影ボタンの位置やカメラビューのサイズなどを好きなように配置できるので独自のカメラアプリを作成するのにおすすめです。

【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法

AVCaptureSession

公式リファレンス:AVCaptureSession

AVFoundationを使用して写真撮影を行うためにはAVCaptureSessionクラスが重要になってきます。まずはこの取り扱いを整理しておきます。

AVFoundationでは明示的にinputsとoutputsを指定する必要があり、それを管理するためのクラスが用意されています。

  1. inputs:入力ソースのこと。カメラやマイクなどデバイスからデータを取得する方法
  2. outputs:出力形式のこと。入力されたデータをどのように出力するか

inputsを管理しているクラスがAVCaptureDeviceInput、outputsを管理しているクラスがAVCaptureOutputになります。

そしてその2つの仲介役を行なっているのがAVCaptureSessionクラスになります。

【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法

AVCaptureSession実装の流れ

  1. AVCaptureDeviceインスタンスを生成
  2. AVCaptureDeviceインスタンスからVCaptureDeviceInputを構築
  3. AVCaptureSessionにAVCaptureDeviceInputを登録
  4. AVCaptureSessionにAVCaptureOutputを登録
  5. AVCaptureVideoPreviewLayerで画面を構築

1.AVCaptureDeviceInputの構築

公式リファレンス:AVCaptureDevice

AVCaptureDeviceInputインスタンスを構築するためにはAVCaptureDeviceクラスを用いてまず使用するデバイスを設定する必要があります。

使用するAVCaptureDeviceインスタンスを生成するには以下の2つのどちらかを使用します。

  1. AVCaptureDevice.defaultメソッド
  2. AVCaptureDevice.DiscoverySessionクラス

後者を使用することでカメラの種類や位置などを柔軟にカスタマイズして指定することが可能になります。

1-1.AVCaptureDevice.defaultメソッド

公式リファレンス:`default`メソッド

defaultメソッドは引数に指定されたタイプのデフォルトデバイスを返します。引数にはAVMediaType型の任意の値を渡します。

公式リファレンス:AVMediaType

guard let audioDevice = AVCaptureDevice.default(for: .audio) else { return }

guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }

1-2.AVCaptureDevice.DiscoverySession

公式リファレンス:AVCaptureDevice.DiscoverySession

AVCaptureDevice.DiscoverySession特定の条件にマッチするAVCaptureDeviceを検索するクラスです。引数にはデバイスタイプ(カメラの種類)とメディアタイプ(videoやaudioなど)、ポジション(カメラの位置)を渡します。

 // カメラデバイスのプロパティ設定
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(
  deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], 
  mediaType: AVMediaType.video, 
  position: AVCaptureDevice.Position.unspecified)
// プロパティの条件を満たしたカメラデバイスの取得
let devices = deviceDiscoverySession.devices

1-2-1.AVCaptureDevice.DeviceType

1つ目の引数ではAVCaptureDevice.DeviceType構造体の任意の値を渡します。

.builtInWideAngleCamera // ワイドカメラ
.builtInUltraWideCamera // 近接カメラ
.builtInTelephotoCamera // 望遠カメラ
.builtInDualCamera      // デュアルカメラ
.builtInDualWideCamera  // デュアルワイドカメラ
.builtInTripleCamera    // トリプルカメラ

iPhoneの機種によっては対応していない値もあるので注意が必要です。

positionカメラの位置を指定します。

AVCaptureDevice.Position

.front // 前面カメラ
.back  // 背面カメラ
.unspecified // 未指定

2.AVCaptureDeviceインスタンスからVCaptureDeviceInputを構築

設定したデバイスを元にAVCaptureDeviceInputインスタンスを生成します。またAVCaptureSessionインスタンスもここで生成しておきます。このインスタンスに対してInputとOutputを登録していきます。

// 方法1
guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }
// or
// 方法2
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
let videoDevice = deviceDiscoverySession.devices

// セッションの生成
self.avSession = AVCaptureSession()

do {
    // 1 or 2 でデバイスを設定後AVCaptureDeviceInputを生成
    let deviceInput = try AVCaptureDeviceInput(device: videoDevice)

    // avInputを登録
    // avOutputを登録
  } catch {
    print(error.localizedDescription)
}

3.AVCaptureSessionにAVCaptureDeviceInputを登録

Inputを登録する際はcanAddInputメソッドを使用して追加が可能かを識別し、問題なければ登録します。

if self.avSession.canAddInput(deviceInput) {
    self.avSession.addInput(deviceInput)
    self.avInput = deviceInput
    
    // avOutputを登録
}

4.AVCaptureSessionにAVCaptureOutputを登録

Outputも同様にcanAddOutputメソッドを使用して追加が可能かを識別し、問題なければ登録します。

let photoOutput = AVCapturePhotoOutput()
if self.avSession.canAddOutput(photoOutput) {
    self.avSession.addOutput(photoOutput)
    self.avOutput = photoOutput

    // AVCaptureVideoPreviewLayerで画面を構築
}

5.AVCaptureVideoPreviewLayerで画面を構築

公式リファレンス:AVCaptureVideoPreviewLayer

カメラデバイスからビューを表示するためのレイヤーを構築します。

let previewLayer = AVCaptureVideoPreviewLayer(session: self.avSession)
previewLayer.frame = self.photoView.bounds
previewLayer.videoGravity = .resizeAspectFill
self.avSession.sessionPreset = AVCaptureSession.Preset.photo
self.view.layer.addSublayer(previewLayer)

最後にstartRunningメソッドを実行します。このメソッドでセッションの入力から出力へのデータフローを開始します。

self.avSession.startRunning()

ここまでがカメラ撮影機能を実装する基本の流れです。ここからは実際に使用できるサンプルを作成してみます。

AVFoundationの実装の流れ

  1. 「info.plist」にNSCameraUsageDescriptionキーを追加
  2. カメラ起動ボタン画面の構築
  3. カメラなどへのアクセス承認申請アラートの実装
  4. カメラUI画面の構築
  5. カメラを起動する処理を定義
  6. 撮影後の処理を実装

今回はサンプルとしてAVFoundationを使ってカメラを起動後撮影した画像をフォトライブラリに保存するアプリを作っていきたいと思います。フォトライブラリ操作のためにPhotosフレームワークも使用します。

おすすめ記事:【Swift/PhotoKit】デバイスに写真を保存・削除・更新する方法!

また今回作成するUIViewControllerクラスは2つです。

  1. MainViewController:カメラ起動ボタン設置画面
  2. AudioViewController:カメラ画面

Swift UIで作成したい場合は以下の記事を参考にしてください。

1.info.plistにNSCameraUsageDescriptionキーを追加

アプリ内からデバイスのカメラにアクセスするためには「info.plist」に「NSCameraUsageDescription」キーを追加する必要があります。

【Swift UIKit】アプリ内からカメラで写真を撮影する方法!

「info.plist」を開いたらKeyにNSCameraUsageDescriptionと入力し、Valueにはカメラ使用する旨を記載しておきます。自動でPrivacy - Camera Usage Descriptionに変換されます。

また今回はPhotosフレームワークを使用してフォトライブラリも操作したいのでデバイスの写真アプリにアクセスできるように「info.plist」に「NSPhotoLibraryUsageDescription」キーも追加しておきます。

【Swift UIKit】画像をカメラロールから保存/取得/削除する方法!

2.カメラ起動ボタン画面の構築

まずはカメラ起動ボタンを配置するビューを構築します。ここはシンプルにボタンの配置と承認申請アラートを表示させるようのメソッドを準備しておきます。


import UIKit
import Photos
import AVFoundation

class MainViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton()
        button.backgroundColor = .orange
        button.frame = CGRect(x: UIScreen.main.bounds.width/3, y: 200, width: UIScreen.main.bounds.width/3, height: 50)
        button.setTitle("カメラ起動", for: .normal)
        button.addTarget(self, action: #selector(tapedAudio), for: .touchUpInside)
        view.addSubview(button)
        
        allowedRequestStatus()
    }
    
    @objc  func tapedAudio(sender: UIButton) {
        self.present(AudioViewController(), animated: true, completion: nil)
    }
    
    func allowedRequestStatus() -> Bool{

    }   
}

3.カメラなどへのアクセス承認申請アラートの実装

カメラなどへのアクセスはユーザーに明示的な許可が必要になります。各クラスのメソッドには承認状態を取得するメソッドと以下のような承認申請アラートを表示するメソッドが用意されています。

【Swift】PhotoKitの使い方と写真アプリ操作方法!画像や動画の選択や保存

カメラへのアクセス承認状態取得

AVCaptureDevice.authorizationStatus(for: .video)

カメラへのアクセス承認申請アラート表示

AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
            
})

フォトライブラリへのアクセス承認状態取得

PHPhotoLibrary.authorizationStatus(for: .addOnly)

フォトライブラリへのアクセス承認申請アラート表示

PHPhotoLibrary.requestAuthorization(for: .addOnly, handler: { status in
})

おすすめ記事:【Swift/PhotoKit】#承認状態を取得する

これらを駆使して現在の承認状態を取得し必要であれば申請を表示させるようにしておきます。

func allowedRequestStatus() -> Bool{
    var avState = false
    var phState = false
    
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        avState = true
        break
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
            DispatchQueue.main.async {
                avState = granted
            }
        })
    default:
        avState = false
        break
    }
    
    switch PHPhotoLibrary.authorizationStatus(for: .addOnly) {
    case .authorized:
        phState = true
        break
        
    case .notDetermined:
        PHPhotoLibrary.requestAuthorization(for: .addOnly, handler: { status in
            DispatchQueue.main.async {
                if status == .authorized {
                    phState = true
                }
            }
        })
    default :
        phState = false
        break
    }
    
    if avState && phState {
        return true
    }else{
        return false
    }
}

4.カメラUI画面の構築

続いてカメラUI画面を構築します。AVFoundationではカメラ画面(カメラビューや撮影ボタンなど)も自分でカスタマイズして実装できるのでそのために必要となるphotoView(カメラ画面)とshutterBtn(撮影ボタン)を定義しておきます。

さらに後述しますがAVFoundationで必要となる3つのプロパティもここで定義しておきます。


import UIKit
import Photos
import AVFoundation

class AudioViewController: UIViewController , AVCapturePhotoCaptureDelegate, AVCaptureMetadataOutputObjectsDelegate {
    
    var photoView:UIImageView! = UIImageView()
    let shutterBtn:UIButton! = UIButton() // これは後からaddSubviewします
    
    var avSession: AVCaptureSession!
    var avInput: AVCaptureDeviceInput!
    var avOutput: AVCapturePhotoOutput!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        photoView.frame = CGRect(x: 0 ,y:0, width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.height)
        self.view.addSubview(photoView)
    }

    func allowedStatus() -> Bool{
      //
    }

    func setupAVCapture() {
      // 
    }

    @objc  func takePictureTapped(_ sender: UIButton) {
      // 
    }
}

ここで実装するメソッドは以下の3つです。

  1. allowedStatus:承認状態識別(許可ならsetupAVCapture呼び出し)
  2. setupAVCapture:カメラ画面構築
  3. takePictureTapped:撮影ボタン

カメラを起動する処理を定義

allowedStatusメソッド

これは承認状態を識別して真偽値を返すメソッドです。

func allowedStatus() -> Bool{
    var avState = false
    var phState = false
    
    if AVCaptureDevice.authorizationStatus(for: .video) == .authorized{
        avState = true
    }
    
    if  PHPhotoLibrary.authorizationStatus(for: .addOnly) == .authorized{
        phState = true
    }
    
    if avState && phState {
        return true
    }else{
        return false
    }
}

setupAVCapture

ここでは実際にカメラ画面を使用するための準備とUIの配置を行います。AVCaptureSessionで解説した流れで実装しています。また撮影ボタンのUI部品も追加しておきます。

 func setupAVCapture() {
        
  self.avSession = AVCaptureSession()
  guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }

      do {
          
          let deviceInput = try AVCaptureDeviceInput(device: videoDevice)
          
          if self.avSession.canAddInput(deviceInput) {
              self.avSession.addInput(deviceInput)
              self.avInput = deviceInput
              
              let photoOutput = AVCapturePhotoOutput()
              if self.avSession.canAddOutput(photoOutput) {
                  self.avSession.addOutput(photoOutput)
                  self.avOutput = photoOutput
                  
                  let previewLayer = AVCaptureVideoPreviewLayer(session: self.avSession)
                  previewLayer.frame = self.photoView.bounds
                  previewLayer.videoGravity = .resizeAspectFill
                  self.avSession.sessionPreset = AVCaptureSession.Preset.photo
                  self.view.layer.addSublayer(previewLayer)
                  
                  // 撮影ボタン
                  shutterBtn.frame = CGRect(x: 20, y: 20, width: 60, height: 60)
                  shutterBtn.setTitle("", for: UIControl.State.normal)
                  shutterBtn.backgroundColor = UIColor.white
                  shutterBtn.addTarget(self, action: #selector(takePictureTapped), for: .touchUpInside)
                  shutterBtn.layer.cornerRadius = 30
                  self.view.addSubview(shutterBtn)
                  
                  self.avSession.startRunning()
              }
          }
      } catch {
          print(error.localizedDescription)
      }
}

takePictureTapped:撮影ボタン

ここではカメラ画面に配置する撮影ボタンのアクションを記述します。capturePhotoメソッドが実際に撮影を実行するメソッドです。

@objc  func takePictureTapped(_ sender: UIButton) {
    let settings = AVCapturePhotoSettings()
    settings.flashMode = .auto
    settings.isHighResolutionPhotoEnabled = false
    self.avOutput?.capturePhoto(with: settings, delegate: self)
}

撮影後の処理を実装

最後に撮影した画像をデバイスに保存する処理を実装します。これはPhotoKitの機能を使用します。詳しい実装方法は以下の記事を参考にしてください。

おすすめ記事:【Swift/PhotoKit】デバイスに写真を保存・削除・更新する方法!

また撮影後の処理を実装するためにAVCapturePhotoCaptureDelegateプロトコルに準拠させphotoOutputメソッド(delegateメソッド)が呼び出せるようにしておきます。

公式リファレンス:AVCapturePhotoCaptureDelegate

func photoOutput(_ output: AVCapturePhotoOutput,
                  didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    if let imageData = photo.fileDataRepresentation() {
        PHPhotoLibrary.shared().performChanges {
              PHAssetChangeRequest.creationRequestForAsset(from:UIImage(data: imageData)!)
        } completionHandler: { success, error in
            print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
        }
    }
}

これで全ての実装が完了しました。シミュレーターではテストできないので実機にビルドして試してみてください。

【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法

全体のコード


import UIKit
import Photos
import AVFoundation

class MainViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton()
        button.backgroundColor = .orange
        button.frame = CGRect(x: UIScreen.main.bounds.width/3, y: 200, width: UIScreen.main.bounds.width/3, height: 50)
        button.setTitle("カメラ起動", for: .normal)
        button.addTarget(self, action: #selector(tapedAudio), for: .touchUpInside)
        view.addSubview(button)
        
        allowedRequestStatus()
    }
    
    @objc  func tapedAudio(sender: UIButton) {
        self.present(AudioViewController(), animated: true, completion: nil)
    }
    
    func allowedRequestStatus() -> Bool{
        var avState = false
        var phState = false
        
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            avState = true
            break
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
                DispatchQueue.main.async {
                    avState = granted
                }
            })
        default:
            avState = false
            break
        }
        
        switch PHPhotoLibrary.authorizationStatus(for: .addOnly) {
        case .authorized:
            phState = true
            break
            
        case .notDetermined:
            PHPhotoLibrary.requestAuthorization(for: .addOnly, handler: { status in
                DispatchQueue.main.async {
                    if status == .authorized {
                        phState = true
                    }
                }
            })
        default :
            phState = false
            break
        }
        
        if avState && phState {
            return true
        }else{
            return false
        }
    }   
}

import UIKit
import Photos
import AVFoundation

class AudioViewController: UIViewController , AVCapturePhotoCaptureDelegate, AVCaptureMetadataOutputObjectsDelegate {
    
    let photoView:UIImageView! = UIImageView()
    let shutterBtn:UIButton! = UIButton()

    var avSession: AVCaptureSession!
    var avInput: AVCaptureDeviceInput!
    var avOutput: AVCapturePhotoOutput!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        photoView.frame = CGRect(x: 0 ,y:0, width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.height)
        self.view.addSubview(photoView)

        if allowedStatus() {
            setupAVCapture()
        }
    }
    
    func allowedStatus() -> Bool{
        var avState = false
        var phState = false
        
        if AVCaptureDevice.authorizationStatus(for: .video) == .authorized{
            avState = true
        }
        
        if  PHPhotoLibrary.authorizationStatus(for: .addOnly) == .authorized{
            phState = true
        }
        
        if avState && phState {
            return true
        }else{
            return false
        }
    }

    func setupAVCapture() {
        
        self.avSession = AVCaptureSession()
        guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }

            do {
                
                let deviceInput = try AVCaptureDeviceInput(device: videoDevice)
                
                if self.avSession.canAddInput(deviceInput) {
                    self.avSession.addInput(deviceInput)
                    self.avInput = deviceInput
                    
                    let photoOutput = AVCapturePhotoOutput()
                    if self.avSession.canAddOutput(photoOutput) {
                        self.avSession.addOutput(photoOutput)
                        self.avOutput = photoOutput
                        
                        let previewLayer = AVCaptureVideoPreviewLayer(session: self.avSession)
                        previewLayer.frame = self.photoView.bounds
                        previewLayer.videoGravity = .resizeAspectFill
                        self.avSession.sessionPreset = AVCaptureSession.Preset.photo
                        self.view.layer.addSublayer(previewLayer)
                        
                        // 撮影ボタン
                        shutterBtn.frame = CGRect(x: 20, y: 20, width: 60, height: 60)
                        shutterBtn.setTitle("", for: UIControl.State.normal)
                        shutterBtn.backgroundColor = UIColor.white
                        shutterBtn.addTarget(self, action: #selector(takePictureTapped), for: .touchUpInside)
                        shutterBtn.layer.cornerRadius = 30
                        self.view.addSubview(shutterBtn)
                        
                        self.avSession.startRunning()
                    }
                }
            } catch {
                print(error.localizedDescription)
            }
    }
    
    @objc  func takePictureTapped(_ sender: UIButton) {
        let settings = AVCapturePhotoSettings()
        settings.flashMode = .auto
        settings.isHighResolutionPhotoEnabled = false
        self.avOutput?.capturePhoto(with: settings, delegate: self)
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput,
                     didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let imageData = photo.fileDataRepresentation() {
            PHPhotoLibrary.shared().performChanges {
                  PHAssetChangeRequest.creationRequestForAsset(from:UIImage(data: imageData)!)
            } completionHandler: { success, error in
                print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
            }
        }
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index