简体   繁体   中英

How to implement a global gesture recognizer which detects tap up event?

I try to implement a global gesture recognizer, which able to detect tap up event, globally.

The following is my 1st attempt.

1st attempt : Global tap up gesture recognizer. (Not perfect)

import UIKit

extension UIWindow {
    static var key: UIWindow! {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

class ViewController: UIViewController {
    // Lazy is required as self is not ready yet without lazy.
    private lazy var globalGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(globalTapped))
    
    private func installGlobalGestureRecognizer() {
        UIWindow.key.removeGestureRecognizer(globalGestureRecognizer)
        UIWindow.key.addGestureRecognizer(globalGestureRecognizer)
    }

    @objc func globalTapped() {
        print("global tapped!")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func handleTapForRedView(_ gesture: UITapGestureRecognizer) {
      print("red view tap")
    }
    
    @IBAction func yellowButtonTap(_ sender: Any) {
        print("yellow button tap")
    }
    
    @IBAction func installGlobalGestureButtonTap(_ sender: Any) {
        print("install global gesture")
        installGlobalGestureRecognizer()
    }
}

However, such solution is not perfect. When the tap up region falls on other touchable components like button, globalGestureRecognizer is NOT able to capture the event. Please refer to the following video.

As you can see in the video, when the touch up region is yellow button, or red custom view, "global tapped!" will NOT be printed.

在此处输入图片说明


I try another attempt.

2nd attempt: Only able to detect tap down (Not perfect)

import UIKit

extension UIWindow {
    static var key: UIWindow! {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        print("global tapped down!")
        
        return false
    }
}


class ViewController: UIViewController {
    // Lazy is required as self is not ready yet without lazy.
    private lazy var globalGestureRecognizer = UITapGestureRecognizer(target: self, action: nil)
    
    private func installGlobalGestureRecognizer() {
        UIWindow.key.removeGestureRecognizer(globalGestureRecognizer)
        UIWindow.key.addGestureRecognizer(globalGestureRecognizer)
        globalGestureRecognizer.delegate = self
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func handleTapForRedView(_ gesture: UITapGestureRecognizer) {
      print("red view tap")
    }
    
    @IBAction func yellowButtonTap(_ sender: Any) {
        print("yellow button tap")
    }
    
    @IBAction func installGlobalGestureButtonTap(_ sender: Any) {
        print("install global gesture")
        installGlobalGestureRecognizer()
    }
}

As you can see in the video, when the tap region fall on other touchable components like button, globalGestureRecognizer is able to capture the event.

在此处输入图片说明

But, this only limit to tap down event. What I wish is to able to capture the tap up event.

Does anyone has idea how to do so? My expectation are

  1. Global gesture will NOT block the original event for button, custom views...
  2. Global gesture will able to detect touch up event anywhere. Even if the event falls on button, custom views... Global gesture still able to detect them.

We did something a few years ago to detect the status bar touches on iOS. In App Delegate we overrode a function like so :


extension AppDelegate {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        // do something here
    }
}

In that function we inspected the touch event and compared its location to the the top 50 pixels of the window.

Here's a solution.

Basically, an instance of a finger on the screen is represented by a UITouch. A multitouch sequence begins when the first finger comes down on the screen, and ends when there are no more fingers on the screen. One UITouch object essentially represents the same finger throughout the multitouch sequence. The system packages all these UITouch objects in an envelope called a UIEvent. This is sent to our UIWindow, who sends it to the correct view using hit testing and stuff. This is done in its sendEvent(:) method.

If we subclass UIWindow, we can override sendEvent(:) to intercept the event. All we have to do is look at the touches in that event and determine if one has ended, and then call super, which sends the event normally.

class MyWindow: UIWindow {

    var touchUpDetectionEnabled = true

    override func sendEvent(_ event: UIEvent) {
        super.sendEvent(event)
    
        guard touchUpDetectionEnabled else { return }
    
        let touchUps = event.allTouches!.filter { $0.phase == .ended }

        for each in touchUps {
            print("😃")
        }
    }
}

That code prints a "😃" each time a touch up happens, even if you were scrolling a UITableView etc, and even if multiple touch ups happen in a single multi touch sequence, or even simultaneously.

There's a bool in there that you can toggle to enable/disable the global touch up detection feature. Oh and, make sure your AppDelegate creates an instance of our UIWindow subclass, and assigns it a root view controller.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: MyWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        self.window = MyWindow()
        self.window?.rootViewController = ViewController()
        self.window?.makeKeyAndVisible()

        return true
    }
}

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