Mastering Gestures in SwiftUI: A Comprehensive Guide
Written on
Introduction to SwiftUI Gestures
SwiftUI streamlines the implementation of gestures, enabling developers to enhance productivity. The framework includes several built-in gestures commonly utilized in Apple devices, such as tap, long press, drag, and magnification. In this guide, we will explore how to implement these gestures in SwiftUI.
This guide is part of my ongoing series.
Getting Started with Tap Gestures
Before delving into more complex gestures, let's start with a basic example. We will create a tappable view that triggers a function upon being tapped. Below is the code snippet:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Tap me!")
.foregroundColor(.white)
.padding()
}
.background(.blue)
.onTapGesture {
print("It works")}
}
} // ContentView
Run the application and tap the created stack view. Xcode should output the following message each time the view is tapped:
It works
You can also customize the onTapGesture to respond to multiple taps by adding a count parameter. Modify the .onTapGesture as follows:
.onTapGesture(count: 2) {
print("It works")
}
Now, the stack will only respond to double taps.
Long Press Gestures
Next, let's explore the long press gesture. By using the .onLongPressGesture modifier, we can create an interactive heart image that scales upon being long pressed. Here’s how:
import SwiftUI
struct ContentView: View {
@State private var isTapped = false
var body: some View {
Image(systemName: "heart.fill")
.font(.system(size: 150))
.foregroundColor(.red)
.scaleEffect(isTapped ? 0.5 : 1.0)
.animation(.linear, value: isTapped)
.onLongPressGesture(minimumDuration: 1.0) {
self.isTapped.toggle()}
}
} // ContentView
The minimumDuration parameter specifies how long the object must be pressed (in seconds) for the effect to take place. We added animation to avoid a dull experience during the long press.
Run the application and try long pressing the heart image; it should scale down when pressed.
Utilizing the Gesture Modifier
For more advanced functionality, the .gesture modifier allows you to implement multiple built-in gestures. Here’s a simple example of using long press within the .gesture modifier:
struct ContentView: View {
@State private var isTapped = false
var body: some View {
Image(systemName: "heart.fill")
.font(.system(size: 150))
.foregroundColor(.red)
.scaleEffect(isTapped ? 0.5 : 1.0)
.animation(.linear, value: isTapped)
.gesture(
LongPressGesture(minimumDuration: 1.0)
.onEnded({ _ in
self.isTapped.toggle()})
)
}
} // ContentView
This code snippet yields the same result as the previous example. We also introduced the onEnded() modifier to capture and execute the final value of the gesture.
Using GestureState for Better Management
The GestureState property wrapper allows automatic resetting of the property once the gesture concludes. Although the standard state property wrapper can achieve this, it requires additional coding. For instance, if we want a sun image to change color when tapped and scale up when long pressed, here’s the code:
import SwiftUI
struct ContentView: View {
@State private var isTapped = false
@GestureState private var isLongPressTapped = false
var body: some View {
Image(systemName: "sun.max.fill")
.font(.system(size: 150))
.foregroundColor(isLongPressTapped ? .orange : .yellow)
.scaleEffect(isTapped ? 2.0 : 1.0)
.animation(.easeInOut, value: isTapped)
.gesture(
LongPressGesture(minimumDuration: 1.0)
.updating($isLongPressTapped, body: { value, state, transaction in
state = value})
.onEnded({ _ in
self.isTapped.toggle()})
)
}
} // ContentView
The updating() modifier is responsible for modifying the state, with three parameters: value, state, and transaction.
Run the application; a single tap on the sun image will change its color, and a long press will scale it up.
Implementing Drag Gestures
With a solid understanding of the gesture modifier and GestureState, we can now learn how to implement drag gestures. Modify your existing code to include the following:
import SwiftUI
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero
var body: some View {
Image(systemName: "sun.max.fill")
.font(.system(size: 150))
.foregroundColor(.yellow)
.offset(x: dragOffset.width, y: dragOffset.height)
.gesture(
DragGesture()
.updating($dragOffset, body: { value, state, transaction in
state = value.translation})
)
}
} // ContentView
This allows the image to be dragged anywhere on the screen. The state is updated to reflect the current position of the image during the drag.
To make the image stay where it is dropped, modify the code as follows:
import SwiftUI
struct ContentView: View {
@GestureState private var dragOffset = CGSize.zero
@State private var position = CGSize.zero
var body: some View {
Image(systemName: "sun.max.fill")
.font(.system(size: 150))
.foregroundColor(.yellow)
.offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
.gesture(
DragGesture()
.updating($dragOffset, body: { value, state, transaction in
state = value.translation})
.onEnded({ value in
self.position.height += value.translation.height
self.position.width += value.translation.width
})
)
}
} // ContentView
Now, the sun image can be dragged and will remain in the position where you drop it.
Magnification Gesture
Lastly, we’ll implement a magnification gesture, often used in image applications. Start by ensuring you have a sample image available. Here’s the code for implementing the magnification gesture:
import SwiftUI
struct ContentView: View {
@GestureState private var imageScale = 1.0
var body: some View {
Image("austin-poon")
.resizable()
.scaledToFill()
.scaleEffect(imageScale)
.gesture(
MagnificationGesture()
.updating($imageScale, body: { value, state, transaction in
state = value.magnitude})
)
}
} // ContentView
This code captures the default scale of the image as a variable wrappe