How to detect shake gestures in SwiftUI

Share on facebook
Share on twitter
Share on pinterest

iOS devices are capable of detecting when the device gets shaken. Implementing these shake gestures into your app can be a pretty cool feature. But how do you implement a shake gesture in SwiftUI? Well, here you go! 

In our example app, we want to roll the dice by executing the corresponding function when the device gets shaken.

This is the initial code of our SwiftUI ContentView. If you want to learn how it works, feel free to download our free SwiftUI Basics eBook where we build it from scratch.

import SwiftUI

struct ContentView: View {
    
    @State var rolledNumber = 1
    @State var rolledIt = false
    
    var body: some View {
        VStack {
            Image(rolledIt ? "\(rolledNumber)" : "unknown")
                .resizable()
                .frame(width: 100, height: 100)
                .aspectRatio(contentMode: .fit)
                .clipped()
                .padding(.top, 250)
            Spacer()
            Text("Shake device to roll the dice!")
                .padding(.bottom, 40)
            Spacer()
        }
    }
    
    func rollDice() {
        let randomNumber = Int.random(in: 1 ..< 7)
        self.rolledNumber = randomNumber
        self.rolledIt = true
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct RollButtonContent: View {
    var body: some View {
        Text("Roll the dice")
            .frame(width: 240, height: 75)
            .foregroundColor(.white)
            .font(.headline)
            .background(Color.orange)
            .cornerRadius(20)
    }
}


Although detecting shake gestures is not possible by relying on merely SwiftUI, there’s a trick to achieve the same result. Just follow these steps:

Step 1: Create a new file and name it ShakeGestureManager.swift. Make sure you import the SwiftUI and the Combine framework.

import SwiftUI
import Combine


Step 2: Inside this file, you need to create a PassthroughSubject that is part of the Combine framework. By using this, we can notify our SwiftUI app when the device was shaken.

let messagePublisher = PassthroughSubject<Void, Never>()


Step 3: Next, create a UIViewController in this file. Inside this class, we can override the motionBegan method that gets executed when the device gets shaken. ⠀

class ShakableViewController: UIViewController {

    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        
    }
}


Step 4: When the shake gesture event happens, we use our messagePublisher to notify the SwiftUI view later on.⠀

class ShakableViewController: UIViewController {

    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        guard motion == .motionShake else { return }
        messagePublisher.send()
    }
}


Step 5: Since we can’t directly embed UIViewControllers into SwiftUI views, we need to host it inside a UIViewControllerRepresentable. Use the protocol’s makeUIViewController method to initialise the defined view controller. Since we won’t need it, you can leave the updateUIViewController empty.⠀

struct ShakableViewRepresentable: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> ShakableViewController {
        ShakableViewController()
    }
    func updateUIViewController(_ uiViewController: ShakableViewController, context: Context) {
        
    }
}


Step 6: Now we’re ready to insert the ShakeableViewRepresentable into our SwiftUI view. We don’t want it to affect the layout of the remaining views of our ContentView, this is why we place it behind the remaining content by using a ZStack. We also prevent the user from interacting with the representable by applying the .allowsHitTesting(false) modifier. ⠀

var body: some View {
        
        ZStack {
            ShakableViewRepresentable()
                .allowsHitTesting(false)
            VStack {
                //...
            }
        }
    }


Step 7: Now, we can make our SwiftUI listen to the messagePublisher by using the .onReceive modifier. Once the publisher gets fired, we execute our rollDice function.⠀

 var body: some View {
        ZStack {
            ShakableViewRepresentable()
                .allowsHitTesting(false)
            VStack {
                //...
            }
                .onReceive(messagePublisher) { _ in
                    self.rollDice()
                }
        }
    }


That’s it! Run your app in the simulator and click on “Device” and “Shake” to test the implemented shake gesture. You see that detecting shake gestures in SwiftUI is not too hard 🎊⠀



We hope you liked this tutorial. Let us know what you think about it in the comments 👇 If you want to learn more about SwiftUI, make sure you check out our free SwiftUI Basics eBook and our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Leave a Reply

Your email address will not be published. Required fields are marked *

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount