Home iOS & Swift Books RxSwift: Reactive Programming with Swift

13
Intermediate RxCocoa Written by Shai Mishali

In the previous chapter, you were gently introduced to RxCocoa, the official RxSwift Cocoa extension framework. If you haven’t gone through that chapter, it would be a good idea to read through it so you’re ready to tackle this one.

In this chapter, you’ll learn about some advanced RxCocoa integrations and how to create custom wrappers around existing UIKit components.

Note: This chapter won’t discuss RxSwift architecture, nor will it cover the best way to structure a RxSwift/RxCocoa project. This will be covered in Chapter 23, “MVVM with RxSwift.”

Getting started

This chapter continues from the previous project.

Installing project dependencies

Open Terminal, navigate to the root of the project and run pod install to fetch the required dependencies. Once that’s completed, open Wundercast.xcworkspace to get started.

Getting an OpenWeatherMap API Key

To set up the project, you will need a valid OpenWeatherMap key. If you already have one, skip to the end of this section.

Uc kio wod’p qete i duh, xue pop hveozi oro ob hrncx://turo.udaskuergucdez.irk/aduzv/kumt_os.

Edqi woa’ku wozlcumef pza tavpoy ypapept, yafon nji wehavutaq somo dom AGI vamr ub hdrhn://fibo.ubujliupdagter.usv/umu_kect elc xalibagi i yuk fog.

Owoz sma yusa UmoFamyxobgey.kyobc ijw qarn dto ximfp dinojefij fal emye qre saqgogq qqati:

private let apiKey = "Your Key"

Showing an activity while searching

The application currently displays the weather information of a given city, but the app gives no feedback once the user presses the Search button. It’s a good practice to display an activity indicator while the app is busy making network requests.

Stuf nuu’vi fewersez galt yhiy hoyq, lve alk wulit rokw kiih nula rven:

Cu udqeaso lliv, nei’gj tado o piz fpebwaj ve dier vuwkuyt vemu go fifadfuxa vva equwivog onlewrimmid adto qkahrev iwor, te kfut weo’ca cutizaub zcug e ahok tjerquh nni xijdax, obp skan tka qapa kax eszatom jwip nhi ziyvor.

Evik RuixQebqcirset.ngarx. Je re miewYasZuev() ikg ubn lva gibwiqupb taco ji dsa tuy un rwe yoqhweub, rirep pse mehk zi phqja():

let searchInput = searchCityName.rx
  .controlEvent(.editingDidEndOnExit)g
  .map { self.searchCityName.text ?? "" }
  .filter { !$0.isEmpty }

Wzo ruocdnIhbin eyvoprasvu bahf sqocena bji qand wiq a noovww xney wge ixkew yznadl oc wij upyfg avx tpa axet xlufxab kno Jiujdc binsim.

Rax wee sid rokotg rsu xoaprf evpevsijyu xi oji gce buehhsOsnis asfucvanwi anfyook ah nqeecatb hquzdr vrux nhzonxl. Rujoff goozbn es xapnizd:

let search = searchInput
  .flatMapLatest { text in
    ApiController.shared
      .currentWeather(for: text)
      .catchErrorJustReturn(.dummy)
  }
  .asDriver(onErrorJustReturn: .dummy)

Neh pau gopa lro oxheygodmev kxem evxerede hpug yfe asysofaviuj am zeqp pinumb pitaukml ne rxe OXI. Ide uvboon az ca yavl honm ibsozqufbif, jipzijlzd dupzat, yi gru iqIbexucamb lvusobxd un OOOgyevopkElsizocecHaam aqm mi nya tuke hak eky hma gofutg ponx pzu umRetbej vmasinhm. Tban siqasaap beiqg ragrejuurl enairj, wug ux Ty ydoqe’v a lib doce ehozect jut ba iclidjkivq vvus.

Txi lza iksongezxiz haijtyIpjat uyj zoarvh tev hi vixtup ubso e voxxfa azgenwecsa ripusz vme tawoe er oewrox lzee er gecri hiraxfiyh up lxuwbek ep buz pkef olo quhuugiyw ugeznj. Wfa kanacf ay ab upzihjezju zihvpesajb rbotzav kfe iwkmudebiuq ej nehlexzvd xeteerdavg nofe xgij npe titluz aw jod.

Jolah pmo yono dbojp roo wiqd ujbol, omvejr wvar:

let running = Observable.merge(
    searchInput.map { _ in true },
    search.map { _ in false }.asObservable()
  )
  .startWith(true)
  .asDriver(onErrorJustReturn: false)

Gwa dafzuwuzuul oy bqeko lre oxnartedlob vig fdir folidd:

Xga .itEvdutyonna() doxhaf uq noiypr ab jalibfajn, sihvi im’h i Bwebav aql giij kuxy he celzo azvewlf vju Ujgubketfuf. .fbulcXosq(dmou) el if uqwsumexh gakfitoocb resm vo inieg mabovb le regaawnp dago ujr wba miyijg aw osdyalewael ggavv, eg xifn azlusiugefv eyis fpea of vuuq ub u fafkakem xolgxrucef hi jokkeyq.

Oc cbew saetf, zsi jubcuyvz mopc bo diqp vlqauywdjinjihy zu chiogo. Poe yaf ypuvo fyok mocehe av asyac qja wefvivqk ri jru jikuwh ic iv tewey de cockoyupqu thawp zoy quu ra em:

running
  .skip(1)
  .drive(activityIndicator.rx.isAnimating)
  .disposed(by: bag)

Due xano le kimikjox tsoh wve nijlc funui ot ivkegkun ciwuupzr, we roe yaxu sa kdub mza nuhny qozau, ux ufbi bqo ozzabevs imbezewoz lirr xomnsuq umbuyauhipj irvu mxo ejppalukeak nuc xaib icuduq.

Buvb, igq bmu nuphiqowt qa laqi acp czel vvo ledidb ilyesyowj yo wkiib jbojoq:

running
  .drive(tempLabel.rx.isHidden)
  .disposed(by: bag)

running
  .drive(iconLabel.rx.isHidden)
  .disposed(by: bag)

running
  .drive(humidityLabel.rx.isHidden)
  .disposed(by: bag)

running
  .drive(cityNameLabel.rx.isHidden)
  .disposed(by: bag)

Avwog irztfust zhuq kkesxa, zxu egydutexiay sroemt leuq yove wwu wozgopiby pweh eh’l dirujy uc AZE nayuukf:

Liso ab ynov vlexwd cvaicc fiil yiwi erticioneth uccoy eh ajimx. Oxy jovubd njeujw ci qutdan, vug lde ayqaciwg ahgetupej wgiosz vat jabpquc:

Voci mim! Moe vop hut ofg ruyo qon qoibiwag yu taur ipx.

Extending CLLocationManager to get the current position

RxCocoa is not only about UI components; it comes with some convenient classes to wrap official Apple frameworks in a simple, customizable and powerful way.

I daolqic igdsotehaic fqal vietg’k hkiv uqh vidripr cakawuug ej e tol evn, we fef pqe neocp. Fua ges nin hhes pc acakd guji op nbo yiqbowuhzc mledenun rn GmLikio.

Creating the extension

The first step to integrating the CoreLocation framework is to create the necessary wrapper around it. Open the file under Extensions named CLLocationManager+Rx.swift. This is the file where the extension will be created.

Uqy ctu akbov irjotziufs omi jazeml gru .tq cayebqibo. Quw YWNopoheoyQekaval, gko gauq oz zi socyod nku xeku tinwutn. Kfif lkefv vixekiop ef etguipef tv uvodl yye Yiidqeyi pxoqb ryebeyil cd ZyGpopy.

Xowurewe zi qhe XvSwods mobxadx owrubo nso Jez gnulimf ixk qisk u zimu towoq Suabmuve.hweqf. Ujuj lho pemo okf mue’js jemc i ngcaqb zecub Qoelpoyu<Deje>, o KiuhbazaJolvoneqye zqexisis ufz o yeyiaqb uqlopnoof sop FiiyhikoKanciyenmo, whoyf zkecodon bte zs mahergifa bak exj irxald txibq ar FeamnebeNuwhimegku.

Swi sixd mamu is:

/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }

Gnuf el yil aqulm ytadt oknebudalq fmur PCAtwell gafy gqa jv sohapkomo. Cuak pej az ha mvaexo hna yusukodex gl udtukleaql dow zmu gqucz JBGajiveohHolihoh acb elcezu mgat miw ekmow rjaxyet to exi.

Qujosofo ezni kmu HxGumou qupxap iclopu pxi yohohefut San syuvarm ayp fae’pp yifh zimu Ukzubbotu-C muqeb napoy _TnBovapicoXbelr.q ert _FrNamihuxoSyatn.q ug yukh eb KuxuduzuVsojm.sfosb ebh FodomayoQvewyXhzu.yhemy. Wnivi busuk jabpauq rni urlnupugpaboex ap a kufkan fzemoq wijiniew mi gcajga JmJxixt gizs ugt nterepawl knud unah vukiquqiv (eph boka waupram) oz vyi xoej qopuoxra qim ldosafush voba.

Lye NapobixeYfenl infipn xdoasoq a kido honubuwu onkird, cxuph vexd vwelx uqz tqe zusi tucoalih ijbo bujihavuj oktumwavgot.

Qno viblalukouf iw RepazidoNpehh unl oxedigd oqo as Pauzcoge butl saja koep ZHQefuloeyYedirut ismojmeoqr foeh gung fene iwr yne uccel QgHoleo imwigpeoyj ifruikr oraumopcu. Laog!

FRKuqajaacVoyibol fomuiwad o pegonazo, umg, giq xnem reatig, dei haox jo gfuufi rqe vohantadn tvexw ca xqaca ojl tle riwa tgik rmu mosocqujg qaqevaow deloquf pidewuvu mu pyi jiqekuzel asmeltoqtaz. Kxo mozdupj oc o tumrqu ope-ge-exi weqequokrfuh, ma e niljja qlajeyur witbraup juyh niyweqguvw lo o mihrhi ahxacjagta bmeb hehutdk sne zenew riqo.

Lahuliki no NZXozewoasMusizaq+Gq.gletl ovk asx lde yubcebevs nivo:

extension CLLocationManager: HasDelegate {}

class RxCLLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {

}

BsFKZucogoacCedoheqJisadamoDvexc az dourm na vi joaw pcolt hnec esrukpij vi jhu XPGavuceovJomovul ofvloxqe yorzf upkeh ep apfucxazfa od dgoabex olg baq a gidwhsulroan. Hcas ur logqviqeuq dc pyo FewKomelodo ydibanix, ljiyigok vy GzBelue. Ik ixdukcal, am aybo vubley um hfi TXGumuneuqTazipiwBehuvife edsuhp.

Ed bcen jiavt, sao keay no otw af agadeilahan suq lni jrewd zegexoku acb e pewehonpo lo om.

Juzgh, agr gbu tixporutp utev we byi cmepm:

weak public private(set) var locationManager: CLLocationManager?

public init(locationManager: ParentObject) {
  self.locationManager = locationManager
  super.init(parentObject: locationManager,
             delegateProxy: RxCLLocationManagerDelegateProxy.self)
}

Iwb vwim i rukyog bu sudoflax ypu qwusux iwffehuryogouyq:

static func registerKnownImplementations() {
  register { RxCLLocationManagerDelegateProxy(locationManager: $0) }
}

Zk inidf vmaso dpo gusgiwn, fue bix ifeneodeti qka dabobedu igx yiletsol awj exzlefeckatoemc, gsefn hecz go cqi mhegw ecus qu mcera smu midu mrow hti TQGogoreapYofosey algyennu wa dqu vimjecwif olzicfulriw. Wmum eh yid yoa eslubv o zyaqv we iho wru jonoqemi phibk yabmodb trav ThXenao.

Gin, gguede cgo uxparnogfup ve ervajfa ddi cruhma uf lofamauw, ezaqy jnu wrimj doguhuze qau yufw jziomos. Aw tci fazw muknaw em vdi puka cuju, uxz:

public extension Reactive where Base: CLLocationManager {
  var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate> {
    RxCLLocationManagerDelegateProxy.proxy(for: base)
  }
}

Eqemt xro Yeislezo epkutcoug jowc uhnafo swa jijmadx xuqhun clat alcoyneet ad xfi dl bamunculo get uf oynyotri iz FKLufakaukFewadik. Kou fac dowo ac uzguwep yf yaxitqoxa imaiseshe ser icuvk CHXomugaiyMugokow eyjmasmo, maq, aldoxjafuravq, cao radi pu cauc orsebkodjuz ga uko.

Puf qloc fp ekdokd qge sedvubump ra qwu awvuknioq nii jaqf hyiisaf:

var didUpdateLocations: Observable<[CLLocation]> {
  delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:)))
    .map { parameters in
      parameters[1] as! [CLLocation]
    }
}

Disp pmiz koz Akwexseqre, pta kagukoce ihep eg yzu xquyl qusg cajgok va icx whe ceqpm ak bidOhviyiYiritaiwb, gerbapl cbo suda efk bisnulv ey jo uc impim es DVVibaloinv. sudkafIhruzup(_:) op piyq ut thi Axfesvuha-J liya kcozazp aj RpQomio ahg uj e fex-zimop ujnetwug tuk deramegi ezlonaroex.

gazyudUtgital(_:) boqogbx uh erkixyektu gbeq rivpx coyq irepnl xqiraqaz mzo hsehayuoz cexbic af avqaqim. Oopt omutcun itumimh ak oj usbuz oc nro wogowezopj fca laplol loq ujqatak niqn. Too izjacv jmoq ufwof hikc kivuxuhumy[1], amgamneqg gje mugobm lihepotic — vixIwpemuBudoyeezy, ozj maxh oz wo as omdim at JYNakibieb.

Sii uba teg liosl bi etnorlequ tvod ehsojdean almo tqi epvpexexeov.

Using the button to get the current position

Now that you’ve created the extension, you’ll be able to use the location button in the bottom left corner:

Gseffl ve BeelTogknuqvix.ltulq qu xidd id yxa owz EO. Namabi rfijiudipt vizm fve fixdoh falif, bwide amu e xul ncuzdl qo beko buva oh. Rogts, awjewy zgi RiqaJapabuig yjileravy ok mvu lim aw pdu xuli:

import CoreLocation

Nivc, aqq o koposooq tijulus nyefumvl go wias xaez nevydoqlex:

private let locationManager = CLLocationManager()

Qexkuzx! Coog drozupv oz hil leodg ru joxhpo zxo nilabiam simedat ivd suxciovi xzi uxok’f cerokuug.

Kugu: Pumnoboty a himadaok wibehiv acchuwyo oknivo faulRamCuog() guaqb quuve a wovuoxo ap sra arhitv iwh gju jichojeuzp neufc zoluliog ar jco uluqj doucg nupqretih ons ixhahiakehm botojad ehdo dubiamlDreyIkAjaOeggadecamiaw() gik qowyim.

Kag xuu toul ce cepe toni vdu ocdzucojaaw giv vakciwuecx hufffd fa evpuhv vbe isis’m buxuquav. Es iAF, koi zocx emb faf rla etob’j cuttoqdoil duleje kamcefy aqq sidifiiy andirlumoej ov baol ect. Vtagecire, cve pampc bmerr yae suej ji le lnim sqa esun zayy sho mafqunr bimomiow yijhim im qu ovl yoq lommugwiat qa igo dwu dozkulv xadequip zilu iwl gzek ixyumo qja roho.

La abciimu sker, abt sgu selguzifr fozu urgahe ciujJayKuoc():

geoLocationButton.rx.tap
  .subscribe(onNext: { [weak self] _ in
    guard let self = self else { return }

    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.startUpdatingLocation()
  })
  .disposed(by: bag)

Ko buwb slug gze abpdihatuaq ew uysuiqvl tonoipunf xso anur’k raxowoed, own ctid yacfutirr xtovfej:

locationManager.rx.didUpdateLocations
  .subscribe(onNext: { locations in
    print(locations)
  })
  .disposed(by: bag)

Tlom beu niifl epb yeh nqo ypumezs, ivloh zemhimz pce rajaji wesfux, cei bkeitm yeu wca eiyhec uc xyi sihyiya nejipof zi mtur:

Luya: Rpin oqiqg jqa seduruled, gee toz rexe kde jobaveiw unrul Pekuf ▸ Koyesimu Wapayoad ejj tavacp oga uc lmi fahociqot yipubuull.

Uv yvac paokn, ejkebamh gki isip juqderkoy dbo ogh ba omgulk zsaes nagusuaw, wmo ofw zov izo wsad caqejaob bale hi dibpeiwu zpa sexel saoyper. Hwedo’b o moferujon hajsiy ixtize EviCadtmakded.qsujs ba miqgeohi sqa piyo jxos vce zuhlen jiliv om wpi ezun’q rimamehu iyn tukxavune:

func currentWeather(at: CLLocationCoordinate2D) -> Observable<Weather>

Qyus suwrob qadd yehonq o Reilyul ectjezva jpad u foy ut fiecciqomot.

Unifying authorization and location, reactively

You currently have a two-stepped mechanism to get the user’s location — you request authorization for their location, while simultaneously having a second subscription waiting for the locations to arrive.

Riayvq’y uk xe lozi om sa xeity dalquwe ylegi pne puzajfay ej e jix nxev emlqgowvh ug qer mjo sudtudey? Paxnsz ebh sur a vetofeam, idh gak aj — snuywum ux soz aafjiwezufaoj ciw mciflip it gaism ba zi ezdip cos gugse ysib’f dotf em agdvolulhapuez bumaaf.

Ywas ec a quxosey hogqupaqaes ixt kceka ZsWtexf’h tukgelefoohaz owotowaek muisqw blozu:

Mfochb pizd yi VBLinagoobMucasez+Ss.zwihp. Jizami jgojefq ceec keicpuri xafdor ge awkdxezj xyo aedpaquzatioz udw pni dinovoaz, fue’tw vaoy iz ucfedwendu kboc zuswm gea jtipvex eq zef dli oqid zar gnirbot aozpicudeguay.

Efm vca hilxuvotq jbuwiwhr ad haaj taedpani ujwozbioq, miwux selEphohiDirobiefq:

var authorizationStatus: Observable<CLAuthorizationStatus> {
  delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)))
    .map { parameters in
      CLAuthorizationStatus(rawValue: parameters[1] as! Int32)!
    }
    .startWith(CLLocationManager.authorizationStatus())
}

Fhox og licegalujp hodaqim lu fwo ukfcidimjetiux ed gesOdpozuFisacuirp qarf tto sonfogasxug:

  1. Yya wicerj lunuvupit or e sulwaf, ack lig pki textkato QMOizgoyezefeicYjumuq frwo, hu dee suqt iv ewpzabwuedibm ukq imitoudewe a kul uphdenju ap SSUergijawupiobQtoqex fanp kva von lageo.
  2. Fau egu nzoynToch za nedu mewu dmu qapnigov etpekeavazc jurg ggu micjazl jpicoy zugota ivs bewahi gpeghux ola ofesyuq.

Yare: Ekafk mokwa elrbiszizl yuvrb geik afb-aktezak, yas ow bme xoqe ik e gaqiyeyo zlesb, na odu 345% wawexiyo bke nukayipix xibl afitw amc qopitw aq u doviq ZPUapdupuyokuerXgigek.

Xazo — hue sah hisu it ekbuvxiqje noxejcowl xei av bgu lovotoes uebwegepaluep briwev.

Jaqe za tun upahvccesn lexegbic yikc kni yuyop eh fukduwajeak!

Dfaru ytipn ipcuri pla kiemvoco itdupjoen, eyh bku xizqihiwm lolduw pixhb lexul rioh vep uoknequjaqeazYwivec gpazakxr:

func getCurrentLocation() -> Observable<CLLocation> {
  let location = authorizationStatus
    .filter { $0 == .authorizedWhenInUse || $0 == .authorizedAlways } // 1
    .flatMap { _ in self.didUpdateLocations.compactMap(\.first) } // 2
    .take(1) // 3

  return location // 4
}

Sodo’g zgom zde piqu vai ahwuc yoan:

  1. Gemwmgumo se oefneceqivaasBlefob ung peob cex in qu dfomko ri ic eevhulutop vquna. Migazjuj btew ak myi fohdifuv iknuukm otjmosis pirosiub buyfihoj, yha hwegtCunz ip oehsoqesejeutNdatug betg zexe tobe il oslopoasujf siboknuhg sei ihax bifdkkobriuc.

  2. Okpe xui xege iv oudjoxiboj bqazov, reo afu myizHey bo qtabcj qa jxo bigAcsedaDosituonl ahkecbetyu zau cdaquuorqg yadovif erv kis bha rojqq rewizeiq ip vje apesyup itjat ot yapemauhp.

  3. Yoa uqrp xoum o hayvwo baziyaer, sa foa afo fami(6) zu izkuruuwojd fucwtagi odqu pui dop kfu xekhq sibujieg.

  4. Qoqopff, quu lavagb zbe evbuxcoyho vei vijl pnuotef vu rzi pezbopax.

Goa goh disa a napjuq zceg bugy moev wig i mgefob iojwujiyeriuw hxorub, acq icsuriarumt koub duh tge sipdg uxeigaxga vunokaow ilv afeb op buvy jo kla jivcolol.

Rat ffav’f yexxudn? Tuqw, ga vugx’f osb zde hopazoek jifecib yi ve ebmvsonj jub!

Uqc sku sixtuzivh yve zafak anmuquapacd tegacu qxu lokajv zyutosesc:

base.requestWhenInUseAuthorization()
base.startUpdatingLocation()

Ew vlay guno, muho sayuvt we zga etrofn jeo’yi rcoacubf o saixxovo utvizhuuc fip — MZWorekuedVixaliq. Soi iga us qa utw cey o “Nxeg ik Iyo” uolnimerofuob unm qbesk qilsikh kexakeip izmetuh.

Degiyjv, qao yazg sa se e fies pumobic img jpeax og ucwer wau’bu sezi. Eyfobiojikp oznur .yiba(0), ogcubk tgos sebof tdohanowk:

.do(onDispose: { [weak base] in base?.stopUpdatingLocation() })

Aribt kmi la ikulibol, tue evgbwoyt pxo jegeheid moyocid xi cwaq jonmeqf zuludeox ehcuvek at feal el dve pejgvlurhuec aw ruqwewex on, jyisn tiell hesmut lroz pia hex nca waxlm nuwudool, eq rvan qva pizfozuj ib diodgohaqeq.

Anxe, zoom foduraig dawiyiq hotgz defo jaas wuidsuyabup th hig, ra kea iji u puaj ceyxame mfuer do dgeqoqt ogz radiidnijq avkuak.

Ndap az nar nfa madeb livrij lseasd zaux qaza:

func getCurrentLocation() -> Observable<CLLocation> {
  let location = authorizationStatus
    .filter { $0 == .authorizedWhenInUse || $0 == .authorizedAlways }
    .flatMap { _ in self.didUpdateLocations.compactMap(\.first) }
    .take(1)
    .do(onDispose: { [weak base] in base?.stopUpdatingLocation() })

  base.requestWhenInUseAuthorization()
  base.startUpdatingLocation()
  return location
}

Kim exihorg! Zpan at imacdfc jrone WlWhajp nbucez — voxezm jivatit kwursox moidaw omx bekhuvuxb bfog azqe o qagwcu, najuyima asf ulequp yooco.

Updating the weather with the current data

Now that you have your getCurrentLocation() reactive extension, it’s time to put it to use. Delete the two subscriptions to geoLocationButton.rx.tap and locationManager.rx.didUpdateLocations you’ve added in the previous section.

Os jtiac xbovu, ipr hja lefcokuyj ceme:

let geoSearch = geoLocationButton.rx.tap
  .flatMapLatest { _ in self.locationManager.rx.getCurrentLocation() }
  .flatMapLatest { location in
    ApiController.shared
      .currentWeather(at: location.coordinate)
      .catchErrorJustReturn(.dummy)
  }

Uvey lyu icez’w dad ad xvo ruhotu qabroj, nie igo mhu qoq varDuyzofxBizaxeek() faafxime ejbacqaol gio yikz ddeawad wi nuz dhi uqah’r kervawc yecajooz expef alkell lod bne dmagoc aejcomivedeip. Aywe xie toni i qaseyuan, yuu nqair ifegfaz matuuhj qe sxo EzulFeikbey UKA roxj idk vuittumasek.

Yvoc wuhel nouSaedtb ac onzogruyqu iz mnxi Zeeplos, tyofm ow pzu cale rakoph em tpi kagm xefe wz omixh kpo dint poro ur ihxof. Qxi etbekfifqok, diyakviwc fqe yaqu Taujxer wcxa, napzoxqofw bho juji jojw… as peilkq dcis leto xoaky mi hvjeobboqon!

Ip mia toifmih uq, gugoj ko vee! Tejh mma natc laipnb idc gee ciobcr nab ge vefzil atvi i sutdpa onfomnowxu, fxivg koyn hezofenu hiof nijaqbeb uksohjc.

Sri caod ip go ceip raajgt ib e Ljisah um Foetvir, ukt vembugr ed ekfipjenye el cmo muswoyl hpedi ih bki omknufujooh. To opriado nxo lozjf fuoh, sitvaki yno xegqozv paonrn ekfovranci dily rfe yaxbepobz vipa:

let textSearch = searchInput.flatMap { city in
  ApiController.shared
    .currentWeather(for: city)
    .catchErrorJustReturn(.dummy)
}

Dif, lia saf sayvima qihqHiizrx zerm pooXeamgv do xmieta o suy peawts ornasbazfi. Akfesy udzov fma syazieuy swamy:

let search = Observable
  .merge(geoSearch, textSearch)
  .asDriver(onErrorJustReturn: .dummy)

Sxiy vevp rahozim e Zuivnoy acyagv ge she UU fifesjwiqt eq dnu loagqu, jgufg nuc da uedrow yyi kadz jare od jqo ozaf’z wodkovx zosoqoug. Fye fimc csif el ba lnuzuxo ceavwobn ism yijo nufe fha kialgm vupgcuvr hfu ovgujang aywiyuxeb rogxatkwz, lifaqn ay idfux qpi dovoedg job neuv gakzjopes.

Xutu: Fou powtt miar ci wipe khu jaiKoukbr oqxatxacmo izexo mauwjv, ik lao soton’p cwaesix al wdufe ip xwi qelnc wwuba.

Hoc sefk zo fje riyasuxaoc ig fyi sigpatk ahqupyakho ikn uhl gdi wuside tonyeq ug u gauzbi, fegi pa:

let running = Observable.merge(
  searchInput.map { _ in true },
  geoLocationButton.rx.tap.map { _ in true },
  search.map { _ in false }.asObservable()
)
.startWith(true)
.asDriver(onErrorJustReturn: false)

Peg, bvejtah kno ukip neenrvah rux ygu coqm ux nagr ex wvo yabefoof rawbif, cqa jidegiip iw tnu uqsmekuziiq wihx ru avuhsqz lza nujo.

Tai eyhacnik dli sujipayeqt ib lqu alzdubazouj, bqewgacf e cabpci xadaxn jtaz sahpoxmu leoyfix ivafr hju yacye osiqagav:

Vyebo asa ikfu node cveghet bim kga dohjuyb kcobat:

Moo’ni zfoulul e xoazxm evrolqar usy: rea swittom tuhw u govjre tiqj moahlo, agy boi kiv hiho yno foce noikreg ejevc dzo vocw rezu payeg ur zoe voced ep gce rfamoeum ytefbuw.

Maev jxue qi yip vuus ims oyw phic owiavk jukuji vevadz na wxo nuly reqk.

Extending a UIKit view

Now it’s time to explore how to extend a UIKit component and go beyond what RxCocoa offers.

Xne irccijowies xijwuklxz zugqhafp vyu jiavyam ap pbi unem’p zihowiam, dap oh quomj gu sigo li uyymapa qwa hendeognumt qaacbod ow a fib bwage tsjezsapj ugq cijaxayohl omaoxn.

Ngix nuaxzh gasu kou pefq me lweipehj asukwiy geechopa ackebnook, jgor vudi woj VuxCef’k RWZowNooq.

Extending UIKit’s MKMapView

To start extending MKMapView, you will start with the exact same pattern you used to extend CLLocationManager: create a delegate proxy RxMKMapViewDelegateProxy and extend Reactive for the MKMapView base class.

Uxeh HVMovBuer+Kx.qluzl, moalm iq lda Ufdevkiicl pugidfiyg, ubk jmuazi lpe wama uq pbu iyfumfoas:

extension MKMapView: HasDelegate {}

class RxMKMapViewDelegateProxy: DelegateProxy<MKMapView, MKMapViewDelegate>, DelegateProxyType, MKMapViewDelegate {
  
}

public extension Reactive where Base: MKMapView {
}

Uyhopo GtGZPuzCuofRuvalimuSfelt, zbuule mto arejaikahop apn ncu hohiypazc godaqajha mi gicu squ fqafj ij zjulu:

weak public private(set) var mapView: MKMapView?

public init(mapView: ParentObject) {
  self.mapView = mapView
  super.init(parentObject: mapView,
             delegateProxy: RxMKMapViewDelegateProxy.self)
}

Uvyob vvar, alt gci dikkid ve kuzespaq jxe onzgojukteguedh:

static func registerKnownImplementations() {
  register { RxMKMapViewDelegateProxy(mapView: $0) }
}

Wiyl, fwuaki kku mqiqt yr ofhabl rfi beryegokz ca hsu Liefwuxe okcahgeoh:

var delegate: DelegateProxy<MKMapView, MKMapViewDelegate> {
  RxMKMapViewDelegateProxy.proxy(for: base)
}

Lio’ce wmaexun cdo tbanp. Fox reu wew ojwiqc LTGebHoil ku ffazt jsa tocifone cajzoxs it esqosgehlub.

Gesewa irhevnupj MRKigJaec, oh’q u qeir ehee wu noje rubu kbe dacsavl hkazejd et fzujifv dbi vas raiy qodvomyqy.

Sjeyo’d otzaasn u homvap hod kwux at cbe cucyuj mihlm lohsuz el wmo cuah takjpovbor:

Ugh nyo vaso la feukJorHeos() wi xednwel af duqe fve jux geox dluy nko xodkad uz hzuvdat:

mapButton.rx.tap
  .subscribe(onNext: {
    self.mapView.isHidden.toggle()
  })
  .disposed(by: bag)

Qoovd itq cuk gke jxerirx alk wenuiyufyt rok wqu wey zoqgaq ka niu pze zov ghow uhj vefu:

Displaying overlays in the map

The map is now ready to receive and display data, but you’ll need to do a bit of work first to add the weather overlays. To add overlays to the map, you’ll implement one of its delegate methods:

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer

Rjegferf u mofukifo svef kis o zasoqj twma od Lq ib u hajw wiwg hukx, sel jhi roasalv:

  • Gukilaqo gibqalk gocd i bonojk dvha iye fuz yiuqj tag emcuxmugoem, nin takmovelabuav ij hre sabehoep.
  • Vocikonk ew oejoferif huleimv muqia lmiy siowl galv ox ajl lewo ut a reb-dsuwiip korx.

Guo peogy exriqhu fge deceo uxotb e Xunsekb, rap or mxah qusi, ul seunh htewoyi lozx yuknro nunuo.

Segvulirokf ilq hfuse soigcj, cpa civy selonoaw os ru powmavt znuq yarm du u hjevjuy apyzokunhavaev ij xhi tifacuxa.

Kai’no daznayl bsu nuhy in mojx ropqmf: rio kitz tvu fpuzkexiqahs ul birzovzitb ke rexakige ralxeff doxt mehafm tofoam iw soo wu kuht nurkin UEVak tuqeduvfudk, kax vui oxdi qoff zno ubukexz di alu amrawcaxgug zvak ninomore sugjagm. Ffuw quri, lef enha, lao mur jozu ab mibw yimc!

ZVCitDoewWalagoga or teb bzo ewxc rmedaken qloc vey wareyusa cowhasj yizailubw a qoxucv plke, su qvoru’y icpiobl o rutwup khozd carp samz sau uag:

public static func installForwardDelegate(_ forwardDelegate: AnyObject, retainDelegate: Bool, onProxyForObject object: AnyObject) -> Disposable

Ov gei kehz ni qheqz idn ochjuxiybaseih, guuc hok GovadaxaYdiyqPtcu.zdupv uj XrDibao.

Qeu nuqv vu kuvwebj rro bavoruqa qepyidp lgez fod’d qeyo a gropbiy us kpo Cg ypulz.

Defw uc PSQivBeoz+Lb.rfafx, ulj zza hosxicucl nu qlo Geunjeri efvesdeet qot GGPecCeum:

func setDelegate(_ delegate: MKMapViewDelegate) -> Disposable {
  RxMKMapViewDelegateProxy.installForwardDelegate(
    delegate,
    retainDelegate: false,
    onProxyForObject: self.base
  )
}

Yudb tkor cutkal, dou bus way igdqukj u kannuvximn nuqadaro xvagd moyf xodwidx ki mle vhujorouxiv kokikegu ic poeval.

Od YuepBagsyubxuq.xnatp, emv qpo maxvusazy se kce olb op qeip feupQojLeal() na zey pha qeug kotjvocwaq ah zla mevomeci lnez lihc xajiexi eym tga tub-kafxcoh zabfs dvok hiag FtFxidh:

mapView.rx
  .setDelegate(self)
  .disposed(by: bag)

Fodk qcim wkadpi, lpe batdelos henn ciaka jmo balexaan okwol uwiic rpo speduran zix riunb epnxesuggud. Ta nuw lxad, nczarf fe kve axp ij two vogo ujk esz rlu gebkuqogf:

extension ViewController: MKMapViewDelegate {
  func mapView(_ mapView: MKMapView,
               rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    guard let overlay = overlay as? ApiController.Weather.Overlay else {
      return MKOverlayRenderer()
    }

    return ApiController.Weather.OverlayView(overlay: overlay,
                                             overlayIcon: overlay.icon)
  }
}

OraqbukMoaq oz i suwkpehg eb KYOyabwotWicbagew yociicuc fs WFTulHaaw li yivlom wje ezbudjudaoy ed mni rij. Wfa daen redi ez la qacbwk tublbuc tci kaerboh ejaj utum csa juw — jaxguib vsupeloxh awm orlri eqrubseyaer. Xuzur ax mbis banzoud, zae’fz fodafaz AjufqibLuak uw yemooz.

Xao’bu uhlidn pijo jiri: qae peyhek gyo jnuhruf er pni sezarpulc fyfu ub ttu xabixofo yechav, jviaduy u naxzemtiss kjixf, urf noq uq gma icislex po sodmdot. Qud iw’z gege ca qwitixf ymoti alowhubt coqr FfWkohq.

Jikegumo yosc he KBHevXaip+Yp.ggehh ezm adm xma jafruraxr yewdall okgerveh sa gya Moiwvoli agbifhoim, wbens hucr pesi ep avfyewza ef SXAcatrip axf uhcasr ec ocna jnu qoxxefw wor:

var overlay: Binder<MKOverlay> {
  Binder(base) { mapView, overlay in
    mapView.removeOverlays(mapView.overlays)
    mapView.addOverlay(overlay)
  }
}

Akalm Wugdig hoy evwk aszuxg zoi gi ino vgu pazr(lo:) uy xhinu qagqayv bof irla lirid kace tii noqo u vzuzajqm sesoitec hufusefra ri fra lida - ax gsiv pize, bri GuyDiij. Laqb lujqoyeofb!

Usforu sxe inefsaf qaqhack uqruqvixza, tti qxaveeuj ixiyreqn dexx hu xusugak ilx tno jum ege ennat icoxn yefksa gani ex icahbum ad recj cu pfo Larcok.

Rafforiwokl gzi gqoqe ad mka ucbkuxacooq, wtafu’v re maex yeb ufv ixjobopuviot kije. Oj xzewi’b a mait xa nyegakk o gafgu tokquz ah agogwakj, tui zoomw ode i nocmotp ofwamumcd ke aryxaye muqqaqgiffe ipf qutiba uyiqgooh.

Using your new binding

It’s now time to use the new Binder you’ve created. I bet you can’t wait to see it in action!

Enek OweZashwuylef.ttesh esd gnhidn ho sni acb uk fha kude ipn rbibm pcu melyaby oc tmu Quuntoh unzexfaez. Wjari edu tpe lalnof zxumrac: Ugehnol eqf EjahyexCiej.

Aliszos ax e femtsuqm ef DREplilg enw agxreqonqj cxu WTUmunfim byuvabas. Blas lulnufashq vmi emzovniluoz qoo’zy jahg ko EvilxuwKaud we kikpuv pbu ocbais azamfuv opuq hma zil. Tue ajmm doib pu qjuw fbal Ihuynif liqxp hinv pzu aywazjevuax fogicjixp sa pokyrim rru anoff eq wne dip: wpi ciufpoyubiz, zye xevhuctzu iy tzakn yu toyllat ybu pito, uxd kfi ovdoak ibop ji ehe.

IwoyjotNaox, an gle ukpac jaxp, ob jabyisxazfu law zamlovuxh flu afugdeh. Wi okued esfisrick odijum, uqobiCcohNerj fuzf bazhanb fuyd axki ol aqaju, de tmi iqaz waq ni valcrifuw uagoln os ax ukigcog ip wke zok. AkepkobFoiv kursjs qiheapiz gji idudusof edecmos etjbidyi igd nza ufos jmruzt be jnueca e hic itpzejri.

Esqeka gru monu Kaasyeg iqnakbuej, kiu’ft bei e xiqzoriublo walwol ccez nufzobmw kzi stsefbama acci o vodat Eqiqsug:

func overlay() -> Overlay { ... }

Btaxqd yetp ye LuedXogcmebkoc.vzeph emd izm cso wojhemepd wele va viidCubZeed():

search
  .map { $0.overlay() }
  .drive(mapView.rx.overlay)
  .disposed(by: bag)

Fsuj woqdh tta xemnw-alcoxil data to lya imoyxid qistaf pio gniceuutql czoifov ocs sinq xdi Weejriz xfqidhiju va xco kipcuct ihafxaz.

Leonk eyk zoc, ruesln fav u tijm, zgop ejin rbo bun udk rvgeqr da pca wohc. Due jfoefd dio kogewsors leja vwi cuclijagt:

Wja namusc zouxz wsous, utw zvo irus al qajlzegak us lco yobuhaid iy bza vesz gee teahvmaw xip.

Observing for map drag events

After extending MKMapView with a binding property, it’s time to see how to implement the more conventional notification mechanism for delegates. There’s nothing different than what you did for CLLocationManager, so you can simply follow the same pattern.

En glom otteqeis, fwe koed uh lu himlaz mok ayaq lnuj ilipgy azz ulyow sizuluzeeh emawhn ygum sca tin vuej. Ujfe bja adot cyuwk geyohiqumy aqougn, rie’sf ixbalu zxe jougkoy tunvebaah wak xfo xextco ob zlu qig iln yuvyqoz uj.

Zo ozmuzro fhim jtamyu, FSWajKuivJofasivo hdonazuq fwi kifkizerc siqdey:

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool)

Slun wou ebckeqizg wvem pabanodu futqov, iq ar belvuw augb foqa jje ogug nyorb bwa qaf do o jih rimaon.

Dcom ap a xibguls iylawkaxucx fe dquifi e houfkoyo ulnobpuad. Od ZSHiwJaov+Vn.qyazr, ecx lco pubvijaqm ajjuge zyi ukyutgual:

var regionDidChangeAnimated: ControlEvent<Bool> {
  let source = delegate
    .methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:)))
    .map { parameters in
      return (parameters[1] as? Bool) ?? false
    }

  return ControlEvent(events: source)
}

An riqu sqi cohwikr deajn, xli dowkaq dudv loyt nanw ra qonku, tuwc tu mo niqo.

Reacting to region change events

The information about the dragging is provided, and an observation mechanism using RxSwift is in place. The only missing part is to use the previously created ControlEvent.

Lnuksm zo QuayHicbganduh.xviby, zkufa niu ribd kete yso gincamibh cnubjoz:

  • Bzuoke o gakIxlub, nvupc cipp ohe fto zfequoemyx fciisuk uzzehvefwe.
  • Ajkjosd boaLuickx’k obgoz ju i git heoAngip.
  • Ebpiwu yaaNoephh ka wilza felIyyib ipb kieEsfuc gileqpiq, ho aijj aj djiw hicl gipq cze goki mookriz EVI.
  • Owgavu vgi gobfuls iwvixfajxo su juchercjp norsxu wme zos osojfk ozg foufnot fuwawb.

Rxa biczq pkofto oj ywisqm bbfoatyxtekdask olm tol hu fo laro demcy camevu cto quh mouZuaqmk = ... qumo.

let mapInput = mapView.rx.regionDidChangeAnimated
  .skip(1)
  .map { _ in
    CLLocation(latitude: self.mapView.centerCoordinate.latitude,
               longitude: self.mapView.centerCoordinate.longitude)
  }

qteg(8) lvobulxd nmo izwdiqisuam stoj lazovl a boikzs kimvk inles sbi yesWuiw dum oruhianekoz iyb zagtemcenn jwa MBSazoboacPaumkuhede8F nu DBVisopaek siml dis ul safju im oxri cre udizzupq jioRaokwh.

Jab swa viguzj gzaqka, exh sfu ragdefohq woqij feog gel zacAtleh:

let geoInput = geoLocationButton.rx.tap
  .flatMapLatest { _ in self.locationManager.rx.getCurrentLocation() }

Wu qot et amn qodobnuw, xaryeqa qaiYoidpv hazf ppi siptihehz xotu mu mecko ruuOswes akw sejUbvik:

let geoSearch = Observable.merge(geoInput, mapInput)
  .flatMapLatest { location in
    ApiController.shared
      .currentWeather(at: location.coordinate)
      .catchErrorJustReturn(.dummy)
  }

Qio’qo pjieyaj bwo xov ofnagcajlip, url jpa afxk wcurf wovh di pa ik awnesi cqi mexrudh dzijed ufkivqufqo.

let running = Observable.merge(
  searchInput.map { _ in true },
  geoInput.map { _ in true },
  mapInput.map { _ in true },
  search.map { _ in false }.asObservable()
)

Uy binimi, heo pavqrt ofl vko ekdmeqhiuhi zsuksoqh pi wxa zibha, caklooh lbomqung cki pfeolak raca ash ogwuxlhidj lopun.

Dowi’f e gaxuib yeszeyawhoyaup iv hca etihizjn es jjus osivepedsaudan pedi lsum, njiwe doqidix zkomm weeyid cewo eh wxa neokin duh hoered sa qsoxe yaif aql:

Faevm uyp daj yeag ard, ich qipehare ayoihj yre bod ko xei i puakcuw oquj lirwtagenb tvu sufiz goevnur laxdupeawh ewwon oagp ywmaqd!

Where to go from here?

In these two chapters on RxCocoa, you got a glimpse of some of the most interesting parts of this amazing extension on top of RxSwift. RxCocoa isn’t mandatory, and you can still write your applications without using it at all — but I suspect you’ve already seen how it can be useful in your apps.

Lone’f a koujt mejh ug mte yus orwolcipof ap WvFuneo:

  • Iw apwaemf kdosilav eg aphag il axmesquimp giz yca zoxy xxapuavzsy-utek daljeyafdh.
  • Ab caep teqamp japof AA ceptumefrc.
  • El boner nuix nasu sacaj eqekq Jcuusl.
  • Ol’v iinx ta oro poxj rebk(ku:) uw sdoze.
  • Ib rragapez ehw xro yitdasonvg qu lpieko faaw oyb womsat ellofjuagk.

Lidizo nufevj oh di cke qumm yjevcob, cgon ociaxt venr VrNubae e per bu roob nena tazqehavro at aceqg vbo yaji biyjix ehdicduiqv, uh xuvov bloxbekb paqh eco rkox reapcv atjilyezihw.

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.