Improved Magnify Gesture

We can smooth out the Magnify Gesture by multiplying the magnification by the initial scale of the entity.

I left this note at the end of the post on Magnify Gesture.

Issue: This doesn’t take into account the initial scale of the entity. If the entity starts at a scale of anything other than 1, you will see it “snap” to the starting scale of the gesture.

We can fix this in the same way we fixed Drag Gesture in Improved Drag Gesture. We just need to:

  • Capture the initial scale of the entity when the gesture starts
  • Multiply the magnification by the initial scale of each axis
  • Apply the new scale
  • Clean up the state when the gesture ends
struct Example011: View {
    var body: some View {
        RealityView { content in
            // Load the scene from the Reality Kit bundle
            if let scene = try? await Entity(named: "GestureLabs", in: realityKitContentBundle) {
                content.add(scene)

                // Lower the entire scene to the bottom of the volume
                scene.position.y = -0.4
            }
        }
        .modifier(MagnifyGestureImproved())
    }
}

 struct MagnifyGestureImproved: ViewModifier {

    @State var isScaling: Bool = false
    @State var initialScale: SIMD3<Float> = .init(repeating: 1.0)

    func body(content: Content) -> some View {
        content
            .gesture(
                MagnifyGesture()
                    .targetedToAnyEntity()
                    .onChanged { value in
                        // Cache the entity's initial scale when the gesture starts
                        if !isScaling {
                            isScaling = true
                            initialScale = value.entity.scale
                        }

                        // Get the magnification from the gesture
                        let magnification = Float(value.magnification)

                        let minScale: Float = 0.25
                        let maxScale: Float = 3

                        // Multiply the magnification by the initial scale to get the new scale
                        // Clamp scale values for each axis independently
                        let newScaleX = min(max(initialScale.x * magnification, minScale), maxScale)
                        let newScaleY = min(max(initialScale.y * magnification, minScale), maxScale)
                        let newScaleZ = min(max(initialScale.z * magnification, minScale), maxScale)

                        // Apply the clamped scale to the entity
                        value.entity.setScale(
                            .init(x: newScaleX, y: newScaleY, z: newScaleZ),
                            relativeTo: value.entity.parent!
                        )
                    }

                    .onEnded { value in
                        // Clean up when the gesture has ended
                        isScaling = false
                        initialScale = .init(repeating: 1.0)
                    }
            )

    }

}

Video Demo

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

Questions or feedback?