Codable Tech Blog

iPhoneアプリケーション開発と AWS(Amazon Web Service)活用に関する記事を配信

UIコンポーネントを拡張してStoryBoardで出来るようにする

ユースケース

たとえばUIButtonを使って枠線を表示したいとき、ストーリーボード上の既存の設定値だけでは枠線を表示することができません。このような場合、ソースコードからプロパティをいじって枠線を追加するのが一般的なやり方です。しかし、UIに関してはストーリーボードで一括管理していたほうがあとからデザインを変更するのも簡単です。本来、ストーリーボードの設定項目にないものをストーリーボード上で編集できるようにするのがIBInspectable属性です。

IBInspectable属性

IBInspectable属性はストーリーボード上にカスタム項目を追加するための属性です。クラスのプロパティに対してIBInspectable属性を指定することでストーリーボード上に新たな属性を追加することができます。
たとえば枠線の設定ができるUIボタンをストーリーボード上で表示で使いたいとします。その場合、まずはUIButtonクラスを継承したクラスに対して枠線に関するプロパティをIBInspectable属性で指定します。

class BorderCustomButton: UIButton {
     @IBInspectable var cornerRadius: CGFloat = 0 {
        didSet {
            layer.cornerRadius = cornerRadius
        }
    }

    @IBInspectable var borderWidth: CGFloat = 0 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }
    
    @IBInspectable var borderColor: UIColor = UIColor.clear {
        didSet {
            layer.borderColor = borderColor.cgColor
        }
    }
    
}

次にストーリーボード上の枠線をつけたいUIボタンのクラスをBorderCustomButtonに変更します。
f:id:codable:20171107113758p:plainf:id:codable:20171107113758p:plain

アトリビュートインスペクタを確認するとカスタマイズできる項目が増えていることが確認できます。あとは値を設定してUIの設定を行いましょう。
f:id:codable:20171107114153p:plainf:id:codable:20171107114153p:plain

AVFoundationを利用してカメラが取り込んだデータを表示する

ユースケース

iOSでは写真を撮影するためのフレームワークとしてiOSでは「UIImagePickerController」が用意されています。単純にカメラの機能を利用したいだけであればUIImagePickerControllerを利用するだけで充分です。しかし、UIImagePickerControllerではカメラに対するカスタマイズがあまり出来ません。カメラ機能に対して柔軟なカスタマイズを行いたいならAVFoundationフレームワークの使用を検討する必要があります。ただし、AVFoundationはUIImagePickerControllerと比較して非常に複雑な開発工程を踏む必要があります。

AVFoundationの構成

AVFoundationフレームワークは大きく2つに分かれます。ビデオまわりのAPIとオーディオに関係するAPIです。今回はビデオまわりのAPIのみを確認していきます。

AVFoundationを利用してカメラが写している風景を表示するために必要なクラス

AVFoundationを利用したカメラ映像の描画を行うためには以下に示す4つのクラスをうまく組み合わせて利用する必要があります。
f:id:codable:20171030102925p:plain

上図はAV Foundationプログラミングより引用

AVCaptureSession

キャプチャセッションはデバイスの入力と出力の橋渡しを行うクラスです。入力されたデータをどこに出力するのかを管理してくれます。

AVCaptureDeviceInput

AVCaptureDeviceInputはカメラなどの物理デバイスのインターフェースを提供してくれるクラスです。

AVCaptureXXXOutput

キャプチャセッションからの出力をキャプチャするために利用するクラスです。

AVCapturePreviewLayer

カメラがキャプチャしているものを表示するためのクラスです。

サンプルコード

以上のクラスを組み合わせてカメラが取り込んだデータを表示するができます。以下のコードはこれらのクラスを組み合わせてカメラが写している映像を表示するためのサンプルコードです。

class ViewController: UIViewController {

    var previewLayer: AVCaptureVideoPreviewLayer?

    enum CameraError : Error {
        case noCamerasAvailable
    }
    
    func defaultDevice() throws -> AVCaptureDevice{
        if let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {
            return device
        } else if let device =  AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back){
            return device
        } else {
            throw CameraError.noCamerasAvailable
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 1. capture sessionの生成
        let session:AVCaptureSession = AVCaptureSession()
    
        do{
            // 2. カメラデバイスの取得
            let cameraDevice:AVCaptureDevice = try defaultDevice()
            
            // 3. カメラデバイスからDeviceInputの作成
            let cameraInput:AVCaptureDeviceInput = try AVCaptureDeviceInput(device:cameraDevice)

            // 4. セッションにDeviceInputを接続
            if session.canAddInput(cameraInput){
                session.addInput(cameraInput)
            }
            
            // 5. イメージをキャプチャするためのAVCaputureOutputを作成
            let imageOutput:AVCapturePhotoOutput = AVCapturePhotoOutput()

            // 6. セッションにOutputを接続
            if session.canAddOutput(imageOutput){
                session.addOutput(imageOutput)
            }
            
            // 7. セッションを開始してデータ転送を開始する
            session.startRunning()
        }catch{
            print(error)
        }
        
        //セッションに流れているデータを表示する
        //カメラデバイスから取り込んだデータを表示する時はAVCaptureVideoPreviewLayerを使用する
        self.previewLayer = AVCaptureVideoPreviewLayer(session: session)
        self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
        self.previewLayer?.connection?.videoOrientation = .portrait
        self.previewLayer?.frame = view.frame
        self.view.layer.addSublayer(self.previewLayer!)
    }

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

}

Github URL

github.com