Reputation: 1318
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?
Upvotes: 11
Views: 4744
Reputation: 54611
As a use case, think of sending "online" user status updates to the server in response to any user activity.
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
}
}
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)
}
}
}
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.
Upvotes: 13