🗒️

[VisionOS開発メモ]RealityViewのupdateクロージャーが呼ばれるタイミング

2024/09/02に公開

VisionOSを開発してる際に、3Dモデルを表示するRealityViewの仕様が分からず、調べたのでメモしておきたいと思います。経緯としては、updateという関数が出るとどうしてもUnityのupdateがよぎってしまい、update呼ばれるはずでは。。ということが多々起こってしまったためです。ここはSwiftUIの世界。

RealityViewのパラメータ

RealityViewのパラメータは、4つあります。こちらの記事がとても詳しく書かれているので、その説明を引用すると下記のようになります。

  • make: A closure to set up and configure the initial content asynchronously.
  • placeholder: A view displayed while the initial content is being loaded.
  • update: An optional closure to update the content in response to state changes.
  • attachments: To add views into your RealityKit content.

ざっくり日本語で和訳すると

  • make: RealityViewの初期化
  • placeholder: ロード前に表示するView
  • update: 状態が変化した場合に呼ばれるオプショナルなクロージャー
  • attachments: Attachmentの追加
    となります。

今回は、このパラメータの中のupdateクロージャーについての話になります。
上の定義では状態が変化した際に呼ばれる と書かれていますが、どうゆう状態まで指しているのかがわかりません。コード上のRealityKitのコメントでは
- update: An optional closure that updates the ``RealityView``instance's content as the view's state changes. View`s State という表現がされており、Viewの変更があった場合にupdateが呼ばれるようです。

結論

調べたものの結論を先に書いておきます。足りないものもたくさんあると思うので、今上がっているものだけではないと思っていただければ幸いです。

【updateが呼ばれる場合】

  • makeクロージャーの処理が終了時
  • updateクロージャーで、@Stateのついた値が利用され、更新された時
  • updateクロージャーで、@Observableのついた値が利用され、更新された時

【updateが呼ばれない場合】

  • makeクロージャーで、@Stateや@Observable等の付いた値が利用され、更新された時
  • makeクロージャーで、生成したEntityのポジションが変更された時
  • makeクロージャーで、contentにAddしたEntityに他のEntityをAddChildした時
  • attachmentsクロージャで定義されているAttachmentのView内で@Stateや@Observable等の付いた値が利用され、更新された時

updateが呼ばれる場合について

基本的には、SwiftUIのView内部で@Stateや@Observableなどの監視している値が利用され、更新された際に再描画される場合と同じ仕様です。具体的には、updateクロージャー内で@Stateや@Observableの値が利用・更新されるとupdateが呼ばれます。それらの値が定義されていない場合は、呼ばれません。
また、makeクロージャーの処理が完了したタイミングで一度だけupdateが呼ばれることを確認しています。makeクロージャーで重い処理がありawait等があった場合は、その処理の後に呼ばれます。

updateが呼ばれない場合について

以下、ほとんどがUnity脳の自分が呼ばれるかもなーと思ったら呼ばれなかったという話なので、上の説明で分かる方は、ここでスキップでも良いです。

makeクロージャーで、@Stateや@Observable等の付いた値が利用され、更新された時

RealityViewがView内で定義されているのでmakeで監視された値を利用していた場合もupdateが更新されるのかなーと思っていたところ更新されなかったという話です。updateの内部で@Stateや@Observableが利用された際のみでupdateの更新を行うようです。

makeクロージャーで、生成したEntityのポジションが変更された時

これが始まりなのですが、Entityのポジションなどの情報が更新されたとしてもupdateが呼ばれるわけではありません。なので、下のような形でRealityViewでEntityを生成し、そのEntityをドラッグする操作をしたとしてもupdateは呼ばれないので注意してください。
一度、@observableなクラスや@StateなプロパティにEntityの操作情報を書き込み、updateクロージャの中で書き込んだ監視可能な値を利用したらupdateが呼ばれるようになります。

var body: some View {
    RealityView { content, attachments in
        content.add(self.root)

        // エンティティの生成
        if let immersiveContentEntity = try? await Entity(named: "Scene", in: realityKitContentBundle) {
            root.addChild(immersiveContentEntity)
        }
    }
    update: { content, attachments in

  //こので監視された値を使わなければ呼ばれない
   print("update")
    } .gesture(dragGesture)
}
var dragGesture: some Gesture {
        DragGesture()
            .targetedToAnyEntity()
            .onChanged { value in
                let entityPos =  value.convert(value.location3D, from: .local, to: value.entity.parent!)
                // ドラッグしているEntityのポジションを変更する
                value.entity.position = entityPos
                // AppModelでDragPosを保存
                appModel.dragPos = entityPos
          }
    }
}

makeクロージャーで、contentにAddしたEntityに他のEntityをAddChildした時

シーンにEntityを追加した場合もupdateは呼ばれません。具体的には、下のようにRealityViewのcontentにrootを先に追加し、その後で時間差でrootにEntityを追加した場合にはupdateは呼ばれませんでした。追加するタイミングはmakeクロジャーで可能ではあるので利用するシチュエーションがないかもしれませんが。

struct ImmersiveView: View {

    var root: Entity = Entity()
    var body: some View {
        RealityView { content, attachments in
            content.add(self.root)

            // エンティティの生成
            Task{
                // 5秒間待機する。
                try? await Task.sleep(nanoseconds: 5 * 1_000_000_000)
                // Entityを生成して、rootに追加
                if let immersiveContentEntity = try? await Entity(named: "Scene", in: realityKitContentBundle) {
                    root.addChild(immersiveContentEntity)
                }
            }
        }
        update: { content, attachments in
           print("update")
        }
    }
}

attachmentsクロージャで定義されているAttachmentのView内で@Stateや@Observable等の付いた値が利用され、更新された時

attachmentsクロージャー内ではAttachmetという形でViewを登録します。このViewの中で@Stateや@Observableの値を利用した場合もupdateクロージャーは呼ばれませんでした。しかし、Attachmentで定義されたView上では値が更新されます。

終わりに

はい。結論以下は、あまり意味のない試行錯誤になってしまいましたが、これでUnityのupdateと全然違う事がわかったかと思います。(自戒)
冗談はさておき、大事なところはSwiftUIでいうところのView内で監視されている値が更新された際にViewが更新される機能がupdateクロージャーで利用できるということです。同じViewModelを通常のViewとRealityViewで共有したい場合等にupdateクロージャーが活躍するのではないかと思います。

Discussion