Gestures
STANDARD GESTURE TABLE:
| Gesture |
SwiftUI API |
Use Case |
| Tap |
Button() |
Primary actions, navigation |
| Long press |
.contextMenu |
Secondary actions, previews |
| Swipe |
.swipeActions |
List row actions (delete, edit) |
| Drag |
.draggable/.dropDest |
Reorder, drag-and-drop |
| Pull |
.refreshable |
Refresh content |
| Pinch |
MagnifyGesture |
Zoom images/maps |
| Rotate |
RotateGesture |
Rotate content |
BUTTON VS ONTAPGESTURE:
- ALWAYS use Button() for tappable UI elements. Never .onTapGesture on Text/Image/VStack.
- Button provides: accessibility labels, hit testing, highlight states, VoiceOver support.
- .onTapGesture only for non-button interactions (dismiss, background tap).
SWIPE ACTIONS (list rows):
- Trailing side: destructive actions (delete, archive).
- Leading side: positive/toggle actions (pin, favorite, mark read).
- Use .tint() to color-code actions.
- Destructive actions: .role(.destructive) for red styling.
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
deleteItem(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading) {
Button {
toggleFavorite(item)
} label: {
Label(item.isFavorite ? "Unfavorite" : "Favorite",
systemImage: item.isFavorite ? "star.slash" : "star.fill")
}
.tint(.yellow)
}
CONTEXT MENUS:
- Use for secondary actions on any element (not just list rows).
- Group related actions, use Divider() between groups.
- Destructive actions go LAST with .role(.destructive).
.contextMenu {
Button("Edit", systemImage: "pencil") { edit(item) }
Button("Share", systemImage: "square.and.arrow.up") { share(item) }
Divider()
Button("Delete", systemImage: "trash", role: .destructive) { delete(item) }
}
GESTURE PRIORITY:
- Default: child gestures take priority over parent.
- .highPriorityGesture(): parent gesture overrides child.
- .simultaneousGesture(): both recognize at the same time.
- Use .simultaneousGesture for scroll + pinch zoom combinations.
HAPTIC FEEDBACK PAIRING:
- Tap actions: UIImpactFeedbackGenerator(style: .light) for subtle confirmation.
- Toggle/switch: UIImpactFeedbackGenerator(style: .medium).
- Destructive action: UINotificationFeedbackGenerator().notificationOccurred(.warning).
- Success completion: UINotificationFeedbackGenerator().notificationOccurred(.success).
- Selection change (picker/list): UISelectionFeedbackGenerator().selectionChanged().
- Drag start/end: UIImpactFeedbackGenerator(style: .medium).
CONFIRMATION FOR DESTRUCTIVE ACTIONS:
- Always confirm before destructive actions (delete, remove, clear all).
- Use .confirmationDialog for choices, .alert for single confirmation.
.confirmationDialog("Delete Item?", isPresented: $showDelete) {
Button("Delete", role: .destructive) { delete(item) }
Button("Cancel", role: .cancel) { }
}
PULL-TO-REFRESH:
- Use .refreshable {} on List or ScrollView for refresh.
- SwiftUI handles the indicator automatically.
- The closure should be async — SwiftUI shows/hides spinner based on task completion.
SCROLL GESTURES:
- ScrollView handles scroll automatically.
- .scrollDismissesKeyboard(.interactively) for forms with keyboard.
- .scrollIndicators(.hidden) only when indicators are visually distracting.
1---2name: gestures3description: Gesture patterns: tap, swipe actions, long press, drag, context menus, gesture priority, haptic pairing. Use when implementing UI patterns related to gestures.4---5# Gestures67STANDARD GESTURE TABLE:89| Gesture | SwiftUI API | Use Case |10|------------|-----------------------|----------------------------------|11| Tap | Button() | Primary actions, navigation |12| Long press | .contextMenu | Secondary actions, previews |13| Swipe | .swipeActions | List row actions (delete, edit) |14| Drag | .draggable/.dropDest | Reorder, drag-and-drop |15| Pull | .refreshable | Refresh content |16| Pinch | MagnifyGesture | Zoom images/maps |17| Rotate | RotateGesture | Rotate content |1819BUTTON VS ONTAPGESTURE:20- ALWAYS use Button() for tappable UI elements. Never .onTapGesture on Text/Image/VStack.21- Button provides: accessibility labels, hit testing, highlight states, VoiceOver support.22- .onTapGesture only for non-button interactions (dismiss, background tap).2324SWIPE ACTIONS (list rows):25- Trailing side: destructive actions (delete, archive).26- Leading side: positive/toggle actions (pin, favorite, mark read).27- Use .tint() to color-code actions.28- Destructive actions: .role(.destructive) for red styling.29```swift30.swipeActions(edge: .trailing, allowsFullSwipe: true) {31 Button(role: .destructive) {32 deleteItem(item)33 } label: {34 Label("Delete", systemImage: "trash")35 }36}37.swipeActions(edge: .leading) {38 Button {39 toggleFavorite(item)40 } label: {41 Label(item.isFavorite ? "Unfavorite" : "Favorite",42 systemImage: item.isFavorite ? "star.slash" : "star.fill")43 }44 .tint(.yellow)45}46```4748CONTEXT MENUS:49- Use for secondary actions on any element (not just list rows).50- Group related actions, use Divider() between groups.51- Destructive actions go LAST with .role(.destructive).52```swift53.contextMenu {54 Button("Edit", systemImage: "pencil") { edit(item) }55 Button("Share", systemImage: "square.and.arrow.up") { share(item) }56 Divider()57 Button("Delete", systemImage: "trash", role: .destructive) { delete(item) }58}59```6061GESTURE PRIORITY:62- Default: child gestures take priority over parent.63- .highPriorityGesture(): parent gesture overrides child.64- .simultaneousGesture(): both recognize at the same time.65- Use .simultaneousGesture for scroll + pinch zoom combinations.6667HAPTIC FEEDBACK PAIRING:68- Tap actions: UIImpactFeedbackGenerator(style: .light) for subtle confirmation.69- Toggle/switch: UIImpactFeedbackGenerator(style: .medium).70- Destructive action: UINotificationFeedbackGenerator().notificationOccurred(.warning).71- Success completion: UINotificationFeedbackGenerator().notificationOccurred(.success).72- Selection change (picker/list): UISelectionFeedbackGenerator().selectionChanged().73- Drag start/end: UIImpactFeedbackGenerator(style: .medium).7475CONFIRMATION FOR DESTRUCTIVE ACTIONS:76- Always confirm before destructive actions (delete, remove, clear all).77- Use .confirmationDialog for choices, .alert for single confirmation.78```swift79.confirmationDialog("Delete Item?", isPresented: $showDelete) {80 Button("Delete", role: .destructive) { delete(item) }81 Button("Cancel", role: .cancel) { }82}83```8485PULL-TO-REFRESH:86- Use .refreshable {} on List or ScrollView for refresh.87- SwiftUI handles the indicator automatically.88- The closure should be async — SwiftUI shows/hides spinner based on task completion.8990SCROLL GESTURES:91- ScrollView handles scroll automatically.92- .scrollDismissesKeyboard(.interactively) for forms with keyboard.93- .scrollIndicators(.hidden) only when indicators are visually distracting.