ARKit4とSwiftUIを組み合わせた拡張現実(AR)体験

こんにちは!
製品開発部の張朝程です。

最近、Apple社の最新UIフレームワークSwiftUIを学習してiOSアプリの開発を行っています。SwiftUIを実際に体験し、短時間でAppleっぽいiOSアプリを作れるため、非常に便利だと感じました。

しかし、2019年誕生のSwiftUIは、Swiftが提供する全てのライブラリや機能には対応できていません。例えば、Apple社が公表しているAR関連のサンプルコードのUIフレームワークは、SwiftUIを使わず、従来のUIKitを採用しています。SwiftUI基盤のアプリに複雑な機能を実装したい場合、SwiftUIとUIKitを組み合わせる必要があります。

SwiftUIとUIKitを組み合わせる方法について、最も理解しやすい例は、Hacking with SwiftのPaul Hudsonさんの記事だと思います。この記事のおかげで、端末の写真ライブラリから画像を取得する手法を学んで、UIKitのUIViewControllerをSwiftUIに組み合わせる方法も理解できました。ネット上の情報サイトには端末内にある画像を取得する例がほとんどのため、今回はMTKViewのUIViewControllerをSwiftUIに組み合わせる方法を記載します。

Hudsonさんの記事にとると、SwiftUIとUIKitを組み合わせる前に、事前に必要なUIKitの知識は以下の三点です。

  1. UIKitには、 UIView クラスがあり、ラベル UILabel やボタン UIButton 、スライダー UISlider などのViewに継承されています。
  2. UIKitには、UIViewのライフサイクルを制御する UIViewController クラスがあります。
  3. UIKitには、delegationというdesign patternがあります。delegationは、なんの処理を誰に呼ばせるのかを決める役割を持ちます。

SwiftUIの話に戻ります。SwiftUIには、一般的なMVCモデルではなく、Model-View-ViewModel (MVVM、モデル・ビュー・ビューモデル) が採用されています。UIViewとUIViewControllerは、SwiftUIのMVVMモデルのViewレイヤとみなされます。

今回、SwiftUIとUIKitを組み合わせた簡単なアプリを作成してみました。

1. 開発環境

iOS : 14.3
Xcode : 12.3
端末: 11-inch iPad Pro
サンプルコード:  Visualizing a Point Cloud Using Scene Depth

2. 画面

  • Home画面
  • ARカメラ画面

3.  利用ライブラリ(SwiftUIとかかわる対象のみ)

  • SwiftUI
    • protocol View 
      • ContentView
      • HomeView
      • ARView
    • protocol UIViewControllweResentable
      • ARViewContainer
  • UIKit
    • controller
      • class ARViewController
    • delegate
      • protocol ARSessionDelegate
    • view
      • class MTKView

4. ファイル構造

5. 実装手順

5.1 サンプルコードをダウンロード

Apple社WWDC20で公表したARKit 4のサンプルコードVisualizing a Point Cloud Using Scene DepthをダウンロードしXcodeでプロジェクトを開いて、AppDelegate.swiftの内容をSwiftUIの形にします。

UIKitの @UIApplicationMain より、SwiftUIの @main のコード量が驚くほど少ないですね。

5.2 HomeViewとARViewを生成

まず、HomeViewとARViewのbodyに Text("Hello AR world!")を入れます。

5.3 ContentViewを生成

HomeViewとARViewをTabView で画面を切り替えるSwiftUIを作ります。

こんな感じですね。

5.4 ARViewを編集

5.4.1 ARViewContainer

UIKitのView controllを組み合わせるために、まずprotocol UIViewControllerRepresentable がインプリメントされるstruct ARViewContainer が必要です。

UIViewControllerRepresentable を ARViewContainer にインプリメントするには、二つのメソッドの実装が必要となります。

  • makeUIViewController() :View controllerのinitailする役割を持ち、ARViewControllerをreturnします。
  • updateUIViewController() :SwiftUIの状態が変わる際、View controllerを更新する役割

メソッドの実装方法について、 UIViewControllerRepresentable をインプリメントすると、Xcodeにエラーメッセージが表示されます。エラーメッセージをクリックしfixボタンを押すと、Xcodeが二つのメソッドを入れてくれます。

5.4.2 ARView

次に、ARViewContainerをARViewのbodyに入れます。

5.5 ARViewControllerを編集

ここまで、SwifyUI側の実装は完了です。UIKitの世界に入りましょう。

サンプルコードをみて分かったこと:
  • 1. class ARViewController はすでに UIViewController と ARSessionDelegate がインプリメントされています。つまり、 ARViewController のviewプロパティのvalueはUIViewだと分かりました。
  • 2. ARViewController のライフサイクルの関数は、 viewDidLoad() と viewWillAppear() しかないです。
    • 2.1 viewDidLoad()内の処理
      • 自分自身(self)をARSessionに処理を依頼(delegate)します。
      • 自分自身(self)をMTKViewに処理を依頼(delegate)します。そうすると、viewプロパティをUIViewからMTKViewになります。
      • 描画クラスRendererを初期化
      • カメラ画面にUIの配置
    • 2.2 viewWillAppear()内の処理
      • ARのconfigurationを実行(run)します

問題なさそうですね、実際にアプリを実行してみて、アプリ下部のカメラボタンを押すと、以下のように怒られました。

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Point_Cloud/ARViewController.swift, line 53

ARViewController.swiftのline 53は、 viewDidLoad() 内の処理だとわかりました。調査してみると、エラーの原因は、 viewDidLoad() 内の描画クラス Renderer は初期化されていなくて、nil値が renderer プロパティに代入されてしまったことです。

具体的に説明すると、 viewDidLoad() 内の if let view = view as? MTKView { Rendererを初期化する処理など } は、if文に入らずに処理が続いてしまい、 ARViewController のviewプロパティがUIViewのままでした。

UIViewからMTKViewに変換できなかった理由がわからず、諦めたいほどハマりました。この問題を解決するには、UIViewControllerのviewライフサイクルについて深く調べました。

一般的に、UIViewControllerのviewライフサイクルは、 > loadView > viewDidLoad > viewWillAppear > viewDidAppear > viewWillDisapper > viewDidDisapper >  です。

一方、 ARViewController のライフサイクルに関するメソッドは、 viewDidLoad() と viewWillAppear() しかないです。なぜかというと、書かなくても ARViewController の親クラスはそれらを実行してくれます。

以上の調査をもとに、 ARViewController のviewプロパティを正しい箇所に設定すれば、問題を解消できると考えます。もっと調べてみたら、 loadView() をoverrideすれれば、うまくviewプロパティにMTKViewに代入できるでしょう。

以下の処理をclass ARViewControllerに入れてみます。


class図から見ると

となりました。

もう一度アプリを実行してみると、うまくSwiftUIフレームワークを使ってApple社のARアプリを開きましたね。

6. 実演

サンプルコードの描画クラス Renderer の numGridPoints と particlaSize プロパティを微調整しました。

そうすると、ARカメラ画面に描画される点群の数が多くなり、点の大きさが小さくなります。

7. 感想

iOS開発に関して吸収したい知識は山ほどあります。今回の記事を契機に、SwiftUIを使ってApple社っぽいアプリを作ってみて、達成感を味わいました。

Apple社のARKitは、世界最大のARプラットフォームに間違いないです。2020に発売されたiPhone 12 Pro、iPad Proには、すでに高度なARを実現するLiDARが搭載されています。ハードウェアの機能とソフトウェアの機能を活用したら、手軽にAR体験できるし、3Dスキャンの分野にも導入できるでしょう。今年のWWDCに、どんな新製品とサービスが公表するのか、とても楽しみですね。

参考資料