Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect and forward taps anywhere on screen in SwiftUI

Tags:

ios

swift

swiftui

In a SwiftUI app, I need to detect any tap on the screen. Only detect, and then forward it to underlying views. As a use case, think of sending "online" user status updates to the server in response to any user activity.

I certainly don't want to add gesture recognizers to every view for this purpose. I tried adding a global one in SceneDelegate. This works as far as detecting any tap goes:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {        
    // ... snip other code
    initGlobalTapRecognizer()
}

// MARK: UIGestureRecognizerDelegate
extension SceneDelegate: UIGestureRecognizerDelegate {
    func initGlobalTapRecognizer() {
        let tapGesture = UITapGestureRecognizer(target: self, action: nil)
        tapGesture.delegate = self
        window?.addGestureRecognizer(tapGesture)
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        print("tapped")
        return true
    }
}

But this breaks some SwiftUI controls. For example, buttons work, but TabView no longer responds to taps.

I tried another way, using simultaneousGesture as suggest here:

struct ContentView: View {
    @State var selectedTab: Int = 1

    var body: some View {
        TabView(selection: $selectedTab) {
            VStack {
                Text("tab 1")
                Button(action: { print("button 1 click") }, label: { Text("button 1") })
            }
            .tabItem( { Text("tab 1") } )
            .tag(1)

            VStack {
                Text("tab 2")
            }
            .tabItem( { Text("tab 2") } )
            .tag(2)
        }
        .contentShape(Rectangle())
        .simultaneousGesture(TapGesture().onEnded({ print("simultaneous gesture tap") }))
    }
}

Same result, buttons work, but TabView is broken.

Any ideas how to get this working?

like image 402
Arman Avatar asked Jun 16 '20 04:06

Arman


People also ask

How do I detect a gesture in SwiftUI?

Gestures performed within the bounds of a view can be detected by adding a gesture recognizer to that view. SwiftUI provides recognizers for tap, long press, rotation, magnification (pinch) and drag gestures. A gesture recognizer is added to a view using the gesture () modifier, passing through the gesture recognizer to be added.

How do I use SwiftUI in a single view application?

If you want to try out the code, create a new project using the Single View Application template and make sure you select SwiftUI as the UI option. Then paste the code in ContentView.swift.

How do I detect up and down swipes?

Each swipe gesture recognizer can only detect swipes in a single direction. That means if you want to detect an up and down swipe, you’ll need to define two separate swipe gesture recognizers where one recognizes only up swipes and the second only recognizes down swipes.

How do I use pan gesture recognizer in Swift?

Move the mouse pointer over the Pan Gesture Recognizer icon in the Document Outline or at the top of the View Controller, hold down the Control key, and Ctrl-drag from the Pan Gesture Recognizer above the last curly bracket in the ViewController. swift file. Release the Control key and the left mouse button.


1 Answers

As a use case, think of sending "online" user status updates to the server in response to any user activity.

  1. Create an extension for a global gesture recogniser:
extension UIApplication {
    func addGestureRecognizer() {
        guard let window = windows.first else { return }
        let gesture = UITapGestureRecognizer(target: window, action: nil)
        gesture.requiresExclusiveTouchType = false
        gesture.cancelsTouchesInView = false
        gesture.delegate = self
        window.addGestureRecognizer(gesture)
    }
}
extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        print("touch detected")
        return true
    }

    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}
  1. Add it to the root window:

SwiftUI 1 version:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {        
    // ...
    addGestureRecognizer()
}

SwiftUI 2 version:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear(perform: UIApplication.shared.addGestureRecognizer)
        }
    }
}
  1. Now the gesture recogniser is attached to the root window and will be recognised simultaneously with other controls:
struct ContentView: View {
    var body: some View {
        TabView {
            Button("Tap me") {
                print("Button tapped")
            }
            .tabItem {
                Text("One")
            }
            List {
                Text("Hello")
                Text("World")
            }
            .tabItem {
                Text("Two")
            }
        }
    }
}

Alternatively, you can create a custom AnyGestureRecongizer as proposed here to detect any gestures.

like image 96
pawello2222 Avatar answered Oct 16 '22 08:10

pawello2222