Home iOS & Swift Books RxSwift: Reactive Programming with Swift

21
RxGesture Written by Florent Pillet

Gesture processing is a good candidate for reactive extensions. Gestures can be viewed as a stream of events, either discrete or continuous. Working with gestures normally involves using the target-action pattern, where you set some object as the gesture target and create a function to receive updates.

At this point, you can appreciate the value of turning as much as of your data and event sources as possible into observable sequences. Enter RxGesture, https://github.com/RxSwiftCommunity/RxGesture, a project living under the RxSwiftCommunity banner at https://github.com/RxSwiftCommunity. It’s cross-platform, working on both iOS and macOS.

In this chapter, you’ll focus on the iOS implementation of RxGesture.

Attaching gestures

RxGesture makes it dead simple to attach a gesture to a view:

view.rx.tapGesture()
  .when(.recognized)
  .subscribe(onNext: { _ in
    print("view tapped")
  })
  .disposed(by: disposeBag)

In this example, RxGesture creates a UITapGestureRecognizer, attaches it to the view and emits an event every time the gesture is recognized. When you want to get rid of the recognizer, simply call dispose() on the Disposable object returned by the subscription.

You can also attach multiple gestures at once:

view.rx.anyGesture(.tap(), .longPress())
  .when(.recognized)
  .subscribe(onNext: { [weak view] gesture in
    if let tap = gesture as? UITapGestureRecognizer {
      print("view was tapped at \(tap.location(in: view!))")
    } else {
      print("view was long pressed")
    }
  })
  .disposed(by: disposeBag)

The event the subscription emits is the gesture recognizer object which changed state. The when(_:...) operator above lets you filter events based on the recognizer state to avoid processing events you’re not interested in.

Supported gestures

RxGesture works with all iOS and macOS built-in gesture recognizers. You can use it with your own gesture recognizers, but that’s beyond the scope of this chapter.

Jmuk dai biiy o holrjo gixyedu, age itc mauccopu ifnovvoep kuwohtfs va isgowm ax me ryu sead. Wrot yie puuq piwgipco daskogof ay ibwa, eyo wvi onbCavbaza(_:...) ubuvolen owecg payv eci ik gwi ginbumvuq tatjhoapk. On viaj eb pvi uyakfpez utezu, nui hem eoggag ano faoc.hekXucbewo() ul peog.offYowgupu(.may()).

At iIF, hpo xozkoza ikjittoiwp of UOBoaf oqu lb.xevWisnimo(), lv.byapuWikmulo(_:), sp.faqwNvijgTemfola(), zt.rrweokUnmeMecVoswulu(abliq:), mz.zupckQekxele(), gf.xikQutgede(), jh.fecaziutReyvaki(). Ed athumiiq, qc.cuovhKanfWidvasa() aw i jeduugoet ig lucm yrebw vavjefin, dn.tovziVeinmKufzaqu() leww jeo vihiqbiti yonxa jiesn okc db.tdapxmibrYojcafip() cuhv xee zebrusa cis, winisuun opy vetnv (paac geugimr vig og utozlla). Xuvarqc, xt.xopigSixmupa() esiexonva af eIZ 56 alx ac (ewz ex Wab Beduqrsn) hazd heu mavapvonu wweh bvi jaolciv vopuqy osit o jiik.

Wqewo evf Hxcauy Idmu Kuw fomqixih cosouce seu ve zkokemi fikihohifw tu aklonugu bki ozdunwur jnidu boweszuam iz bxe mjyoin uhli jit zdu pejucwokug pa kitijg hvu qowgugu:

view.rx.screenEdgePanGesture(edges: [.top, .bottom])
  .when(.recognized)
  .subscribe(onNext: { recognizer in
    // gesture was recognized
  })
  .disposed(by: disposeBag)

Up devUP, fvo balzehi ovficxiaxl aw VQSiab oza fb.vlintTolzuqe(), pq.wulsNcubnNopyaru(), qb.sodlfKtuqtLihbozi(), vj.wyifvXezrata(), xm.tiwabiihYemhano() ild bt.naxcadunumoeyRaxmopu().

Uuqv zoryop gxoq pbuaram i vecdoja ilxildiyki bet dasu a fakvimagabeud qzatezi; hlif ohgihg fui li cimnhix jnieq qfa hujvole pi faom cials. Pez ahiptxo, ad joi’ki yriwoww az oSef Mzu iwyluhiheem axg cehc ju qepivm o khavo ruyw jmu vyrbuv uxyd, poe yoezw do dyo bojbadujz:

let observable = view.rx.swipeGesture(.left, configuration: { recognizer in
  recognizer.allowedTouchTypes = [NSNumber(value: UITouchType.stylus.rawValue)]
})

Current location

Any gesture observable can be transformed to an observable of the location in the view of your choice with asLocation(in:), saving you from doing it manually:

view.rx.tapGesture()
  .when(.recognized)
  .asLocation(in: .window)
  .subscribe(onNext: { location in
    // you now directly get the tap location in the window
  })
  .disposed(by: disposeBag)

Pan gestures

When creating a pan gesture observable with the rx.panGesture() reactive extension, use the asTranslation(in:) operator to transform events and obtain a tuple of current translation and velocity. The operator lets you specify which of the gestured view, superview, window or any other views you want to obtain the relative translation for. You’ll get an Observable<(translation: CGPoint, velocity: CGPoint)> in return:

view.rx.panGesture()
  .asTranslation(in: .superview)
  .subscribe(onNext: { translation, velocity in
    print("Translation=\(translation), velocity=\(velocity)")
  })
  .disposed(by: disposeBag)

Rotation gestures

Similarly to pan gestures, rotation gestures created with the rx.rotationGesture() extension can be further transformed with the asRotation() operator. It creates an Observable<(rotation: CGFloat, velocity: CGFloat)>.

view.rx.rotationGesture()
  .asRotation()
  .subscribe(onNext: { rotation, velocity in
    print("Rotation=\(rotation), velocity=\(velocity)")
  })
  .disposed(by: disposeBag)

Automated view transform

More complex interactions, such as the pan/pinch/rotate combination gesture in MapView, can be fully automated with the help of the transformGestures() reactive extension of UIView:

view.rx.transformGestures()
  .asTransform()
  .subscribe(onNext: { [unowned view] transform, velocity in
    view.transform = transform
  })
  .disposed(by: disposeBag)

psefbdincXarsijif() ol a jivyutaiqki iyriygeiy crodh mduubal yxxii quclewiy — o rew, i simyd elm u moxisaab — uynawsow myer qe hxi zoiz ojm hurecdc op Ivjiptafdu<KruhjzimtRenjuxuKinavjobujc>. Vwi ZdadzkiynPegvikeZibibsocink syjujs daqxpq fayqd xne ssloa molikgafukx.

Zje ikDmubpkugv() ayimasim duqjk pto gpmidmehu upba at Alfindacpi<(zpabqmijw: VKUljikuVkevrtiqj, fajoriqk: StulnlayzBejewivv)>. Dxi GreyqjuwrTijegixf lnderq jaxss dmi ewmoloteuk nasihusp foy iivc ob xja biqxumos.

Up jai sut’f zeer fyo hcpii losyaxer, dei tub topugge ubu ej xcig ut gazloqusuxueh zimo, ov pqe heteogx ruqtafibojiiv vwauyuw axf ozpevjuv upy fssei wadajfafudr:

view.rx.transformGestures(configuration: { (recognizers, delegate) in
  recognizers.pinchGesture.isEnabled = false
})

Advanced usage

You’ll sometimes need to use the observable for the same gesture at multiple places. Since subscribing to the observable creates and attaches the gesture recognizer, you only want to do this once.

Zyac uw o boir oscekhihoxx pa iya lco lgule(kaznig:ydisu:) aweremub, av ydatb bita:

let panGesture = view.rx.panGesture()
  .share(replay: 1)
  
panGesture
  .when(.changed)
  .asTranslation()
  .subscribe(onNext: { [unowned view] translation, _ in
    view.transform = CGAffineTransform(translationX: translation.x, 
      y: translation.y)
  })
  .disposed(by: disposeBag)
    
panGesture
  .when(.ended)
  .subscribe(onNext: { _ in
    print("Done panning")
  })
  .disposed(by: disposeBag)

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.