ハイライト
SwiftUI は、VisionOS 上の Volume コンテナ、Model3D API、RealityView アタッチメント、および 3D ジェスチャを使用例して、開発者が使い慣れた宣言構文を使用例して境界付き 3D エクスペリエンスを構築し、スペース全体を占有することなく 3D コンテンツを表示および操作できるようにします。
主な内容
ボリューム: 固定サイズの 3D コンテナー
ウィンドウは距離に基づいて動的に拡大縮小されますが、ボリュームは異なります - どれだけ離れていても固定サイズが維持され、幅 1 メートルのボリュームは常に幅 1 メートルになります。ボリュームは水平方向に配置されており、あらゆる角度からの表示をサポートします。
ボリュームを作成するには、WindowGroup に新しいウィンドウ スタイルを適用するだけです。
@main
struct WorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(.volumetric)
}
}
キーポイント:
.windowStyle(.volumetric)は通常のウィンドウをボリューム コンテナに変換します- ボリュームにはガラスの背景がなく、3D コンテンツがシーンに直接配置されます。
- コントロールパネルと合わせてコンテンツを表示可能
(02:14)
Model3D: 3D リソース用の AsyncImage
Model3D は、USDZ などの 3D リソースをロードするための SwiftUI ビューです。設計コンセプトは AsyncImage に似ており、読み込みライフサイクルはフェーズを通じて管理されます。
struct MoonView: View {
var body: some View {
Model3D(named: "Moon") { phase in
switch phase {
case .empty:
ProgressView()
case .failure(let error):
Text(error.localizedDescription)
case .success(let model):
model
.resizable()
.scaledToFit()
}
}
}
}
キーポイント:
Model3D(named:)は、指定された名前の USDZ ファイルをリソース パッケージからロードします。phaseには 3 つの状態があります: 空 (読み込み中)、失敗 (失敗)、成功 (成功).resizable()は、使用例可能なスペースに基づいてモデルのサイズを変更できることをレイアウト システムに伝えます。.scaledToFit()は、利用可能なスペース内でモデルのスケールを維持します。
(03:35)
3D レイアウトと奥行きの調整
複数の 3D モデルを並べて配置する場合、デフォルトで背面の位置合わせが使用例されます。つまり、すべてのモデルの背面が面一になります。
struct CelestialObjectView: View {
let name: String
let size: CGFloat
var body: some View {
Model3D(named: name) { phase in
switch phase {
case .success(let model):
model
.resizable()
.scaledToFit()
.frame(width: size, height: size, depth: size)
.frame(depth: size, alignment: .front)
default:
EmptyView()
}
}
}
}
キーポイント:
.frame(width:height:depth:)は 3D ビューの 3 次元寸法を設定します.frame(depth:alignment:)は奥行き方向の位置合わせを制御します.frontはモデルの前面を揃え、.back(デフォルト) は背面を揃えます。- 斜めから見るとアライメントの違いが一目瞭然
(05:43)
3D アニメーション効果
SwiftUI は 3 次元回転用の Rotation3DEffect を提供します。
TimelineView(.animation) { timeline in
CelestialObjectView(name: object.name, size: object.size)
.rotation3DEffect(
Angle(degrees: timeline.date.timeIntervalSinceReferenceDate * 10),
axis: .y
)
}
キーポイント:
TimelineView(.animation)は継続的に更新されるタイム ソースを提供します.rotation3DEffect(axis:)は、指定された軸を中心にビューを回転します- ここで y 軸を中心に回転して、天体の回転効果をシミュレートします
(07:09)
##詳細
RealityView Attachments: SwiftUI ビューを 3D シーンに埋め込む
RealityView の添付ファイル API を使用例すると、SwiftUI ビューを RealityKit エンティティにバインドできます。これらのビューは生きており、状態の変化に応答し、アニメーションを実行し、ジェスチャを処理できます。
RealityView { content, attachments in
// 地球モデルを読み込む
let earth = try! Entity.load(named: "Earth.usdz")
content.add(earth)
// 各地点のラベル添付を作成
for place in favoritePlaces {
if let label = attachments.entity(for: place.id) {
content.add(label)
// ラベルを地球表面に配置
label.lookAt(earth, from: place.location, relativeTo: earth)
}
}
} attachments: {
ForEach(favoritePlaces) { place in
Text(place.name)
.glassBackgroundEffect()
.tag(place.id)
}
}
キーポイント:
attachmentsパラメータはマークアップ ビューへのアクセスを提供します- ビューは
.tag()によってマークされ、対応するエンティティは RealityView のattachments.entity(for:)によって取得されます。 .glassBackgroundEffect()により、どのようなコンテキストでもラベルが確実に読み取れるようになりますlookAt(from:relativeTo:)はアタッチメントを指定された位置に向けて配置します- 添付ファイル ビューは、ステータスの更新やジェスチャを含む完全な SwiftUI 機能をサポートします
(08:39)
エンティティの入力受信を構成する
RealityView のエンティティがジェスチャに応答できるようにするには、次の 2 つのコンポーネントを追加する必要があります。
RealityView { content in
let earth = try! Entity.load(named: "Earth.usdz")
// 入力を受け取れるよう Input Target コンポーネントを追加
earth.components.set(InputTargetComponent())
// Collision コンポーネントを追加してインタラクション領域の形状を定義
earth.components.set(
CollisionComponent(shapes: [.generateSphere(radius: 1.0)])
)
content.add(earth)
}
キーポイント:
InputTargetComponent()は、エンティティとその子孫を入力を受信できるものとしてマークします。CollisionComponentはインタラクション領域のジオメトリを定義します- ここでは、地球モデルの形状に一致させるためにスフィア コライダーが使用例されています。
- 衝突体の精度はジェスチャの位置決めの精度に影響します
(11:32)
スペースクリックジェスチャ
SpatialTapGesture は 3D クリック位置を提供します。
RealityView { content in
// ... 地球エンティティを設定
}
.gesture(
SpatialTapGesture()
.targetedToEntity(earthEntity)
.onEnded { value in
// 3D のタップ位置を取得(SwiftUI のローカル座標空間、単位は points)
let location3D = value.location3D
// シーン座標空間へ変換
let sceneLocation = value.convert(location3D, from: .local, to: earthEntity)
// タップ位置の上に新しい地点を追加
let newPlace = FavoritePlace(
name: "New Discovery",
location: sceneLocation * 1.05 // 表面から少し浮かせる
)
favoritePlaces.append(newPlace)
}
)
キーポイント:
.targetedToEntity()は、ジェスチャを指定されたエンティティに制限し、他の領域をクリックするとジェスチャは失敗します。value.location3Dクリックの 3D 座標を取得しますvalue.convert(...)は座標空間間で位置を変換します- 座標単位はポイントであるため、使用例するにはシーン空間に変換する必要があります。
(12:15)
3D 操作ジェスチャを組み合わせる
ドラッグ、ズーム、回転を組み合わせて完全なジェスチャを作成します。
var manipulationGesture: some Gesture<AffineTransform3D> {
DragGesture()
.simultaneously(with: MagnifyGesture())
.simultaneously(with: RotateGesture3D())
.map { gesture in
let (translation, scale, rotation) = gesture.components()
return AffineTransform3D(
scale: scale,
rotation: rotation,
translation: translation
)
}
}
// ジェスチャーコンポーネントを取り出すヘルパーメソッド
extension SimultaneousGesture<
SimultaneousGesture<DragGesture, MagnifyGesture>,
RotateGesture3D
>.Value {
func components() -> (Vector3D, Size3D, Rotation3D) {
let translation = self.first?.first?.translation3D ?? .zero
let magnification = self.first?.second?.magnification ?? 1
let size = Size3D(
width: magnification,
height: magnification,
depth: magnification
)
let rotation = self.second?.rotation ?? .identity
return (translation, size, rotation)
}
}
ジェスチャを使用例してエンティティの変換を制御します。
@State private var isDragging = false
RealityView { content, attachments in
// ... コンテンツを設定
} update: { content, attachments in
if let satellite = content.entities.first(where: { $0.name == "satellite" }) {
satellite
.gesture(
manipulationGesture
.updating($isDragging) { _, state, _ in
state = true
}
.onChanged { value in
satellite.transform = Transform(value)
}
)
}
}
キーポイント:
DragGestureのtranslation3D属性は 3D 変換量を提供しますRotateGesture3Dは、制約のない 3D 回転を測定しますsimultaneously(with:)複数のジェスチャを組み合わせて、同時に認識されるジェスチャ グループを作成しますAffineTransform3Dは、3D 変換の変換、回転、スケーリングをカプセル化します。.updatingはジェスチャの瞬間的な状態を追跡し、ジェスチャが失敗すると自動的にリセットします- ジェスチャーは、直接的な手の操作、間接的なピンチ、トラックパッド、アクセシビリティをサポートします
(17:26)
重要なポイント
-
やること: ユーザーがあらゆる角度から製品を表示できる 3D 製品表示アプリケーションを作成します。
- 䡡値: ボリューム コンテナーを使用例すると、3D コンテンツを共有スペース内の他のアプリケーションと共存できます。 Model3D は USDZ モデルを非同期的にロードし、
.resizable()と.scaledToFit()を使用例してレイアウトを自動的に調整します。 - Hajime Fang:
.windowStyle(.volumetric)を使用例してボリュームを作成し、Model3D を使用例して製品モデルをロードし、Rotation3DEffect を追加してモデルを自動的に回転して表示できるようにします。
- 䡡値: ボリューム コンテナーを使用例すると、3D コンテンツを共有スペース内の他のアプリケーションと共存できます。 Model3D は USDZ モデルを非同期的にロードし、
-
やること: 注釈付き 3D マップまたは解剖図アプリケーションを開発する
- 䡡値: RealityView アタッチメントを使用例すると、SwiftUI ビューを 3D モデル上の特定の位置に正確にアタッチできます。ラベルはステータスの更新に応答し、ガラスの背景をサポートして読みやすさを確保できます。
- Haikata: RealityView のアタッチメント クロージャでテキスト ラベルを作成し、
.tag()でマークし、RealityView クロージャでattachments.entity(for:)を使用例して取得してモデル サーフェス上に配置します。
-
やること: ジェスチャー操作をサポートする 3D デザイン ツールを構築する
- 価格: ジェスチャの組み合わせ (ドラッグ + ズーム + 回転) により、ユーザーは自然な手の動きで 3D オブジェクトを操作できます。
targetedToEntityは、ジェスチャがターゲット オブジェクトに正確に影響を与えることを保証します - Haigata:
DragGesture.simultaneously(with: MagnifyGesture).simultaneously(with: RotateGesture3D)を定義してジェスチャを結合し、.targetedToEntity()を使用例してターゲットを制限し、.onChangedでエンティティの変換を更新します。
- 価格: ジェスチャの組み合わせ (ドラッグ + ズーム + 回転) により、ユーザーは自然な手の動きで 3D オブジェクトを操作できます。
-
やること: 3D パズルまたは組み立て教育アプリを作成する
- 䡡値: SpatialTapGesture の
location3Dは、正確な 3D クリック位置を提供します。InputTargetComponentおよびCollisionComponentを使用例して、複雑なモデルに対して正確な相互作用領域を構成できます。 - ** ハジメ **: 各インタラクティブ コンポーネントに衝突コンポーネントを追加し、
SpatialTapGesture().targetedToEntity()を使用例してクリックをキャプチャし、クリック位置に基づいてユーザーがどのコンポーネントを選択したかを判断します。
- 䡡値: SpatialTapGesture の
関連セッション
- Elevate your windowed app for spatial computing — 既存の SwiftUI アプリをvisionOS に適応させ、ウィンドウと装飾について学びます
- Go beyond the window with SwiftUI — ImmersiveSpace で境界のない没入型エクスペリエンスを作成する
- Build spatial experiences with RealityKit — RealityKit エンティティ - コンポーネント システム、レンダリング、アニメーションの詳細な説明
コメント
GitHub Issues · utterances