LoginSignup
13
16

More than 5 years have passed since last update.

[iOS10] カメラを触ってみる

Posted at

はじめに

久しぶりにAVFoundation.frameworkを利用したカメラを触る機会がありました。
他にも、UIKit.framework(UIImagePickerController)を利用するパターンもあります。
参考記事

今更感はありますが、
iOS10で、非推奨になっているクラスがあったので、整理しておきます。

非推奨

Warningが出たものは下記の2箇所でした。

① AVCaptureStillImageOutput -> AVCapturePhotoOutput(iOS10.0〜)
撮影および、完了通知の仕組みが変わった。

〜iOS9
    let imageOutput = AVCaptureStillImageOutput()

    func takePhoto() {

        let videoConnection = imageOutput.connection(withMediaType: AVMediaTypeVideo)

        imageOutput.captureStillImageAsynchronously(
            from: videoConnection,
            completionHandler: { [weak self]
                (imageDataBuffer, error) in

            if let e = error {
                print(e.localizedDescription)
                return
            }
            self?.savePhoto(imageDataBuffer: imageDataBuffer!)
        })
    }

    func savePhoto(imageDataBuffer: CMSampleBuffer) {

        if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataBuffer),
            let image = UIImage(data: imageData) {
            //TODO : do something
        }
    }
iOS10〜
    let imageOutput = AVCapturePhotoOutput()

    func takePhoto() {
        imageOutput.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
    }


// MARK: - AVCapturePhotoCaptureDelegate
extension CameraUtil: AVCapturePhotoCaptureDelegate {

    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?,
                 previewPhotoSampleBuffer: CMSampleBuffer?,
                 resolvedSettings: AVCaptureResolvedPhotoSettings,
                 bracketSettings: AVCaptureBracketedStillImageSettings?,
                 error: Error?) {

                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                savePhoto(imageDataBuffer: photoSampleBuffer!)
        }
    }

    func savePhoto(imageDataBuffer: CMSampleBuffer) {

        if let imageData =
            AVCapturePhotoOutput.jpegPhotoDataRepresentation(
                forJPEGSampleBuffer: imageDataBuffer,
                previewPhotoSampleBuffer: nil),
            let image = UIImage(data: imageData) {

            //TODO : do something
        }
    }
}

② AVCaptureDevice.devices() -> AVCaptureDeviceDiscoverySession(iOS10.0〜)
カメラのインスタンスの取得方法が変わった。

〜iOS9
    func findDevice(position: AVCaptureDevicePosition) -> AVCaptureDevice? {

        guard let device = AVCaptureDevice.devices().filter({
            ($0 as! AVCaptureDevice).position == position
        }).first as? AVCaptureDevice else {
            fatalError("カメラが見つかりません")
        }
        return device
    }
iOS10〜
    func findDevice(position: AVCaptureDevicePosition) -> AVCaptureDevice? {

        return AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera,
                                             mediaType: AVMediaTypeVideo,
                                             position: position)
    }

サンプル

アプリ起動直後にカメラが起動し、
撮影ボタンを押下するとカメラロールに保存するだけのサンプルです。
Deployment Targetを10.0以上とします。

完成版のソースコードは、こちらも御覧ください。

実装手順

1. AVFoundation.frameworkを追加する

2. info.plistに下記を追加する

info.plist
    <key>NSCameraUsageDescription</key>
    <string>カメラへアクセスするために必要です</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>アルバムへアクセスするために必要です</string>

3. カメラへアクセスするためのクラスを定義する

CameraUtil.swift
import UIKit
import AVFoundation

final class CameraUtil: NSObject {

    let imageOutput = AVCapturePhotoOutput()

    func findDevice(position: AVCaptureDevicePosition) -> AVCaptureDevice? {

        return AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera,
                                             mediaType: AVMediaTypeVideo,
                                             position: position)
    }

    func createView(session: AVCaptureSession?,
                                  device: AVCaptureDevice?) -> AVCaptureVideoPreviewLayer?{

        let videoInput = try! AVCaptureDeviceInput.init(device: device)
        session?.addInput(videoInput)
        session?.addOutput(imageOutput)
        return AVCaptureVideoPreviewLayer.init(session: session)
    }

    func takePhoto() {
        imageOutput.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
    }
}

// MARK: - AVCapturePhotoCaptureDelegate
extension CameraUtil: AVCapturePhotoCaptureDelegate {

    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?,
                 previewPhotoSampleBuffer: CMSampleBuffer?,
                 resolvedSettings: AVCaptureResolvedPhotoSettings,
                 bracketSettings: AVCaptureBracketedStillImageSettings?,
                 error: Error?) {

                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                savePhoto(imageDataBuffer: photoSampleBuffer!)
    }

    func savePhoto(imageDataBuffer: CMSampleBuffer) {

        if let imageData =
            AVCapturePhotoOutput.jpegPhotoDataRepresentation(
                forJPEGSampleBuffer: imageDataBuffer,
                previewPhotoSampleBuffer: nil),
            let image = UIImage(data: imageData) {

            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
    }
}

4. 使ってみる

UI周りは割愛します。

ViewController.swift
import UIKit
import AVFoundation

final class ViewController: UIViewController {

    @IBOutlet weak var baseView: UIView!
    let session = AVCaptureSession()
    let camera = CameraUtil()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupCameraView()
        session.startRunning()
    }

    private func setupCameraView() {

        let device = camera.findDevice(position: .back)

        if let videoLayer = camera.createView(session: session, device: device) {

            videoLayer.frame = baseView.bounds
            videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            baseView.layer.addSublayer(videoLayer)
        } else {
            fatalError("VideoLayerがNil")
        }
    }

    @IBAction func photoDidTap(_ sender: UIButton) {
        camera.takePhoto()
    }
}

まとめ

API diffは見るものの、実装する機会がないと見落としてしまいますね。
たまにシンプルな機能を実装してみるのも勉強になりました。

以上です。

13
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
16