📸

Composable Architectureにおけるカメラ制御の扱いのお悩み相談

2 min read 2

こんばんは。やっと技術っぽいこと書き始めたアマゾネスです。
...といっても有益な知識共有とかではなく、お悩み相談です。ごめんなさい。

現在The Composable Architecture(以下、TCA)の構成でアプリを実装しているのですが、カメラを使用するにあたり、とても考えていることがあるで、それを共有&お力をかりたい感じです🙇‍♀️

悩んでいること

はじめに悩んでいるポイントをいいます。

前提:TCAで実装している

  • カメラのセッション開始や終了はReducerで扱うべきか
  • ViewをStateとして扱うことに違和感を感じないか

が、今回のお悩みポイントです。

それを踏まえ、中身の簡単な実装を書いていきます。
実装物は、起動時にカメラが表示されるだけのアプリです。

実装

SwiftUIでカメラを扱う場合、UIViewControllerRepresentableに準拠したCALayerViewを用意する必要があります。
※アーキテクチャは違いますが、全体の実装としてはこちらを参考にさせていただいています。

CALayerViewはキャプチャされたビデオを表示する必要があります。
キャプチャしたビデオはCALayerのサブクラスであるAVCaptureVideoPreviewLayerで表示することができるので、それをプロパティに持ちます。

struct CaptureView: View {
    var body: some View {
        ZStack {
            CALayerView(caLayer: AVCaptureVideoPreviewLayer)
        }
    }
}

次に、カメラ制御を責務とする AVFoundationManagerを用意しました。
このクラスでは、AVCaptureDeviceAVCaptureSessionクラスを使用してカメラの設定や制御を行ったりします。CALayerViewに渡す必要があるAVCaptureVideoPreviewLayerはCaptureSession組み合わせた状態である必要があるので、このクラスで生成されます。詳細

そうすると、以下のような実装になってくると思います。

struct CaptureView: View {
    var avFoundationManager: AVFoundationManager
    var body: some View {
        ZStack {
            CALayerView(caLayer: avFoundationManager.previewLayer)
            }
            .onAppear {
                avFoundationManager.startSession()
            }
            .onDisappear {
                avFoundationManager.endSession()
            }
    }
}

これでカメラが起動した画面は表示されます📷

が、

TCAを使用する場合、AVFoundationManagerEnvironmentにもたせて、Reducerでカメラの起動を扱うこともべき? とか考え始めました。
ただこの場合、avFoundationManager.previewLayerStateとして扱う必要がでてきます。

大体こんな感じ。

struct CaptureEnvironment {
    var avFoundationManager : AVFoundationManager
}

let captureReducer = Reducer<CaptureState, CaptureAction, CaptureEnvironment> {
    state, action, environment in

    switch action {

    case let .onAppear:
        environment.avFoundationManager.startSession()
        state.previewLayer = avFoundationManager.previewLayer
        return .none

    case .onDisappear:
        environment.avFoundationManager.endSession()
        return .none

    }
}

まとめ

冒頭でもお伝えした通り、気になっているポイントとしては

  • カメラのセッション開始や終了はReducerで扱うべきか
  • ViewをStateとして扱うことに違和感を感じないか

の二つになります。

私ははじめ、カメラのセッション開始や終了はReducerで扱う形で実装していました。
ですが、View自体をStateとして扱うことに違和感があったり、要件的にカメラ状態はViewの生存と一蓮托生なのでReducerで扱う必要ないんじゃないかとかいろいろ悩んでいる、なうです。

いいアイディアや、ご意見ご感想、お待ちしています🙏

Discussion

PointFreeのTCAシリーズも見ましたけど、実装することがまったくないのでアドバイスとか言えませんが。

TCAを採用した一番のメリット(解決したい問題とも言える)は、外部から発火した Action による State 変化も観察できるや、外部からローカル状態を操作できることとかじゃないですか?
(ディテール画面のいいねはリストにも反映するや、URL Schemeによるアプリ起動後の状態設定など)

そうすると、もし、カメラの初期挙動が、外部から設定することはまったくないなら(今後の機能拡張も考えた上)、一つ目の簡単なほうがいいんじゃない?わざわざTCAに従って複雑度を上げる必要はないと思います。

自分もTCAの動画を見る時結構悩みます、UI上の状態は一体Storeに属するべきかどうかという問題。属すると、SwiftUIならまだしも、UIKitなら異常に面倒くさくなる感じがします。後期の動画は見てなかったので、UI状態管理に関する問題に触れたのかな...

生産環境でTCAを実装したどの方からアドバイスを貰えるといいですね。

コメントありがとうございます!とても嬉しいです!

カメラの初期挙動が、外部から設定することはまったくないなら(今後の機能拡張も考えた上)、一つ目の簡単なほうがいいんじゃない?わざわざTCAに従って複雑度を上げる必要はない

現在の実装パターンだと、カメラのセッションとViewの生存と一蓮托生なので、動的な状態管理は実際必要ないんですよね。なので、おっしゃる通り、頑張ってTCAに従って複雑度を上げる必要はないのかもしれません。

それから、実際TCAを実装した方から↓というアドバイスいただきました!

AVFoundation関係はViewとして扱う方が都合がいいですよ。

もしくはstart, endに関する状態をStateに作って、その変更によってsessionをコントロールするみたいな

このコメントをみて、AVFoundation関係はViewとして扱う方が結果的にシンプルになりそうな気がしました。

SwiftUIならまだしも、UIKitなら異常に面倒くさくなる感じがします

これには完全同意です!!
現在実装しているアプリにはもちろん撮影以外の画面があるのですが、UIKitを使用する撮影画面っがめちゃくちゃ面倒臭そうだったので、先に実装して、TCAを採用しても本当に大丈夫なのか検証してました。
結果としては良さそうな気がしています💡

ログインするとコメントできます