Zooming, Dragging, and Rotating simultaneously on an Image in SwiftUI

How to zoom, drag, and rotate an image simultaneously in SwiftUI.

Introduction

In this tutorial, we will learn how to zoom, drag, and rotate an image simultaneously in SwiftUI. We will create a custom SwiftUI view that allows users to interact with an image by zooming, dragging, and rotating it. This functionality is commonly used in photo editing apps and can enhance the user experience of your app.

Prerequisites

  • Xcode 16.0 or later: Ensure you have the latest version of Xcode installed on your device.

Zooming an Image in SwiftUI

To enable zooming on an image in SwiftUI, we can use the MagnificationGesture modifier. This modifier allows users to pinch to zoom in and out of an image. Here’s how you can implement zooming on an image:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import SwiftUI

struct ZoomingImageView: View {
  @State private var scale: CGFloat = 1.0
  @State private var lastScale = 0.0

  var body: some View {
    Image("example-image")
      .resizable()
      .scaleEffect(scale)
      .scaledToFit()
      .gesture(scaleGesture)
  }

  var scaleGesture: some Gesture {
    MagnificationGesture(minimumScaleDelta: 0)
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          scale = handleScaleChange(value)
        }
      }
      .onEnded { _ in
        lastScale = scale
      }
  }

  private func handleScaleChange(_ zoom: CGFloat) -> CGFloat {
    lastScale + zoom - (lastScale == 0 ? 0 : 1)
  }
}

In the code snippet above, we define a ZoomingImageView struct that contains an Image view. We use the scaleEffect modifier to apply the zooming effect based on the scale state variable. The MagnificationGesture is added to the image to handle the pinch gesture for zooming. The onChanged closure updates the scale value based on the pinch gesture’s scale factor. The onEnded closure stores the last scale value for future reference.

Dragging an Image in SwiftUI

To enable dragging on an image in SwiftUI, we can use the DragGesture modifier. This modifier allows users to drag an image across the screen. Here’s how you can implement dragging on an image:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import SwiftUI

struct DraggingImageView: View {
  @State private var offset = CGSize.zero
  @State private var lastOffset = CGSize.zero

  var body: some View {
    Image("example-image")
      .resizable()
      .offset(offset)
      .scaledToFit()
      .gesture(dragGesture)
  }

  var dragGesture: some Gesture {
    DragGesture(minimumDistance: 0)
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          offset = handleOffsetChange(value.translation)
        }
      }
      .onEnded { _ in
        lastOffset = offset
      }
  }

  private func handleOffsetChange(_ offset: CGSize) -> CGSize {
    var newOffset: CGSize = .zero

    newOffset.width = offset.width + lastOffset.width
    newOffset.height = offset.height + lastOffset.height

    return newOffset
  }
}

In the code snippet above, we define a DraggingImageView struct that contains an Image view. We use the offset modifier to apply the dragging effect based on the offset state variable. The DragGesture is added to the image to handle the drag gesture. The onChanged closure updates the offset value based on the drag gesture’s translation. The onEnded closure stores the last offset value for future reference.

Rotating an Image in SwiftUI

To enable rotating an image in SwiftUI, we can use the RotationGesture modifier. This modifier allows users to rotate an image by using two fingers. Here’s how you can implement rotating an image:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import SwiftUI

struct RotatingImageView: View {
  @State private var rotation: Angle = .degrees(0)
  @State private var lastRotation = Angle(degrees: 0)

  var body: some View {
    Image("example-image")
      .resizable()
      .rotationEffect(rotation)
      .scaledToFit()
      .gesture(rotationGesture)
  }

  var rotationGesture: some Gesture {
    RotationGesture()
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          rotation = handleRotationChange(value)
        }
      }
      .onEnded { _ in
        lastRotation = rotation
      }
  }

  private func handleRotationChange(_ angle: Angle) -> Angle {
    lastRotation + angle
  }
}

In the code snippet above, we define a RotatingImageView struct that contains an Image view. We use the rotationEffect modifier to apply the rotation effect based on the rotation state variable. The RotationGesture is added to the image to handle the rotation gesture. The onChanged closure updates the rotation value based on the rotation gesture’s angle. The onEnded closure stores the last rotation value for future reference.

Combining Zooming, Dragging, and Rotating Simultaneously

To combine zooming, dragging, and rotating an image simultaneously in SwiftUI, we can use the simultaneousGesture modifier. This modifier allows us to combine multiple gestures on a single view. Here’s how you can implement zooming, dragging, and rotating an image simultaneously:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import SwiftUI

struct ZoomingDraggingRotatingImageView: View {
  @State private var scale: CGFloat = 1.0
  @State private var lastScale = 0.0
  @State private var offset = CGSize.zero
  @State private var lastOffset = CGSize.zero
  @State private var rotation: Angle = .degrees(0)
  @State private var lastRotation = Angle(degrees: 0)

  var body: some View {
    Image("example-image")
      .resizable()
      .scaleEffect(scale)
      .rotationEffect(rotation)
      .offset(offset)
      .scaledToFit()
      .gesture(
        scaleGesture
          .simultaneously(with: dragGesture)
          .simultaneously(with: rotationGesture)
      )
  }
}

// MARK: - Scale
private extension ZoomingDraggingRotatingImageView {
  var scaleGesture: some Gesture {
    MagnificationGesture(minimumScaleDelta: 0)
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          scale = handleScaleChange(value)
        }
      }
      .onEnded { _ in
        lastScale = scale
      }
  }

  private func handleScaleChange(_ zoom: CGFloat) -> CGFloat {
    lastScale + zoom - (lastScale == 0 ? 0 : 1)
  }
}

// MARK: - Drag
private extension ZoomingDraggingRotatingImageView {
  var dragGesture: some Gesture {
    DragGesture(minimumDistance: 0)
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          offset = handleOffsetChange(value.translation)
        }
      }
      .onEnded { _ in
        lastOffset = offset
      }
  }

  private func handleOffsetChange(_ offset: CGSize) -> CGSize {
    var newOffset: CGSize = .zero

    newOffset.width = offset.width + lastOffset.width
    newOffset.height = offset.height + lastOffset.height

    return newOffset
  }
}

// MARK: - Rotation
private extension ZoomingDraggingRotatingImageView {
  var rotationGesture: some Gesture {
    RotationGesture()
      .onChanged { value in
        withAnimation(.interactiveSpring()) {
          rotation = handleRotationChange(value)
        }
      }
      .onEnded { _ in
        lastRotation = rotation
      }
  }

  private func handleRotationChange(_ angle: Angle) -> Angle {
    lastRotation + angle
  }
}

Conclusion

In this tutorial, we learned how to zoom, drag, and rotate an image simultaneously in SwiftUI. By combining the MagnificationGesture, DragGesture, and RotationGesture modifiers, we were able to create a custom SwiftUI view that allows users to interact with an image in a photo editing app. You can further customize the gestures and animations to suit your app’s design and user experience requirements. I hope you found this tutorial helpful, and feel free to leave any questions or feedback in the comments below. Happy coding!

References

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy