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

# はじめに
久しぶりにAVFoundation.frameworkを利用したカメラを触る機会がありました。
他にも、UIKit.framework（UIImagePickerController）を利用するパターンもあります。
[参考記事](http://qiita.com/satoshi_imanishi/items/0ddd89d7249c00224d59)

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

## 非推奨
Warningが出たものは下記の2箇所でした。

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

```swift:〜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
        }
    }
```

```swift: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〜)
カメラのインスタンスの取得方法が変わった。

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

        guard let device = AVCaptureDevice.devices().filter({
            ($0 as! AVCaptureDevice).position == position
        }).first as? AVCaptureDevice else {
            fatalError("カメラが見つかりません")
        }
        return device
    }
```

```swift:iOS10〜
    func findDevice(position: AVCaptureDevicePosition) -> AVCaptureDevice? {

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

```

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

完成版のソースコードは、[こちら](https://github.com/stv-ekushida/ios10-camera-demo)も御覧ください。

### 実装手順
#### 1. AVFoundation.frameworkを追加する
#### 2. info.plistに下記を追加する

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

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

```swift: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周りは割愛します。

```swift: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は見るものの、実装する機会がないと見落としてしまいますね。
たまにシンプルな機能を実装してみるのも勉強になりました。

以上です。