According to How to add a vertical swipe gesture to iPhone app for all screens, I add a two finger swipe down gesture to window, which works fine on normal pages throughout the whole app. But it fails on the page has UIScrollView (like UITableViewController). When I swipe down with two fingers on UIScrollView, it just scrolls it as normal. If I swipe from UINavigationBar that above UIScrollView, it works fine again.
The ideal result is that I can scroll tableview by one finger normally, and call some method by swiping page with two fingers without scrolling tableview. This is used in TweetBot to switch dark mode, works perfectly.
According to Apple's document: Using Responders and the Responder Chain to Handle Events, I think I understand how the Responder Chain works, so I want to ask UIScrollView to ignore the two fingers swipe gesture so that it can pass this event to UIWindow. But I can't figure out how to:
I tried to implement UIGestureRecognizerDelegate's func gestureRecognizer(_:, shouldRequireFailureOf otherGestureRecognizer:) from Apple's document, or override gestureRecognizerShouldBegin(_) by inherit UITableView. But all didn't work.
Any solutions or advice is welcomed.
Update - Final Solution
I simplified joern's solution and here is.
In AppDelegate
// It's not necessary to keep a reference to gesture unless you want to do something further.
lazy var gesture: UIPanGestureRecognizer = {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(twoFingerDidSwipe(recognizer:)))
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
return gesture
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
gesture.delegate = AppDelegate
window.addGestureRecognizer(gesture)
return true
}
@objc func twoFingerDidSwipe(recognizer: UIPanGestureRecognizer) {
let swipeThreshold: CGFloat = 50
if recognizer.state == .changed { // 1
switch recognizer.translation(in: window).y { // 2
case ...(-swipeThreshold):
print("Swipe Up")
recognizer.state = .cancelled // 3
case swipeThreshold...:
print("Swipe Down")
recognizer.state = .cancelled
default:
break
}
}
}
And
extension AppDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Some explanations about differences from joern's solution:
- Only
recognizer.state == .changeis concerned. recognizer.translation(in:)'s parameter should bewindowso that I can do nothing about particular VCs.- After triggering some methods by this recognizer, it should be cancelled to prevent from triggering them continually.
And It's unnecessary to do anything about any particular VC.
With this, two-finger swipe gesture works fine on both normal VC and scroll VC.