Reading time: 1 min

This recipe shows how to implement shake gesture detection in SwiftUI.

The result looks like this (you'll have to image the phone being shaken :)).

Preview

This component is available as a Swift Package in this repo.

The recipe is as follows:

  1. Override UIWindow's motionEnded for motionShake and have it post a notification.
  2. Receive the notification in your view via onReceive and trigger your action.

To make things nicer, we'll wrap the reception part in a ViewModifier and expose a nice View extension to handle everything.

OK, first define the Notification.Name, as you generally should if you're working with custom notification:

public extension Notification.Name {
  static let shakeEnded = Notification.Name("ShakeEnded")
}

Then, detect the shake gesture in the UIWindow extension:

public extension UIWindow {
  override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    if motion == .motionShake {
      NotificationCenter.default.post(name: .shakeEnded, object: nil)
    }
    super.motionEnded(motion, with: event)
  }
}

Next, add this ViewModifier that adds a onReceive that specifically captures shakeEnded notification:

struct ShakeDetector: ViewModifier {
  let onShake: () -> Void

  func body(content: Content) -> some View {
    content
      .onAppear() // this has to be here because of a SwiftUI bug
      .onReceive(NotificationCenter.default.publisher(for: .shakeEnded)) { _ in
        onShake()
      }
  }
}

Finally, wrap everything in this nice extension:

extension View {
  func onShake(perform action: @escaping () -> Void) -> some View {
    self.modifier(ShakeDetector(onShake: action))
  }
}

And that's it! Now, you can detect shake gesture in any view:

struct ShakeTest: View {
  @State private var text = "Shake me!"

  var body: some View {
    Text(text)
      .onShake { // ADD THIS
        text = "Shaken at \(Date())"
      }
  }
}

Next Post Previous Post