Simultaneously Combine Gestures

An example of using SimultaneousGesture to create a Magnify + Rotate gesture.

Another way to gestures is to use simultaneously(with:). This time, how about we combine the Magnify and Rotate gestures into a new one?

There are a number of ways to create gestures, but for this example I’ll stick with the custom modifier pattern that we used in the previous examples. Notice that we call RotateGesture3D, the chain it with .simultaneously(), passing MagnifyGesture. We can unpack the data we need from the first and second properties on the gesture value.

struct Example014: View {
    var body: some View {
        RealityView { ... }
        .modifier(ScaleAndRotateGesture())
    }
}

struct ScaleAndRotateGesture: ViewModifier {

    @State var isActive: Bool = false
    @State var initialScale: SIMD3<Float> = .init(repeating: 1.0)
    @State var initialOrientation:simd_quatf = simd_quatf(
        vector: .init(repeating: 0.0)
    )

    func body(content: Content) -> some View {
        content
            .gesture(
                RotateGesture3D(constrainedToAxis: .y)
                    .simultaneously(with: MagnifyGesture())
                    .targetedToAnyEntity()
                    .onChanged { value in

                        // Cache the entity's initial scale and orientation when the gesture starts
                        if !isActive {
                            isActive = true
                            initialScale = value.entity.scale
                            initialOrientation = value.entity.transform.rotation
                        }

                        // Get the rotationa and magnification values
                        if let rotation = value.first?.rotation, let magnification = value.second?.magnification {


                            // Handle rotation (see exaple 012)
                            let rotationTransform = Transform(AffineTransform3D(rotation: rotation))
                            value.entity.transform.rotation = initialOrientation * rotationTransform.rotation

                            // Handle scale (see example 011)
                            let magnificationF = Float(magnification) // convert from CGFloat to Float...
                            let minScale: Float = 0.25
                            let maxScale: Float = 3
                            let newScaleX = min(max(initialScale.x * magnificationF, minScale), maxScale)
                            let newScaleY = min(max(initialScale.y * magnificationF, minScale), maxScale)
                            let newScaleZ = min(max(initialScale.z * magnificationF, minScale), maxScale)
                            value.entity.setScale(
                                .init(x: newScaleX, y: newScaleY, z: newScaleZ),
                                relativeTo: value.entity.parent!
                            )
                        }

                    }
                    .onEnded { value in
                        // Clean up when the gesture has ended
                        isActive = false
                        initialScale = .init(repeating: 1.0)
                        initialOrientation = simd_quatf(
                            vector: .init(repeating: 0.0)
                        )

                    }
                )
    }

}

Video demo

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?