Spatial SwiftUI: transform3DEffect

Exploring some uses of transform3DEffect and AffineTransform3D.

We recently took a look at both the offset and rotation3DEffect modifiers. We can use transform3DEffect to take complete control of the transform of our view.

This modifier takes an AffineTransform3D. There are many options for creating an AffineTransform3D. I’ll start with a few simple examples. Make sure to check the docs to find the best one for your use case.

Translate (move) the view using a Vector3D

.transform3DEffect(AffineTransform3D(translation: Vector3D(x: 25, y: 25, z: 50)))

Rotate the view using Rotation3D

.transform3DEffect(AffineTransform3D(rotation: Rotation3D(angle: Angle2D(degrees: 20), axis: .x)))

Scale the view using Size3D

.transform3DEffect(AffineTransform3D(scale: Size3D(vector: [0.5, 0.5, 0.5])))

Combine these together

.transform3DEffect(AffineTransform3D(
    scale: Size3D(vector: [0.5, 0.5, 0.5]),
    rotation: Rotation3D(angle: Angle2D(degrees: 20), axis: .x),
    translation: Vector3D(x: 25, y: 25, z: 50)
))

These are the signatures that I use most often, but there are many others.

Watch the video demo:

Full example code:

struct Example033: View {
    @State fileprivate var transformMode: TransformType = .none

    var body: some View {
        HStack() {
            List {
                Section("Transform Mode") {
                    ForEach(TransformType.allCases, id: \.self) { transformType in
                        Button(transformType.rawValue) {
                            self.transformMode = transformType
                        }
                    }
                }
            }
            .frame(maxWidth: .infinity)


            ZStack {
                RoundedRectangle(cornerRadius: 12.0)
                    .foregroundStyle(.thickMaterial)
                    .frame(width: 200, height: 200)

                RoundedRectangle(cornerRadius: 12.0)
                    .foregroundStyle(.stepRed)
                    .frame(width: 200, height: 200)
                    .modifier(ExploringTransform3DEffect(mode: $transformMode))
            }
            .frame(maxWidth: .infinity)
        }
        .padding(EdgeInsets(top: 24, leading: 12, bottom: 12, trailing: 12))
    }
}

fileprivate enum TransformType: String, CaseIterable {
    case none = "None"
    case translate = "Translate"
    case rotate = "Rotate"
    case scale = "Scale"
    case combine = "Scale+Rotate+Translate"
}

fileprivate struct ExploringTransform3DEffect: ViewModifier {
    @Binding var mode: TransformType

    func body(content: Content) -> some View {
        switch mode {
        case .none:
            content
        case .translate:
            content
                .transform3DEffect(AffineTransform3D(translation: Vector3D(x: 25, y: 25, z: 50)))
        case .rotate:
            content
                .transform3DEffect(AffineTransform3D(rotation: Rotation3D(angle: Angle2D(degrees: 20), axis: .x)))
        case .scale:
            content
                .transform3DEffect(AffineTransform3D(scale: Size3D(vector: [0.5, 0.5, 0.5])))
        case .combine:
            content
                .transform3DEffect(AffineTransform3D(
                    scale: Size3D(vector: [0.5, 0.5, 0.5]),
                    rotation: Rotation3D(angle: Angle2D(degrees: 20), axis: .x),
                    translation: Vector3D(x: 25, y: 25, z: 50)
                ))

        }
    }
}

Support our work so we can continue to bring you new examples and articles.

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?