- Published on
> Detecting Shake Gestures in SwiftUI
- Authors

- Name
- Mick MacCallum
- @0x7fs
SwiftUI doesn't provide a built-in way to detect shake gestures, but you can bridge to UIKit's motion event system to make it work. The approach involves overriding motionEnded(_:with:) on UIWindow and posting a notification that SwiftUI views can observe.
Setting Up the Notification
First, create a custom notification that will be broadcast whenever a shake gesture is detected:
import UIKit
extension UIDevice {
static let deviceDidShakeNotification = Notification.Name("deviceDidShakeNotification")
}
Overriding UIWindow
Next, extend UIWindow to detect the shake motion and post the notification:
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
NotificationCenter.default.post(name: UIDevice.deviceDidShakeNotification, object: nil)
}
super.motionEnded(motion, with: event)
}
}
This override runs for the entire app, so any shake gesture anywhere will trigger the notification. The call to super.motionEnded ensures the normal responder chain behavior continues.
Creating a SwiftUI View Modifier
Now create a view modifier that listens for the shake notification:
import SwiftUI
struct DeviceShakeViewModifier: ViewModifier {
let action: () -> Void
func body(content: Content) -> some View {
content
.onAppear()
.onReceive(NotificationCenter.default.publisher(for: UIDevice.deviceDidShakeNotification)) { _ in
action()
}
}
}
The .onAppear() call is required here because onReceive doesn't work correctly without it in some SwiftUI contexts. It's a quirk you need to include for reliable behavior.
Adding a View Extension
Wrap the modifier in a convenient extension:
extension View {
func onShake(perform action: @escaping () -> Void) -> some View {
self.modifier(DeviceShakeViewModifier(action: action))
}
}
Using the Shake Gesture
With all the pieces in place, you can now detect shakes anywhere in your app:
struct ContentView: View {
@State private var shakeCount = 0
var body: some View {
VStack(spacing: 20) {
Text("Shake your device!")
.font(.title)
Text("Shakes detected: \(shakeCount)")
.font(.headline)
Image(systemName: "iphone.radiowaves.left.and.right")
.font(.system(size: 60))
.foregroundColor(.blue)
}
.onShake {
shakeCount += 1
}
}
}
Practical Example: Undo Action
A common use case for shake gestures is triggering undo functionality:
struct DrawingView: View {
@State private var strokes: [Stroke] = []
@State private var showUndoAlert = false
var body: some View {
Canvas { context, size in
for stroke in strokes {
context.stroke(stroke.path, with: .color(stroke.color), lineWidth: 3)
}
}
.onShake {
if !strokes.isEmpty {
showUndoAlert = true
}
}
.alert("Undo", isPresented: $showUndoAlert) {
Button("Undo Last Stroke", role: .destructive) {
strokes.removeLast()
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Do you want to undo your last stroke?")
}
}
}
Shake to Refresh
Another practical application is refreshing content:
struct FeedView: View {
@State private var items: [FeedItem] = []
@State private var isRefreshing = false
var body: some View {
List(items) { item in
FeedRow(item: item)
}
.overlay {
if isRefreshing {
ProgressView("Refreshing...")
.padding()
.background(.regularMaterial)
.cornerRadius(10)
}
}
.onShake {
Task {
await refresh()
}
}
}
func refresh() async {
isRefreshing = true
try? await Task.sleep(nanoseconds: 1_000_000_000)
items = await fetchNewItems()
isRefreshing = false
}
}
Shake for Debug Menu
During development, a shake gesture can reveal debugging tools:
struct AppRootView: View {
@State private var showDebugMenu = false
var body: some View {
MainContentView()
.onShake {
#if DEBUG
showDebugMenu.toggle()
#endif
}
.sheet(isPresented: $showDebugMenu) {
DebugMenuView()
}
}
}
struct DebugMenuView: View {
var body: some View {
NavigationStack {
List {
Section("Environment") {
LabeledContent("Build", value: Bundle.main.buildNumber)
LabeledContent("Environment", value: "Development")
}
Section("Actions") {
Button("Clear Cache") { }
Button("Reset User Defaults") { }
Button("Trigger Crash", role: .destructive) { }
}
}
.navigationTitle("Debug Menu")
}
}
}
extension Bundle {
var buildNumber: String {
infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
}
}
Haptic Feedback
Consider adding haptic feedback when a shake is detected to acknowledge the gesture:
struct ShakeWithFeedback: View {
@State private var message = "Shake me!"
var body: some View {
Text(message)
.font(.title)
.onShake {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
message = "Shake detected!"
}
}
}
Testing on Simulator
You can test shake gestures in the iOS Simulator by selecting Device → Shake from the menu bar, or using the keyboard shortcut Control + Command + Z.
The shake gesture is a discoverable but unobtrusive way to trigger secondary actions in your app. It works well for undo, refresh, or accessing hidden functionality without cluttering your UI with additional buttons.
// Continue_Learning
Accessing the Camera Roll in SwiftUI
Learn how to access photos from the camera roll in SwiftUI, including required permissions, Info.plist configuration, and using PhotosPicker for iOS 16+ or UIImagePickerController for earlier versions.
Detecting When a Screenshot is Taken in SwiftUI
Learn how to detect when users take screenshots in your SwiftUI app by observing UIApplication notifications.
Detect Successful Share Sheet Completion in SwiftUI
Learn how to detect when a user successfully shares content using a share sheet in SwiftUI by wrapping UIActivityViewController.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.