34

I have a label with isUserInteractionEnabled set to true. Now, I need to add UITapGestureRecognizer for the label. Is there a way to add in Rx way.

I have looked at the RxSwift library here. Which they didn't provide any extension for adding gesture. The UILabel+Rx file has only text and attributedText.

Is there any workaround to add gesture to label?

christopher.online
  • 2,614
  • 3
  • 28
  • 52
Rugmangathan
  • 3,186
  • 6
  • 33
  • 44

7 Answers7

76

A UILabel is not configured with a tap gesture recognizer out of the box, that's why RxCocoa does not provide the means to listen to a gesture directly on the label. You will have to add the gesture recognizer yourself. Then you can use Rx to observe events from the recognizer, like so:

let disposeBag = DisposeBag()
let label = UILabel()
label.text = "Hello World!"

let tapGesture = UITapGestureRecognizer()
label.addGestureRecognizer(tapGesture)

tapGesture.rx.event.bind(onNext: { recognizer in
    print("touches: \(recognizer.numberOfTouches)") //or whatever you like
}).disposed(by: disposeBag)
RvdB
  • 868
  • 7
  • 7
  • For me, it throws `'UILabel' is not a subtype of 'UIGestureRecognizer'` error. I'm using `IBOutlet` – Rugmangathan Jun 23 '17 at 18:36
  • 1
    I made a mistake in the answer. You need to use `rx.event` on the recognizer instead of the label. I just updated the answer. – RvdB Jun 24 '17 at 16:31
  • 1
    There's no need to create a seperate gesture recogniser. I you use RX in your file you can just do: label.rx.tapGesture() – FredFlinstone Mar 26 '20 at 15:07
20

Swift 5 (using RxGesture library).

Best and simplest option imho.

     label
        .rx
        .tapGesture()
        .when(.recognized) // This is important!
        .subscribe(onNext: { [weak self] _ in
            guard let self = self else { return }
            self.doWhatYouNeedToDo()
        })
        .disposed(by: disposeBag)

Take care! If you don't use .when(.recognized) the tap gesture will fire as soon as your label is initialised!

atereshkov
  • 4,311
  • 1
  • 38
  • 49
FredFlinstone
  • 896
  • 11
  • 16
  • 6
    It took me some time to find that this functionality is provided by the RxGesture library. – Marcus Sep 11 '20 at 08:51
12

Swift 4 with RxCocoa + RxSwift + RxGesture

let disposeBag = DisposeBag()
let myView = UIView()

myView.rx
  .longPressGesture(numberOfTouchesRequired: 1,
                    numberOfTapsRequired: 0,
                    minimumPressDuration: 0.01,
                    allowableMovement: 1.0)
            .when(.began, .changed, .ended)
            .subscribe(onNext: { pan in
                let view = pan.view
                let location = pan.location(in: view)
                switch pan.state {
                case .began:
                    print("began")
                case .changed:
                    print("changed \(location)")
                case .ended:
                    print("ended")
                default:
                    break
                }
            }).disposed(by bag)

or

myView.rx
.gesture(.tap(), .pan(), .swipe([.up, .down]))
.subscribe({ onNext: gesture in
    switch gesture {
    case .tap: // Do something
    case .pan: // Do something
    case .swipeUp: // Do something 
    default: break       
    }        
}).disposed(by: bag)

or event clever, to return an event. i.e string

var buttons: Observable<[UIButton]>!

let stringEvents = buttons
        .flatMapLatest({ Observable.merge($0.map({ button in
            return button.rx.tapGesture().when(.recognized)
                .map({ _ in return "tap" })
            }) )
        })
norbDEV
  • 4,795
  • 2
  • 37
  • 28
George Quentin
  • 175
  • 2
  • 6
2

As Write George Quentin. All work.

    view.rx
        .longPressGesture(configuration: { gestureRecognizer, delegate in
            gestureRecognizer.numberOfTouchesRequired = 1
            gestureRecognizer.numberOfTapsRequired = 0
            gestureRecognizer.minimumPressDuration = 0.01
            gestureRecognizer.allowableMovement = 1.0
        })
        .when(.began, .changed, .ended)
        .subscribe(onNext: { pan in
            let view = pan.view
            let location = pan.location(in: view)
            switch pan.state {
            case .began:
                print(":DEBUG:began")
            case .changed:
                print(":DEBUG:changed \(location)")
            case .ended:
                print(":DEBUG:end \(location)")
                nextStep()
            default:
                break
            }
        })
        .disposed(by: stepBag)
1

Those extensions are technically part of the RxCocoa libary which is currently packaged with RxSwift.

You should be able to add the UITapGestureRecognizer to the view then just use the rx.event (rx_event if older) on that gesture object.

If you have to do this in the context of the UILabel, then you might need to wrap it inside the UILabel+Rx too, but if you have simpler requirements just using the rx.event on the gesture should be a good workaround.

Evan Anger
  • 712
  • 1
  • 5
  • 24
1

You can subscribe label to the tap gesture

    label
        .rx
        .tapGesture()
        .subscribe(onNext: { _ in
            print("tap")
        }).disposed(by: disposeBag)
Booharin
  • 753
  • 10
  • 10
0

I simple use this extension to get the tap as Driver in UI layer.

public extension Reactive where Base: RxGestureView {
    func justTap() -> Driver<Void> {
        return tapGesture()
            .when(.recognized)
            .map{ _ in }
            .asDriver { _ in
                return Driver.empty()
            }
    }
}

When I need the tap event I call this

view.rx.justTap()
yannisalexiou
  • 605
  • 12
  • 25