対象:
Swift4

カメラの利用(AVFoundation)(Swift)

iOSでカメラを利用して、加えて画像処理等も行いたい場合、AVFoundationを利用することになるだろう。

AVFoundationを利用せずとも、UIImagePickerControllerを使えばより簡単にカメラの機能を利用できる。とにかくカメラを使ってみたいという場合はこちらを利用しても良いだろう。

また、iOS 10以降でカメラを使用する場合、Info.plistでNSCameraUsageDescriptionキーにカメラ初回使用時のメッセージを設定等しておく必要がある。それについても以下をご参照いただきたい。

AVFoundationの機能を利用してカメラを利用するには、ViewController.swiftの冒頭でAVFoundationをimportする。

import UIKit
import AVFoundation

AVFoundationで重要となるのがセッション(AVCaptureSession)である。これを媒介として背面カメラとAVCaptureStillImageOutput等の出力をつなぐことになる。まずは背面カメラの取得である。

        // バックカメラを選択
        for d in AVCaptureDevice.devices() {
            // Swift 3まで
            // if (d as AnyObject).position == AVCaptureDevicePosition.back {
            if (d as AnyObject).position == AVCaptureDevice.Position.back {
                // Swift 3まで
                // device = d as? AVCaptureDevice
                device = d as AVCaptureDevice
                print("\(device!.localizedName) found.")
            }
        }

取得した背面カメラからセッションにつなぐ入力を生成する。

        // バックカメラからキャプチャ入力生成
        // Swift 3まで
        // let input: AVCaptureDeviceInput?
        guard let input = try? AVCaptureDeviceInput(device: device) else {
            print("Caught exception!")
            return
        }

後は、先ほどの入力と出力をセッションにつなぎ、プレビュー用のAVCaptureVideoPreviewLayerを生成してViewControllerにサブレイヤとして追加する。最後にセッションをstartRunningすれば完成である。

コード全体を以下に示す。

import UIKit
import AVFoundation

class ViewController: UIViewController {
    var device: AVCaptureDevice!
    var session: AVCaptureSession!
    var output: AVCaptureStillImageOutput!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // セッションを生成
        session = AVCaptureSession()
        // バックカメラを選択
        for d in AVCaptureDevice.devices() {
            // Swift 3まで
            // if (d as AnyObject).position == AVCaptureDevicePosition.back {
            if (d as AnyObject).position == AVCaptureDevice.Position.back {
                // Swift 3まで
                // device = d as? AVCaptureDevice
                device = d as AVCaptureDevice
                print("\(device!.localizedName) found.")
            }
        }
        // バックカメラからキャプチャ入力生成
        // Swift 3まで
        // let input: AVCaptureDeviceInput?
        guard let input = try? AVCaptureDeviceInput(device: device) else {
            print("Caught exception!")
            return
        }
        session.addInput(input)
        output = AVCaptureStillImageOutput()
        session.addOutput(output)
        // Swift 3まで
        // session.sessionPreset = AVCaptureSessionPresetPhoto
        session.sessionPreset = .photo
        // プレビューレイヤを生成
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        // Swift 3まで
        // previewLayer?.frame = view.bounds
        // view.layer.addSublayer(previewLayer!)
        previewLayer.frame = view.bounds
        view.layer.addSublayer(previewLayer)
        // セッションを開始
        session.startRunning()
        // 撮影ボタンを生成
        let button = UIButton()
        button.setTitle("撮影", for: .normal)
        button.contentMode = .center
        button.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
        button.backgroundColor = UIColor.blue
        //button.tintColor = UIColor.grayColor()
        //button.setTitleColor(UIColor.blue, for: UIControlState())
        button.layer.position = CGPoint(x: view.frame.width / 2, y: self.view.bounds.size.height - 80)
        button.addTarget(self, action: #selector(ViewController.shot(_:)), for: .touchUpInside)
        view.addSubview(button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // Swift 3まで
    // func shot(_ sender: AnyObject) {
    @objc func shot(_ sender: AnyObject) {
        // Swift 3まで
        // let connection = output.connection(withMediaType: AVMediaTypeVideo)
        // output.captureStillImageAsynchronously(from: connection) {(imageDataBuffer, error) -> Void in
        //     let imageData: Data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataBuffer)
        let connection = output.connection(with: .video)
        output.captureStillImageAsynchronously(from: connection!) {(imageDataBuffer, error) -> Void in
            if error == nil, let imageData: Data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataBuffer!) {
                let image = UIImage(data: imageData)!
                // 画像をカメラロールに保存
                UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)
            }
        }
    }

}

作成したアプリを実行すると、以下のような画面が表示される。


ここまでは、既にDeprecatedになっているAPIも使ってiOS 9以上で動作するように書いたコードである。DeprecatedになったAPIには、それに代わる新しいAPIもあるにはあるのだが、画像周りに関してはiOS 10で追加されたAPIがiOS 11でもうDeprecatedになったりしていてなかなか手を出しにくい。とは言え、最新のAPIを使って同様のものを書くと若干すっきりと書くことができる。

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {
    var device: AVCaptureDevice!
    var session: AVCaptureSession!
    var output: AVCapturePhotoOutput!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // セッションを生成
        session = AVCaptureSession()
        // バックカメラを選択
        //device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first
        device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
        // バックカメラからキャプチャ入力生成
        guard let input = try? AVCaptureDeviceInput(device: device) else {
            print("Caught exception!")
            return
        }
        session.addInput(input)
        output = AVCapturePhotoOutput()
        session.addOutput(output)
        session.sessionPreset = .photo
        // プレビューレイヤを生成
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.frame = view.bounds
        view.layer.addSublayer(previewLayer)
        // セッションを開始
        session.startRunning()
        // 撮影ボタンを生成
        let button = UIButton()
        button.setTitle("撮影", for: .normal)
        button.contentMode = .center
        button.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
        //button.backgroundColor = UIColor.blue
        button.backgroundColor = UIColor(red: 0, green: 0, blue: 1.0, alpha: 0.5)
        //button.tintColor = UIColor.grayColor()
        //button.setTitleColor(UIColor.blue, for: UIControlState())
        button.layer.position = CGPoint(x: view.frame.width / 2, y: self.view.bounds.size.height - 80)
        button.addTarget(self, action: #selector(ViewController.shot(_:)), for: .touchUpInside)
        view.addSubview(button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @objc func shot(_ sender: AnyObject) {
        // JPEG
        let settings = AVCapturePhotoSettings(/*format: [AVVideoCodecKey : AVVideoCodecType.jpeg]*/)
        // 何も指定しなくてもフォーマットはJPEGになってるみたい
        print(settings.format)
        output.capturePhoto(with: settings, delegate: self)
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if error == nil {
            UIImageWriteToSavedPhotosAlbum(UIImage(data: photo.fileDataRepresentation()!)!, self, nil, nil)
        }
    }

}
(2016/05/09)
() iOS 11最新API版も記載。

新着情報
【iOS Objective-C, Swift Tips】ファイルアップロード(Swift)
【オープンソースソフトウェア環境構築】Amazon S3にアクセスするSparkアプリケーション
【iOS Objective-C, Swift Tips】ジオコーディング(Swift)

Copyright© 2004-2018 モバイル開発系(K) All rights reserved.
[Home]