SwiftUIでARKitを触って考えたこと


はじめに

SwiftUIが2019年に登場してそろそろ2年になろうとしているところ、仕様も確立しつつあり、SwiftUIに移行し始めている方も多いかと思います。

しかしARKitを使ったアプリとなると話は別で、調べてもあまり情報が出てきません。Apple公式から出ているARアプリのSampleもまだStoryboardによる実装です。
例えばLiDARを使ったPointCloudとか。
https://developer.apple.com/documentation/arkit/visualizing_a_point_cloud_using_scene_depth

確かにARアプリとなるとPinchやRotateなどUIGestureRecognizerによる操作が中心になるので、既存のUIViewControllerとセレクタによる実装の方が直感的にとっつきやすいです。

そういう事情はあれ、これからはSwiftUIによるアプリ開発が主流になると思うので、この記事ではSwiftUIでARKitを使った際のポイントについて書いていきます。

環境

  • Swift 5.3
  • Xcode 12.2
  • ARKit 4

UIView/UIViewControllerの実装

まずSwiftUIのプロジェクトからAugmented Reality Appを選択すると以下のように初期コードが実装されます。

import SwiftUI
import RealityKit

struct ContentView : View {
    var body: some View {
        return ARViewContainer().edgesIgnoringSafeArea(.all)
    }
}

struct ARViewContainer: UIViewRepresentable {

    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero)
        let boxAnchor = try! Experience.loadBox()
        arView.scene.anchors.append(boxAnchor)
        return arView
    }

    func updateUIView(_ uiView: ARView, context: Context) {}
}

//Preview部分は省略

SwiftUIでUIKitを使う場合はRepresentableによる実装となります。
このコードではARViewContainerの部分ですね。
このARViewContainerを上のContentViewで読み込むことにより、画面に表示する形となります。

AR空間を表示するARViewはARViewContainerのmakeUIView(context:)で実装しています。
下のupdateUIView(_:context:)でARViewの更新を管理することになります。

一方でUIViewContorollerを使う場合はこちら。

import SwiftUI
import RealityKit

struct ContentView : View {
    var body: some View {
        return ARViewControllerContainer().edgesIgnoringSafeArea(.all)
    }
}

struct ARViewControllerContainer: UIViewControllerRepresentable {

    typealias UIViewControllerType = ARViewController

    func makeUIViewController(context: Context) -> ARViewController {
        return ARViewController()
    }

    func updateUIViewController(_ arViewController: ARViewController, context: Context) {}
}

class ARViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let arView = ARView(frame: .zero)

        let boxAnchor = try! Experience.loadBox()
        arView.scene.anchors.append(boxAnchor)

        self.view = arView
    }
}

//Preview部分は省略

UIViewRepresentableと比べてARViewControllerのクラスが増えました。
このARViewControllerは必須というわけではなく、例えばviewDidLoad()の中身をそのままARViewControllerContainerに移しても使えます。

ですからARViewのみを使うのであればUIViewRepresentableでの実装で問題ないと思います。

ちなみに上の2つのサンプルを実装するとこうなります。

UIGestureRecognizerの実装

UIViewcontrollerRepresentableを使うとこれまでのUIGestureがそのまま使えます。

class ARViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let arView = ARView(frame: .zero)

        let boxAnchor = try! Experience.loadBox()
        arView.scene.anchors.append(boxAnchor)

        self.view = arView

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
        self.view.addGestureRecognizer(tapGesture)
    }

    @objc
    func tap(sender: UITapGestureRecognizer) {}
}

ただ、SwiftUIのような新しいフレームワークを出したということは、これまで以上にObjective-Cから脱却していくような感じなりそうな中で、ずっと@objcを使うのが良いのかどうかは微妙なところです。

しかしながらSwiftUIでGestureを実装しようとすると、最初にonTapGesgure{}などで値を取得して、それを@State@BindingでRepresentableに送って、そこからSCNNodeやModelEntityなどのプロパティを変更して・・・と手順を踏む必要があります。

恐らくAppleとしてはTapのような基本的な動作はRealityComposerで設定して、RotateやPanのような動作はコードで実装して欲しいというところではないかと思いますが、現状ではこれまで通りの実装でも問題無さそうではあります。

感想

個人的には最初に書いたようにUIViewControllerとセレクタによる実装はとっつきやすいです。
ただ、今後この方法が使えなくなる可能性もありますし、SwiftUIを使ったもっと簡単な仕組みを実装しそうでもあります。

あとはSwiftUIだとコードを書いたらそのまま複数デバイスで使用することができるので、Storyboardのようにデバイスごとに細かくボタンなどの位置を調整して・・・という作業がなくなるのが非常に助かります。
これはARアプリ以外にも当てはまりますね。

これからAppleはSwiftUIを中心にコンテンツを追加していきますし、ARKitもどんどん発展・改善されていきそうなので、早いところSwiftUIによる実装に慣れておいた方がいいのかなと思いました。