简体   繁体   中英

How to detect shake gesture in swiftUI

In apple's documentation, I did not find any gesture related to shake for swiftUI. So how to detect it?

I am very new to swift programming and this question really bothers me for a long time.

In UIKit If I want to detect shake gesture is quite simple and straight forward. In swiftUI, there are bunch of gestures like tap drag rotation, however I cannot find shake gesture in official documentation or anyone who asked. Is this possible in swiftUI to achieve the same result? Or they just forget to add it into the swiftUI framework...

If it's not possible in swiftUI, then how am I going to import the motionEnded function in UIKit to the swiftUI view that I want to detect shake motion?

You can add a new notification and override UIWindow.motionEnded in an extension (as also mentioned in an earlier answer):

extension NSNotification.Name {
    public static let deviceDidShakeNotification = NSNotification.Name("MyDeviceDidShakeNotification")
}

extension UIWindow {
    open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        super.motionEnded(motion, with: event)
        NotificationCenter.default.post(name: .deviceDidShakeNotification, object: event)
    }
}

With this in place, it's easy to subscribe to the notification in your view:

struct Example: View {
    @State private var message = "Unshaken"

    var body: some View {
        Text(message)
            .onReceive(NotificationCenter.default.publisher(for: .deviceDidShakeNotification)) { _ in
                self.message = "Shaken, not stirred."
        }
    }
}

You can do this from the ViewController level. For example:

final class MyVC: UIHostingController<ContentView> {

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

        // Notify view.
    }
}

I am trying to solve the same problem. Any success for you. I am able to detect shakes within AppDelegate, but have no idea how to access my SceneDelegate with my environmentObject

I made this work using a view controller inside of a UIViewControllerRepresentable view.

Corresponding UIViewControllerRepresentable and UIViewController :

struct ShakableViewRepresentable: UIViewControllerRepresentable {

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


class ShakableViewController: UIViewController {

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

        /* Do something */
        print("Shaken")
    }
}

How to implement it:

struct ContentView: View {

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

            /* Other views, in VStack or whatever it may be */
        }
    }
}

iOS 13, Swift 5... a solution that works with SwiftUI based in jk2K answer, with this obviously this is in the ContentView.swift file.

let messagePublisher = PassthroughSubject<String, Never>()

class ContentMode {

...

}

extension UIWindow {
 open override func motionEnded(_ motion: UIEvent.EventSubtype, with event:   UIEvent?) {
    if motion == .motionShake {
        print("Device shaken")
        messagePublisher.send("Stop Shaking Me")
    }
  }
}

You can than use this to pick it up in your UI

.onReceive(messagePublisher) { (message) in
  // message = "Stop Shaking Me"
}

Here's a refinement on the above with a view modifier.

import SwiftUI
import Combine

private let motionShaked = PassthroughSubject<Void, Never>()

extension UIWindow {
     open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        if motion == .motionShake {
            motionShaked.send()
        }
     }
}

private struct ShakeViewModifier: ViewModifier {
    let action: (() -> Void)

    init(perform action: @escaping (() -> Void)) {
        self.action = action
    }
        
    func body(content: Content) -> some View {
        content
            .onAppear {
            }
            .onReceive(motionShaked) {
                action()
            }
    }
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM