Search code examples
iosswiftrx-swiftrx-cocoa

How to detect double tap with RxSwift


I'm trying to detect a double tap with RxSwift

Without RxSwift I would something like this:

private func setupFakePanView() {
    let singleTapGesture = UITapGestureRecognizer()
    let doubleTapGesture = UITapGestureRecognizer()

    singleTapGesture.numberOfTapsRequired = 1
    doubleTapGesture.numberOfTapsRequired = 2

    singleTapGesture.addTarget(self, action: #selector(self.tapped))
    doubleTapGesture.addTarget(self, action: #selector(self.doubleTapped))

    someView.addGestureRecognizer(singleTapGesture)
    someView.addGestureRecognizer(doubleTapGesture)

    singleTapGesture.require(toFail: doubleTapGesture)
}

@objc private func tapped() {
    // Do something
}

@objc private func doubleTapped() {
    // Do something else
}

Is there a way I could achieve the same with RxSwift, RxCocoa and RxGesture? I've tried the following but of course it doesn't work:

someView.rx
    .tapGesture(numberOfTouchesRequired: 1, numberOfTapsRequired: 1)
    .when(.recognized)
    .subscribe(onNext: { _ in
        // Do something
    })
    .disposed(by: bag)

someView.rx
    .tapGesture(numberOfTouchesRequired: 1, numberOfTapsRequired: 2)
    .when(.recognized)
    .subscribe(onNext: { _ in
        // Do something else
    })
    .disposed(by: bag)

Is there way to let the first tapGesture know the second has to fail?


Solution

  • I've found 2 solutions to solve this problem!

    A. Using a custom UITapGestureRecognizer

    let doubleTapGesture = UITapGestureRecognizer()
    doubleTapGesture.numberOfTapsRequired = 2
    
    let singleTapGesture = UITapGestureRecognizer()
    singleTapGesture.numberOfTapsRequired = 1
    singleTapGesture.require(toFail: doubleTapGesture)
    
    let singleTap = someView.rx
        .gesture(singleTapGesture)
        .when(.recognized)
        .subscribe(onNext: { _ in
            // Do something
        })
        .disposed(by: bag)
    
    
    let doubleTap = someView.rx
        .gesture(doubleTapGesture)
        .when(.recognized)
        .subscribe(onNext: { _ in
            // Do something else
        })
        .disposed(by: bag)
    

    or..

    B. Using a custom UIGestureRecognizerDelegate

    Thanks Kishan for suggesting Jegnux's answer!

    1 - Set a custom delegate for the single tap gesture...

    someView.rx
        .tapGesture(
            numberOfTouchesRequired: 1,
            numberOfTapsRequired: 1,
            configuration: { [weak self] gesture, delegate in
                gesture.delegate = self
            }
        )
        .subscribe(onNext: { _ in
            // Do something
        })
        .disposed(by: bag)
    
    // double tap same as before
    

    2 - Implement gestureRecognizer(_:shouldRequireFailureOf:)

    extension MyController: UIGestureRecognizerDelegate {
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            if let gesture = otherGestureRecognizer as? UITapGestureRecognizer, gesture.numberOfTapsRequired == 2 {
                 return true
            }
            return false
        }
    }
    

    Both solutions work fine.