How it works
ManipulationGestureModifier is the view modifier that recognizes a combined pan, pinch, and rotate manipulation and feeds the resulting transform back into a view's geometry. Rather than wiring up separate DragGesture, MagnifyGesture, and RotateGesture recognizers and reconciling their simultaneous state by hand, you attach this single modifier and SwiftUI tracks the manipulation as one continuous interaction. Reach for it when you want a piece of content to feel directly handled — dragged, scaled, and spun under the user's fingers — with the platform managing gesture composition, inertia, and conflict resolution for you. You rarely name the type directly; you apply it through the manipulable() modifier, which returns a view wrapped in ManipulationGestureModifier.
Apply the gesture with manipulable()
The
manipulable()modifier installsManipulationGestureModifieron the view it decorates, opting that view into the unified pan/pinch/rotate recognizer. In the example it sits at the end of the chain on theRoundedRectangle, so the rectangle — already styled and sized — becomes the manipulable target. Because it returns a normalsome View, it composes with the rest of the modifier chain like any other modifier.Order it after the transform you want to drive
ManipulationGestureModifierreports its manipulation relative to where it is attached, so placement in the modifier chain matters. Here.manipulable()follows.scaleEffect(scale)and.frame(width:height:), meaning the gesture acts on the laid-out, scaled rectangle rather than on an un-transformed version of it. Inserting it before a transform would change which geometry the recognizer measures against.Reflect the manipulation in view state
The manipulation produces a transform that you mirror into your own
@Stateso the view re-renders as the user interacts. The example keeps an@State private var scale: CGFloatand binds it through.scaleEffect(scale); as the manipulation changes, the rectangle'sscaleEffectupdates, and the readoutText(String(format: "Scale: %.2f", scale))shows the live value. This is the standard pattern:ManipulationGestureModifierdrives the interaction, your state records the result.Let SwiftUI compose the sub-gestures
A key reason to choose
ManipulationGestureModifierover hand-assembled recognizers is that it treats translation, magnification, and rotation as facets of one gesture, so they can happen simultaneously without you arbitrating between them. The single.manipulable()call on theRoundedRectanglecovers all three, where doing it manually would mean combiningDragGesture,MagnifyGesture, andRotateGestureand resolving their overlap yourself.
.rotationEffect(angle) (backed by a new @State private var angle: Angle) to the RoundedRectangle alongside .scaleEffect(scale) to see ManipulationGestureModifier drive rotation as well as scale from the same .manipulable() interaction.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct ManipulationGestureModifierDemo: View {
@State private var scale: CGFloat = 1.0
var body: some View {
VStack(spacing: 16) {
Text("Manipulation Gesture")
.font(.headline)
RoundedRectangle(cornerRadius: 16)
.fill(.blue.gradient)
.frame(width: 120, height: 120)
.scaleEffect(scale)
.manipulable()
Text(String(format: "Scale: %.2f", scale))
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
}
}