Skip to content

Instantly share code, notes, and snippets.

@lanserxt
Created August 22, 2026 20:09
Show Gist options
  • Select an option

  • Save lanserxt/cc22ac5dd0d8cea2bbd877d1ce18fd48 to your computer and use it in GitHub Desktop.

Select an option

Save lanserxt/cc22ac5dd0d8cea2bbd877d1ce18fd48 to your computer and use it in GitHub Desktop.
iOS 27: UISDKit Framework example
//
// USDInspectorModel.swift
// WWDC25Demo
//
// Created by Anton Gubarenko on 22.08.2026.
//
import SwiftUI
import Observation
import RealityKit
import USDKit
@available(iOS 27.0, *)
@MainActor
@Observable
final class USDInspectorModel {
struct PrimRow: Identifiable, Hashable {
let id: String
let path: String
let description: String
let specifier: String
let propertyCount: Int
let childCount: Int
}
private(set) var stage: USDStage?
private(set) var prims: [PrimRow] = []
private(set) var errorMessage: String?
private(set) var isLoaded = false
let source = """
#usda 1.0
(
defaultPrim = "Showroom"
metersPerUnit = 1
upAxis = "Y"
)
def Xform "Showroom"
{
def Xform "Table"
{
def Cube "Product"
{
double size = 0.4
}
}
def Xform "LightRig"
{
def Sphere "KeyLight"
{
double radius = 0.1
}
}
}
"""
func load() {
do {
let stage = try USDStage(string: source, loadingPayloads: .all)
self.stage = stage
self.prims = stage.descendants.map {
PrimRow(
id: String(describing: $0.path),
path: String(describing: $0.path),
description: $0.description,
specifier: String(describing: $0.specifier),
propertyCount: $0.properties.count,
childCount: $0.children.count
)
}
self.errorMessage = nil
self.isLoaded = true
} catch {
self.stage = nil
self.prims = []
self.errorMessage = error.localizedDescription
self.isLoaded = false
}
}
func reload() {
stage = nil
prims = []
isLoaded = false
load()
}
}
@available(iOS 27.0, *)
struct USDStagePreview: View {
let stage: USDStage
@State private var rootEntity: Entity?
@State private var pitchDegrees = 0.0
@State private var yawDegrees = 0.0
@State private var rollDegrees = 0.0
var body: some View {
VStack(spacing: 12) {
RealityView { content in
let root = Entity()
root.components.set(
USDStageComponent(allowsHitTesting: true)
)
content.add(root)
rootEntity = root
_ = await USDStageComponent.render(
stage,
to: root,
at: .default
)
applyRotation(to: root)
} update: { _ in
if let rootEntity {
applyRotation(to: rootEntity)
}
}
.frame(minHeight: 280)
.clipShape(RoundedRectangle(cornerRadius: 16))
VStack(alignment: .leading, spacing: 10) {
rotationSlider(
title: "Pitch",
value: $pitchDegrees,
systemImage: "rotate.3d"
)
rotationSlider(
title: "Yaw",
value: $yawDegrees,
systemImage: "rotate.right"
)
rotationSlider(
title: "Roll",
value: $rollDegrees,
systemImage: "arrow.triangle.2.circlepath"
)
}
.padding(.horizontal)
}
}
private func rotationSlider(
title: String,
value: Binding<Double>,
systemImage: String
) -> some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Label(title, systemImage: systemImage)
Spacer()
Text("\(Int(value.wrappedValue))°")
.monospacedDigit()
.foregroundStyle(.secondary)
}
Slider(value: value, in: -180...180)
}
}
private func applyRotation(to root: Entity) {
let pitch = Float(pitchDegrees * .pi / 180)
let yaw = Float(yawDegrees * .pi / 180)
let roll = Float(rollDegrees * .pi / 180)
root.transform = Transform(pitch: pitch, yaw: yaw, roll: roll)
}
}
@available(iOS 27.0, *)
struct USDInspectorView: View {
@State private var model = USDInspectorModel()
@State private var selectedTab = 0
var body: some View {
NavigationStack {
Group {
if let errorMessage = model.errorMessage {
ContentUnavailableView(
"Couldn't Load USD",
systemImage: "cube.transparent",
description: Text(errorMessage)
)
} else if let stage = model.stage {
VStack(spacing: 0) {
Picker("View", selection: $selectedTab) {
Text("Scene").tag(0)
Text("Hierarchy").tag(1)
Text("Source").tag(2)
}
.pickerStyle(.segmented)
.padding()
switch selectedTab {
case 0:
USDStagePreview(stage: stage)
case 1:
List(model.prims) { prim in
VStack(alignment: .leading, spacing: 6) {
Text(prim.path).font(.headline)
Text(prim.description)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
HStack(spacing: 12) {
Label("\(prim.childCount)", systemImage: "arrow.triangle.branch")
Label("\(prim.propertyCount)", systemImage: "list.bullet.rectangle")
Text(prim.specifier).font(.caption.monospaced())
}
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
}
default:
ScrollView {
Text(model.source)
.font(.system(.caption, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
.padding()
}
}
}
} else {
ProgressView("Loading USD stage…")
}
}
.navigationTitle("USD Inspector")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
model.reload()
} label: {
Image(systemName: "arrow.clockwise")
}
}
}
.task {
if !model.isLoaded {
model.load()
}
}
}
}
}
struct USDKitArticleDemoView: View {
var body: some View {
if #available(iOS 27.0, *) {
USDInspectorView()
} else {
ContentUnavailableView(
"iOS 27 Required",
systemImage: "cube.transparent",
description: Text("This sample uses the USDKit framework.")
)
}
}
}
#Preview {
USDKitArticleDemoView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment