카메라 UI를 직접 제작

추상



카메라를 구현할 때, 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
    - 카메라 플래시

    (주) 덧붙여서 전면 카메라의 사용에 대해서 코드상에 기술하지 않고 빌드하면,
    "이봐, front-facing-camera를 사용하지 않아!"라고 경고 받았다.

    3. 스토리 보드에 UI를 배치합니다.



    (예) 다음과 같은 UIView에 미리보기를 표시하고 싶습니다.



    Source Code



    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를 잊지 마세요!

    ViewController.h
    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController <UIGestureRecognizerDelegate>
    
    @end
    
    
    

    Let's Run!



    이런 느낌이 듭니다.



    조금 어색한 것은 용서해 주세요.

    Finally



    이번에는 AVFoundation을 사용한 오리지널 촬영 화면의 작성 예를 소개했습니다. AVFoundation을 이용하는 경우, 언뜻 보면 UIImagePickerController보다 설정하는 부분이 늘어나므로 복잡해 보입니다 (분명 코드가 더럽기 때문입니다). 그러나 실제로는 카메라 촬영으로서의 기본적인 처리는 간단합니다. 거기를 클리어 할 수 있으면, 반드시 레이아웃이나 디자인에 전념할 수 있을까 생각합니다. 여기에 든 예에서는 배경색을 그린으로 지정했을 뿐이었습니다만, 만약 오리지날의 버튼 배치해 보고 싶다! 촬영 화면의 프레임을 귀엽게 해보고 싶다! 라고 하는 요망이 있으면, 언제나처럼 StoryBoard에서 하고 있는 노력만 있으면, 매우 멋진 카메라 촬영을 제공할 수 있을지도 모릅니다.

    좋은 웹페이지 즐겨찾기