How it works
RotateGesture is a gesture that recognizes a rotation motion as the user moves two fingers around a shared center point on a trackpad or touchscreen. As the fingers turn, it tracks the cumulative angle and reports it as an Angle value, giving you a measurement you can feed directly into a view's rotation. Reach for it whenever a view should respond to a twist — turning a knob, spinning a card, or orienting an element by hand — instead of mapping raw touch coordinates yourself.
Create the gesture with RotateGesture()
The initializer constructs a recognizer that begins tracking once a two-finger rotation is detected. You can pass a minimumAngleDelta to require a small initial turn before the gesture activates, which filters out incidental movement; calling
RotateGesture()with no arguments accepts the default threshold so even slight rotations are honored.Read the angle from the gesture value
While the gesture is active it produces a value whose
rotationproperty is the Angle turned since the gesture started. In the example this drives state directly withangle = value.rotation, so the storedanglealways reflects the current twist of the fingers rather than an absolute screen orientation.Respond to updates with onChanged
Chaining
.onChangedonto the gesture installs a closure that runs on every recognition update, receiving the latestvalue. This is where you apply live feedback as the user rotates; pair it with onEnded when you need to commit or snap a final position once the fingers lift.Attach it to a view with gesture(_:)
A gesture only does work once it's bound to a view's interaction region. Here
.gesture(...)attaches the configuredRotateGestureto the RoundedRectangle, so rotations performed over that view are the ones it recognizes.Apply the result with rotationEffect(_:)
RotateGesture supplies the measurement, and a view modifier consumes it. The
.rotationEffect(angle)modifier reads the sameanglethe gesture writes, visually turning the card so it tracks the user's fingers in real time.
.onEnded { _ in angle = Angle(degrees: 0) } after onChanged so the card snaps back to upright the moment you release, making the difference between a live rotation and its final committed state obvious.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 RotateGestureDemo: View {
@State private var angle = Angle(degrees: 30)
var body: some View {
VStack(spacing: 16) {
Text("Rotate me")
RoundedRectangle(cornerRadius: 16)
.fill(.blue.gradient)
.frame(width: 140, height: 140)
.overlay(Text("\(Int(angle.degrees))°").foregroundStyle(.white).font(.title))
.rotationEffect(angle)
.gesture(
RotateGesture()
.onChanged { value in angle = value.rotation }
)
}
.padding()
}
}