カメラのUIを自作する



# Abstract

カメラを実装する際、UIImagePickerControllerを使用するのが手っ取り早いですが、詳細の部分やデザインをもっとこだわってみたいときには、少しばかり物足りない...。実は最近、デザイナーさんとのやりとりの中で「真っ暗な撮影画面にカラーを入れてみたい」という要望があったので、ふと気になってその辺を調べてみました。すると、どうやらAVFoundation.frameworkを使って実装すれば、UIの部分も自由自在にデザインできることが分かったのでご紹介します。


# Main Specification

- 画面をタップするばシャッターが切れる。
- 撮影画面フレームを緑にする。 


# First Step


#### 1.プロジェクトに以下を読み込む。

 - **AVFoundation.framework**


#### 2.info.plistを編集する。
Info.plistの **Required device capabilities** という項目があるので、
使用する機能に応じて以下のキーを追加します。

**- still-camera**
**- auto-focus-camera**
**- video-camera**
- front-facing-camera
- camera-flash

（注）ちなみに正面カメラの使用についてコード上に記述せずにビルドしたら、
「おいおい、front-facing-cameraを使用してないぞ！！」と警告されました。





#### 3.StoryboardにUIを配置する。
（例） 以下のようなUIView上にプレビューを表示したいと思います。

![スクリーンショット 2016-08-14 18.57.06.png](https://qiita-image-store.s3.amazonaws.com/0/133774/5264ccad-23fb-9139-82ba-3b2e3d04cc03.png)





# Source Code


```objc++:ViewController.m

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
@property (nonatomic, strong) IBOutlet UIView *preView;
@property (nonatomic, strong) AVCaptureSession *session;

@end

@implementation ViewController {
    AVCaptureDeviceInput *_input;
    AVCaptureStillImageOutput *_output;
    AVCaptureDevice *_camera;
}


#pragma mark - Life cycle

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 画面タップでシャッターを切るための設定
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
    
    // デリゲートをセット
    tapGesture.delegate = self;
    
    // Viewに追加
    [self.view addGestureRecognizer:tapGesture];

}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // カメラの設定
    [self setupCamera];
    
}


/**
 *  撮影後にメモリを解放する
 */
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    //セッション終了
    [self.session stopRunning];
    
    for (_output in self.session.outputs) {
        [self.session removeOutput:_output];
    }
    
    for (_input in self.session.inputs) {
        [self.session removeInput:_input];
    }

    self.session = nil;
    _camera = nil;
}


#pragma mark - Camera setup

- (void)setupCamera {
    
    // セッション初期化
    self.session = [AVCaptureSession new];
    
    // カメラデバイスの初期化
    _camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    
    for ( AVCaptureDevice *device in [AVCaptureDevice devices] ) {
        
        //背面カメラを取得
        if (device.position == AVCaptureDevicePositionBack) {
            _camera = device;
        }
        
        // 前面カメラを取得
        //if (captureDevice.position == AVCaptureDevicePositionFront) {
        //    _camera = captureDevice;
        //}
    }
    

    /*  iOS8以上では、カメラのパーミッション許可を追加して下さい。   **/ 


    
    NSError *error = nil;
    _input = [[AVCaptureDeviceInput alloc] initWithDevice:_camera error:&error];
    
    //入力をセッションに追加
    if ([self.session canAddInput:_input]) {
        [self.session addInput:_input];
        [self.session beginConfiguration];
        //セッションの設定
        self.session.sessionPreset = AVCaptureSessionPresetPhoto;
        [self.session commitConfiguration];
    } else {
        NSLog(@"Error: %@", error);
    }

    
    // 静止画出力のインスタンス生成
    _output = [AVCaptureStillImageOutput new];

    // 出力をセッションに追加
    if ([self.session canAddOutput:_output]) {
        [self.session addOutput:_output];
    } else {
        NSLog(@"Error: %@",error);
    }

    // キャプチャーセッションから入力のプレビュー表示を作成
    AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
   
    previewLayer.frame = self.preView.bounds; 
    previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;  // <-- アスペクト比を変えない。レイヤーからはみ出した部分は隠す。
    //previewLayer.videoGravity = AVLayerVideoGravityResize;   <-- アスペクト比を変えてレイヤーに収める。
    //previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;   <-- アスペクト比を変えない。はみ出さないようにレイヤー内に収める。
    
    //UIViewの境界矩形外は描写しない
    self.preView.layer.masksToBounds = YES;

    // レイヤーをpreViewに設定
    // これを外すとプレビューが無くなる、けれど撮影はできる
    [self.preView.layer addSublayer:previewLayer];
    
    // セッション開始
    [self.session startRunning];
    
}


#pragma mark - UIAction

/**
 *  タップイベント処理
 */
- (void)tapped:(UITapGestureRecognizer *)sender {
    [self willTakePhoto];
}


/**
 *  出力データ処理
 */
- (void)willTakePhoto {

    //ビデオ出力に接続
    AVCaptureConnection *connection = [_output connectionWithMediaType:AVMediaTypeVideo];
    
    if (connection) {
    
        //出力画像の処理などを行う　  etc.保存処理
        
    }

}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


```

割とメモリー消費が激しいようなので、
念のためメモリーを解放させるようにしました。
また、iOS8以降ではプライバシー設定が厳格になっているので、
カメラへのアクセスに関するパーミッション変更の処理が必要です。

コード上でUITapGestureRecognizerを実装する場合は、Delegateをお忘れなく！

```objc++:ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIGestureRecognizerDelegate>

@end


```

# Let's Run!

こんな感じになります。

<img width="320" alt="Screen Shot 2016-08-14 at 20.09.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/133774/541123f3-7325-16c0-5e12-02e960cd6f16.png">

ちょっとダサいのはご勘弁下さい。



# Finally
今回はAVFoundationを使ったオリジナルの撮影画面の作成例をご紹介しました。AVFoundationを利用する場合、一見するとUIImagePickerControllerよりも設定する部分が増えるので複雑そうに見えます（きっとコードが汚いからです）。しかし、実際はカメラ撮影としての基本的な処理はシンプルです。そこをクリアできれば、きっとレイアウトやデザインに専念できるかと思います。ここで挙げた例では、背景色をグリーンに指定したのみでしたが、もしもオリジナルのボタン配置してみたい！撮影画面のフレームを可愛くしてみたい！という要望があれば、いつものようにStoryBoardで行っている労力さえあれば、とても素敵なカメラ撮影を提供できるかもしれません。