こんにちは!
製品開発部の張朝程です。
最近、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の知識は以下の三点です。
- UIKitには、 UIView クラスがあり、ラベル UILabel やボタン UIButton 、スライダー UISlider などのViewに継承されています。
- UIKitには、UIViewのライフサイクルを制御する UIViewController クラスがあります。
- 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
- protocol View
- UIKit
- controller
- class ARViewController
- delegate
- protocol ARSessionDelegate
- view
- class MTKView
- controller
4. ファイル構造
5. 実装手順
5.1 サンプルコードをダウンロード
Apple社WWDC20で公表したARKit 4のサンプルコードVisualizing a Point Cloud Using Scene DepthをダウンロードしXcodeでプロジェクトを開いて、AppDelegate.swiftの内容をSwiftUIの形にします。
1 2 3 4 5 6 7 8 |
@main struct Sample: App { var body: some Scene { WindowGroup{ ContentView() } } } |
UIKitの @UIApplicationMain より、SwiftUIの @main のコード量が驚くほど少ないですね。
5.2 HomeViewとARViewを生成
まず、HomeViewとARViewのbodyに Text("Hello AR world!")を入れます。
5.3 ContentViewを生成
HomeViewとARViewをTabView で画面を切り替えるSwiftUIを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct ContentView: View { var body: some View { TabView () { HomeView().tabItem { Image(systemName: "list.bullet") Text("ホーム") } ARView().tabItem { Image(systemName: "arkit") Text("ARカメラ") } }.font(.headline) } } |
こんな感じですね。
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が二つのメソッドを入れてくれます。
1 2 3 4 5 6 7 |
struct ARViewContainer: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> ARViewController { return ARViewController() } func updateUIViewController(_ uiViewController: ARViewController, context: Context) { } } |
5.4.2 ARView
次に、ARViewContainerをARViewのbodyに入れます。
1 2 3 4 5 |
struct ARView: View { var body: some View { ARViewContainer() } } |
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)します
- 2.1 viewDidLoad()内の処理
問題なさそうですね、実際にアプリを実行してみて、アプリ下部のカメラボタンを押すと、以下のように怒られました。
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Point_Cloud/ARViewController.swift, line 53
具体的に説明すると、 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 の親クラスはそれらを実行してくれます。
以下の処理をclass ARViewControllerに入れてみます。
1 2 3 4 |
override func loadView() { super.loadView() self.view = MTKView(frame: .zero) } |
class図から見ると
↓
となりました。
もう一度アプリを実行してみると、うまくSwiftUIフレームワークを使ってApple社のARアプリを開きましたね。
6. 実演
サンプルコードの描画クラス Renderer の numGridPoints と particlaSize プロパティを微調整しました。
1 2 |
private let numGridPoints = 5000 private let particleSize: Float = 3 |
そうすると、ARカメラ画面に描画される点群の数が多くなり、点の大きさが小さくなります。
7. 感想
iOS開発に関して吸収したい知識は山ほどあります。今回の記事を契機に、SwiftUIを使ってApple社っぽいアプリを作ってみて、達成感を味わいました。
Apple社のARKitは、世界最大のARプラットフォームに間違いないです。2020に発売されたiPhone 12 Pro、iPad Proには、すでに高度なARを実現するLiDARが搭載されています。ハードウェアの機能とソフトウェアの機能を活用したら、手軽にAR体験できるし、3Dスキャンの分野にも導入できるでしょう。今年のWWDCに、どんな新製品とサービスが公表するのか、とても楽しみですね。
参考資料
Wrapping a UIViewController in a SwiftUI view
https://www.hackingwithswift.com/books/ios-swiftui/wrapping-a-uiviewcontroller-in-a-swiftui-view
Explore ARKit 4
https://developer.apple.com/videos/play/wwdc2020/10611/
Visualizing a Point Cloud Using Scene Depth
https://developer.apple.com/documentation/arkit/visualizing_a_point_cloud_using_scene_depth
What is MVVM?
https://www.hackingwithswift.com/example-code/language/what-is-mvvm
UIKitのView表示ライフサイクルを理解する
https://qiita.com/shtnkgm/items/f133f73baaa71172efb2