SwiftUIでRoomPlan使ってみた!
RoomPlanとは
RoomPlanとはARKitを活用して部屋の3Dモデルを作成するAppleが提供しているSwift APIです。
このAPIはLiDARスキャナを搭載しているiPhone/iPadで使用可能なものになっており、スキャンした3DモデルはUSD/USDZファイル形式で出力することもできます。
またCinema 4D、Shapr3D、AutoCADといった各種USDZ対応ツールにエクスポートするとより細かく調整することができるので、建築やインテリアデザインにおける最初のワークフローで有効的なAPIです。
RoomPlanを使用する環境について
使用に適した環境
・最大約9メートル×9メートルの大きさの部屋のシングルルーム
・最低50ルクス(夜間のリビングルームに相当)以上の明るさ
使用に適さない環境
・フルハイトの鏡や窓
・高い天井
・とても暗い表面
高い精度を得るための環境作り
・日中はカーテンを開け、十分な自然光を取り入れる
・ドアを閉めて部屋の外の余計な場所がスキャンされるのを防ぐ
使用する上での注意点
バッテリーの消耗や発熱問題でユーザ体験に影響を与える可能性がある行為
・繰り返しのスキャン
・5分以上のスキャン
RoomPlan対応端末
RoomPlanを使用するにはLiDARスキャナを搭載しているiPhone/iPadを使う必要があります。
以下対応端末一覧になります。
機種名 | 発売日 | モデル番号 |
---|---|---|
iPhone 14 Pro Max | 2022年 | 全モデル |
iPhone 14 Pro | 2022年 | 全モデル |
iPhone 13 Pro Max | 2021年 | A2641 |
iPhone 13 Pro | 2021年 | A2636 |
iPhone 12 Pro Max | 2020年 | A2410 |
iPhone 12 Pro | 2020年 | A2406 |
iPad Pro 12.9 インチ (第 5 世代) | 2021年 | A2378,A2461 |
iPad Pro 11 インチ (第 3 世代) | 2021年 | A2377,A2459 |
iPad Pro 12.9 インチ (第 4 世代) | 2020年 | A2229,A2069,A2232 |
iPad Pro 11 インチ (第 2 世代) | 2020年 | A2228,A2068,A2230 |
RoomPlanの活用方法例
・インテリアデザインアプリであれば壁の色の変更をプレビューし、必要な塗料の量を正確に計算できます。
・建築アプリの場合は部屋のレイアウト変更のプレビューや編集がリアルタイムで可能になります。
・不動産アプリの場合は業者が間取りをキャプチャしその3Dモデルを提供できます。
SwiftUIでRoomPlanを使用する
今回はSwiftUIでの実装例を紹介いたします。
実装は以下の3つのクラスによって行っていきます。
・RoomCaptureViewの設定、Delegatenの処理を行うクラス
・RoomCaptureViewをSwitUIで使用できるように変換するUIViewRepresentableのクラス
・RoomCaptureViewをSwiftUIで使用するクラス
またRoomPlanの機能には関係ないですが、補足でActivityViewControllerでのデータ共有についても記載していきたいと思います。
RoomCaptureViewの設定、Delegatenの処理
静的変数にinstanceを定義することでシングルトンでアクセスするようにしておきます。
他の処理は最低限のデリゲートの実装とスキャン開始/停止を実装しておきます。
import Foundation
import RoomPlan
class RoomCaptureController : ObservableObject, RoomCaptureViewDelegate, RoomCaptureSessionDelegate
{
static var instance = RoomCaptureController()
@Published var roomCaptureView: RoomCaptureView
@Published var showExportButton = false
@Published var showShareSheet = false
@Published var exportUrl: URL?
var sessionConfig: RoomCaptureSession.Configuration
var finalResult: CapturedRoom?
init() {
// roomCaptureViewの設定
roomCaptureView = RoomCaptureView(frame: CGRect(x: 0, y: 0, width: 42, height: 42))
sessionConfig = RoomCaptureSession.Configuration()
roomCaptureView.captureSession.delegate = self
roomCaptureView.delegate = self
}
// スキャン開始
func startSession() {
roomCaptureView.captureSession.run(configuration: sessionConfig)
}
// スキャン停止
func stopSession() {
roomCaptureView.captureSession.stop()
}
func captureView(shouldPresent roomDataForProcessing: CapturedRoomData, error: Error?) -> Bool {
return true
}
// スキャン結果受け取り
func captureView(didPresent processedResult: CapturedRoom, error: Error?) {
finalResult = processedResult
}
// スキャンデータの出力
func export() {
exportUrl = FileManager.default.temporaryDirectory.appending(path: "scan.usdz")
do {
try finalResult?.export(to: exportUrl!)
} catch {
print("Error exporting usdz scan.")
return
}
showShareSheet = true
}
required init?(coder: NSCoder) {
fatalError("Not needed.")
}
func encode(with coder: NSCoder) {
fatalError("Not needed.")
}
}
RoomCaptureViewをSwitUIで使用できるように変換
ここでの処理はUIViewをUIViewRepresentableに変換するだけになります。
import SwiftUI
struct RoomCaptureViewRep : UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
RoomCaptureController.instance.roomCaptureView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
RoomCaptureViewをSwiftUIで使用
NavigationLinkでRoomCaptureViewを使用する想定での実装コードになります。
実装しているアクションは
・キャンセル(スキャン停止+画面閉じ)
・Done(スキャン停止+出力ボタン表示)
・onAppear(スキャン開始+出力ボタン非表示)
・データ共有(データの共有+画面閉じ)
の4つになります。
ここまでの処理でRoomPlanによる部屋のスキャン、3Dモデル作成、共有ができます。
import SwiftUI
import RoomPlan
struct ScanningView: View {
@Environment(\.dismiss) private var dismiss
@StateObject var captureController = RoomCaptureController.instance
var body: some View {
ZStack(alignment: .bottom) {
RoomCaptureViewRep()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button("Cancel") {
// キャンセル(スキャン停止+画面閉じ)
captureController.stopSession()
dismiss()
})
.navigationBarItems(trailing: Button("Done") {
// Done(スキャン停止+出力ボタン表示)
captureController.stopSession()
captureController.showExportButton = true
}.opacity(captureController.showExportButton ? 0 : 1)).onAppear() {
// onAppear(スキャン開始+出力ボタン非表示)
captureController.showExportButton = false
captureController.startSession()
}
Button(action: {
// データ共有(データの共有+画面閉じ)
captureController.export()
dismiss()
}, label: {
Text("Export").font(.title2)
})
.buttonStyle(.borderedProminent).cornerRadius(40).opacity(captureController.showExportButton ? 1 : 0).padding().sheet(isPresented: $captureController.showShareSheet, content:{
ActivityViewControllerRep(items: [captureController.exportUrl!])
})
}
}
}
関連処理
最後にRoomPlanに直接的には関係ないですが、今回の実装コードに関連がある処理を記載いたします。
ScanningViewの呼び出し処理
NavigationLinkでScanViewへ遷移する処理になります。
import SwiftUI
struct IntroductionView: View {
var body: some View {
NavigationStack {
VStack {
Text("RoomPlan Demo")
NavigationLink(destination: ScanningView(), label: {Text("Start Scan")}).buttonStyle(.borderedProminent).cornerRadius(40).font(.title2)
}
}
}
}
ActivityViewControllerのSwiftUIでの利用処理
RoomPlanでスキャンしたデータを共有するためのActivityViewControllerをSwiftUIでの利用するための処理になります。
import SwiftUI
struct ActivityViewControllerRep: UIViewControllerRepresentable {
var items: [Any]
var activities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewControllerRep>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: items, applicationActivities: activities)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewControllerRep>) {}
}
感想
AR周りの開発は普段触らないのですが、思ったより簡単に実装できてびっくりしました!
ARKitの座標の概念とか難しそうという人は、その辺りを気にしなくていいRoomPlanで遊んでみるのもいいかもしれませんね!
最後まで読んでいただきありがとうございました!
参考
Discussion