Spatial SwiftUI: Model3D

Model3D is a simple view that can load a USD or `.reality` file and display it in your SwiftUI view.

Overview

Most of the work on this site is done in RealityKit, but there are times when all we need to do is display the contents of a 3D file. In those cases, Model3D may be a good fit.

We can load a model that has been included in the app bundle. Apple suggests that we place 3D assets inside a .rkassets bundle. This bundle can be found in the RealityKitContent bundle if you used a visionOS template project.

This will load a USDZ from the bundle.

Model3D(named: "Earth", bundle: realityKitContentBundle)

We can unpack the ResolvedModel3D and call SwiftUI modifiers on it. For example, let’s mark the earth model and resizable, then wrap it in a frame.

Model3D(named: "Earth", bundle: realityKitContentBundle)  { model in
    model
        .resizable()
        .aspectRatio(contentMode: .fit)
} placeholder: {
    ProgressView()
}
.frame(width: 100, height: 100)

If we need to load a file from a server, we can use an asynchronous version of Model3D. We can show a placeholder and a view to display on error.

Model3D(url: url)
{ phase in
    if let model = phase.model {
        model
            .resizable()
            .aspectRatio(contentMode: .fit)
    } else if phase.error != nil {
        Text("Could not load model \(name).")
    } else {
        ProgressView()
    }
}

Model3D is just a SwiftUI view, so we can use modifiers on it just like any other view. We can use some of the spatial modifiers from this series.

Example: the moon model with frame, offset, and hover.

ModelView(name: "Moon")
  .frame(width: 40, height: 40)
  .offset(x: 50, y: -50)
  .offset(z: 100)
  .hoverEffect()

Apple has a lot more details about Model3D in the documentation, so we won’t cover much more here.

Bonus: We can load more than just USDZ files. For example, when we create scenes in Reality Composer Pro, those are saved as .usda files. We can load them from the bundle just like we did with the earth model above. We could make special preview versions of our scenes that we can use in SwiftUI views. I may use this to provide a menu of scenes for the Dark Spaces project.

Full Example Code

struct Example051: View {

    @State private var isActive = false

    var body: some View {
        VStack {
            Text("Model3D Examples")
                .font(.title)

            HStack(alignment: .center) {
                ModelView(name: "Earth")
                    .frame(width: 160, height: 160)
                    .rotation3DEffect(
                        Angle(degrees: isActive ? 180 : 0),
                        axis: (x: 0, y: 1, z: 0)
                    )
                    .hoverEffect()
                    .onTapGesture {
                        withAnimation {
                            isActive.toggle()
                        }
                    }
                ModelView(name: "Moon")
                    .frame(width: 40, height: 40)
                    .offset(x: 50, y: -50)
                    .offset(z: 100)
                    .hoverEffect()

            }
            .padding(24)

            Text("Earth and Moon from the Reality Composer Pro asset library.")
                .font(.caption)
        }
    }
}

fileprivate struct ModelView: View {

    @State var name: String = ""

    var body: some View {
        Model3D(named: name, bundle: realityKitContentBundle)
        { phase in
            if let model = phase.model {
                model
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else if phase.error != nil {
                Text("Could not load model \(name).")
            } else {
                ProgressView()
            }
        }
    }
}

Download the Xcode project with this and many more examples from Step Into Vision.

Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?