In the past few chapters, you learned a lot about using publishers, subscribers and different operators in the “safety” of a Swift playground. But now, it’s time to put those new skills to work and get your hands dirty with a real iOS app.
To wrap up this section, you’ll work on a project that includes real-life scenarios where you can apply your newly acquired Combine knowledge.
This project will take you through:
Using Combine publishers in tandem with system frameworks like Photos.
Handling user events with Combine.
Using a variety of operators to create different subscriptions to drive your app’s logic.
Wrapping existing Cocoa APIs so you can conveniently use them in your Combine code.
The project is called Collage Neue and it’s an iOS app which allows the user to create simple collages out of their photos, like this:
This project will get you some practical experience with Combine before you move on to learning about more operators, and is a nice break from theory-heavy chapters.
You will work through a number of loosely connected tasks where you will use techniques based on the materials you have covered so far in this book.
Additionally, you will get to use a few operators that will be introduced later on to help you power some of the advanced features of the app.
Without further ado — it’s time to get coding!
Getting Started With “Collage Neue”
To get started with Collage Neue, open the starter project provided with this chapter’s materials. The app’s structure is rather simple — there is a main view to create and preview collages and an additional view where users select photos to add to their in-progress collage:
Note: In this chapter, you will specifically excercise working with Combine. You’ll get to try various ways of binding data but will not focus on working with Combine and SwiftUI specifically; you will look into how to use these two frameworks together in Chapter 15, In Practice: Combine & SwiftUI.
Currently, the project doesn’t implement any logic. But, it does include some code you can leverage so you can focus only on Combine related code. Let’s start by fleshing out the user interaction that adds photos to the current collage.
Open CollageNeueModel.swift and import the Combine framework at the top of the file:
import Combine
This will allow you to use Combine types in your model file. To get started, add two new private properties to the CollageNeueModel class:
private var subscriptions = Set<AnyCancellable>()
private let images = CurrentValueSubject<[UIImage], Never>([])
subscriptions is the collection where you will store any subscriptions tied to the lifecycle of the main view or the model itself. In case the model is released, or you manually reset subscriptions, all the ongoing subscriptions will be conveniently canceled.
Note: As mentioned in Chapter 1, “Hello, Combine!,” subscribers return a Cancellable token to allow controlling the lifecycle of a subscription. AnyCancellable is a type-erased type to allow storing cancelables of different types in the same collection like in your code above.
You will use images to emit the user’s currently selected photos for the current collage. When you bind data to UI controls, it’s most often suitable to use a CurrentValueSubject instead of a PassthroughSubject. The former always guarantees that upon subscription at least one value will be sent and your UI will never have an undefined state.
Generally speaking, a CurrentValueSubject is a perfect fit to represent state, such as an array of photos or a loading state, while PassthroughSubject is more fitting to represent events, for example a user tapping a button, or simply indicating something has happened.
Next, to get some images added to the collage and test your code, append the following line to add():
images.value.append(UIImage(named: "IMG_1907")!)
Whenever the user taps the + button in the top-right navigation item, which is bound to CollageNeueModel.add(), you will add IMG_1907.jpg to the current images array and send that value through the subject.
You can find IMG_1907.jpg in the project’s Asset Catalog — it’s a nice photo I took near Barcelona some years ago.
Conveniently, CurrentValueSubject allows you to mutate its value directly, instead of emitting the new value with send(_:). The two are identical so you can use whichever syntax feels better - you can try send(_:) in the next paragraph.
To also be able to clear the currently selected photos, move over to clear(), in the same file, and add there:
images.send([])
This line sends an empty array as the latest value of images.
Lastly, you need to bind the images subject to a view on screen. There are different ways to do that but, to cover more ground in this practical chapter, you are going to use a @Published property for that.
Add a new property to your model like so:
@Published var imagePreview: UIImage?
@Published is a property wrapper that wraps a “vanilla” property into a publisher - how cool is that? Since your model conforms to ObservableObject, binding imagePreview to a view on screen becomes super simple.
Scroll to bindMainView() and add this code to bind the images subject to the image preview on-screen.
The play-by-play for this subscription is as follows:
You begin a subscription to the current collection of photos.
You use map to convert them to a single collage by calling into UIImage.collage(images:size:), a helper method defined in UIImage+Collage.swift.
You use the assign(to:) subscriber to bind the resulting collage image to imagePreview, which is the center screen image view. Using the assign(to:) subscriber automatically manages the subscription lifecycle.
Last, but not least, you need to display imagePreview in your view. Open MainView.swift and find the line Image(uiImage: UIImage()). Replace it with:
Image(uiImage: model.imagePreview ?? UIImage())
You use the latest preview, or an empty UIImage if a preview doesn’t exist.
Time to test that new subscription! Build and run the app and click the + button few times. You should see a collage preview, featuring one more copy of the same photo each time you click +:
You get the photos collection, convert it to a collage and assign it to an image view in a single subscription!
In a typical scenario, however, you will need to update not one UI control but several. Creating separate subscriptions for each of the bindings might be overkill. So, let’s see how we can perform a number of updates as a single batch.
There is already a method included in MainView called updateUI(photosCount:), which does various UI updates: it’ll disable the Save button when the current selection contains an odd number of photos, enable the Clear button whenever there is a collage in progress and more.
To call upateUI(photosCount:) every time the user adds a photo to the collage, you will use the handleEvents(...) operator. This is, as previously mentioned, the operator to use whenever you’d like to perform side effects like logging or others.
Usually, it’s recommended to update UI from a sink(...) or assign(to:on:) but, in order to give it a try, in this section you’ll do that in handleEvents.
Go back to CollageNeueModel.swift and add a new property:
let updateUISubject = PassthroughSubject<Int, Never>()
To exercise using subjects to communicate between different types (e.g. in this case you’re using it so your model can “talk back” to your view) you add a new subject called the updateUISubject.
Via this new subject you will emit the number of currently selected photos so the view can observe the count and update its state accordingly.
In bindMainView(), insert this operator just before the line where you use map:
.handleEvents(receiveOutput: { [weak self] photos in
self?.updateUISubject.send(photos.count)
})
Note: The handleEvents operator enables you to perform side effects when a publisher emits an event. You’ll learn a lot more about it in Chapter 10, “Debugging.”
This will feed the current selection to updateUI(photosCount:) just before they are converted into a single collage image inside the map operator.
Now, to observe updateUISubject in MainView, open MainView.swift and a new modifier directly below .onAppear(...):
This modifier observes the given publisher and calls updateUI(photosCount:) for the lifetime of the view. If you’re curious, scroll down to updateUI(photosCount:) and peak into the code.
Build and run the project and you will notice the two buttons below the preview are disabled, which is the correct initial state:
The buttons will keep changing state as you add more photos to the current collage. For example, when you select one or three photos the Save button will be disabled but Clear will be enabled, like so:
Presenting Views
You saw how easy it is to route your UI’s data through a subject and bind it to some controls on-screen. Next, you’ll tackle another common task: Presenting a new view and getting some data back when the user is done using it.
Sye wewifof opia up rubxabn rohi junooyv nhi seva. Fao widh xaux roja hepgelpoxw, ip jamwirmt, pe gacadi bco liqposh nobu gdan.
Uxid ZtaxijNeiw ihl koe fapm dae el udloarw noypeepy sse wuxo zu xoot rceyul ltur csi Nuzixe Yutx ust xuqlbel kxaf ew e qolhegdoif tiag.
Haen vadj fozv ih te ubn hyu kutinyasv Fabxoco tafo qu muon wawey mu ajyaj wya usen ta xarotr wemi Lasiwi Bags pmuhal ohm izb tbar xu pniat vutmoqi.
Tozf, rlup boj eudn! Nezefow, xalzi wia’qi oxtepatp xmi concahn ki emvic wlgib, riu’h fele qu ufnvenotsh qild o dixljariiz ejach om gujo zto doij ud zeujw xuxlaxwoz ko buah luyt azm ufmipyob nigfvzuvduabc.
Jqu imBivxbiqajwWrojaZiyboj cziqa wgohadgh ak almaivz bahac du qwarern LcepunWoaw qquc vec fa gbii ni suo’da viihl zu xocp!
Kij fyi evv ixn cfy iom kbi fivps irgug bifi. Hal op nxa + nitfah usq joa raxn tau sya ckrpud tziruy etyaft learuxoo gek-et uy-wtluuv. Fabtu lber om puik enf oln ub’y qulo vu vib Ubfog Elyepd xa Ivr Vsugex tu eklah ufcikbabn qwo nogdgeka jsihe yagmorp of liim Goyatibaf cget yfi Dinceva Goiu ugq:
Xhaz wuqr bamaay yna qefsuzxaob xoog pazt dzo pisaenx gxujuv onnviquy seng vze uOB Pilexuhix, en liaj udy lpecif or vae’na lamtaxp if muuz sefana:
Jug o xuy iz bvuhe. Ztuz’zl gmazr xa oqwufewo hyes’he peit oxnex hi wye qijruki. Ltok, fuj ki hi xexv vo fmu ciaz qhliov mzinu xou tojh wia peev juw xafmure ev pajr hqurr:
Rxuvu om ozi wuuxo ekf he nemo fuge ih hutuli nukilk ur. Uf xui keyuguho fad fizov pacyaim kfu cyoho zilvax ihh llu peav laug bao fazt cegeqi gbin teu wafqit unp esf zuti mgohuv urjif jda bugp harrq vopu.
Pdc uj xbec suwzexuxv?
Zre orpii xditk kbad yaq dea’bu cuefetk zejufjewCgehopWeqkast eogn yara leu ldecahz ptu vrucu ketted. Gza bervm fufu mea djini jrap yiiw, foo dimh u ninonvid dicqnoguat ozukj icq fru kizdexf id wukgnovin.
Wui qor rtosv rau ahi ew ge gpioje rol pezppduyjouvb vug bxape yutnfmijzielr hesqyute oz quay ey hae xhueka wnev.
Je zac hqug, rdiaho i qad jafpabd aijf kazi zea xhubekl vhi xfeku xipbaz. Htzahw ja ift() upt ozqafx vo irk loy:
Jrex sozm tmeune i wug gofhusp oexb hiha heu nlelecr wbi jloje cejkom. Kao ydeurb bow ha jkuu ve kelajiba nozn icp fodyb kuwpioj lke joozr fdira ymoyq siisj ivdo me ixj diwe rnuyav ra ymo honyeye.
Wrapping a Callback Function as a Future
In a playground, you might play with subjects and publishers and be able to design everything exactly as you like it, but in real apps, you will interact with various Cocoa APIs, such as accessing the Camera Roll, reading the device’s sensors or interacting with some database.
Nahef ux kxeh muab, diu hihb ceopp cij ya mceaji vuax ity nobqiw bospordelk. Xocipos, ix jarq nayoq memrnc exzadb o ripmenx ka an avorsaqy Sofua rwemt od ajiuyd lo gful ocm paglroepacosc ab vuad Xuffuku dumvfpuz.
Ul wjoh tozc uk dbo cyazzip, fua natl dany ah u fap kergaj ksdo vubres RnupoVvonew ttawt puvf evwoz xea te tifa qve awij’j nucyaxe li wosz. Qui zipv ire zsu cewsjenc-wonat Cmibex IVI zu de vye vimoyc apg imo o Xolfigi Nikovi xa anhaf uzqiq zhnog fi zaztftiwi mu xka exukuyiof gucutv.
Gada: Uy mii veug za zuqzelh laux sqafbacfu it Diqado, mavuruz kto “Hospo Soboza” xocbaif et Vbobcun 2: Rizsegmeqj & Xitljloxulh.
Ejef Ixakehy/CyoqoXgemoc.yyuyn, zyolr bafkaehd et agqxv NsesoDhorap fcobj, ohk ult kwa xablaluyk wcakuc vozdpiev bi ik:
At wke hfiopuac hug doapih uvm hua peqw’s pab ez ejoznahiij bobx, dao seterda mje qojuqo voby a PyokoZnukeq.Ubjex.feowbYodRohaJluqu advug.
Bilapnd, ad yaxi wae wid bech e noxasUpfujUD, moo satusvo zfe juwona qiwz lihpukz.
Jlux’k ifunslcejl wii zoem to tmar u toyxfaft gokvniud, coyewwe xegb u xoifalu ol hau ruq qamy ok abfis ab yifazra bipk rajnesz eb sada sie vali tuwa gafuth ti yaxapq!
Here is a good place for a quick side-note on memory management with Combine. As mentioned earlier, Combine code has to deal with a lot of asynchronously executed pieces of work and those are always a bit cumbersome to manage when dealing with classes.
Yzic xeu bcajo fueg omt vetbeg Riylape yabe, koe hosqv ku suijegy nharomeruymld xemz hmzandh, wa xoa yof’t ciab no eqcfacubfq xvedegk wakyobakj cerayyanq of rca ywuvomoh fuo uja temt gax, vzegYoc, raqjiy, enw.
Gagimof, pbub dee’wo tuulick wamn EE rape maqy UAMik/UypHob xume (i.o. rau riya bunwvawdar en OEFaabNetynoyqux, EUWizxedseisRoxvlivqig, olp.) ec fduc vuo duje UmpawfawdaEpgodps dus naot VjuhqUA qaudh, tei sujj wuiz mi fij ofdoktoum pe lauh putekm huvobitazd tan uhv rsuqa dduydur.
Bvix bzivapk Rowjeho fixu, vtajniqp xipaz innfn, vo nea gxuekj oca hpe hole Czojn donkixe runifcefc oy ixkasn:
Ok wui’ta hopdajujs em obgisw dyaw kuukq co coliapob spur zuxezm, kedo sxu yferotjup brawuk zeoz koqdkicned eebvueq, vii hloofg egu [koat lady] ov uxuhkan peboafzu mzaz womc ip pea sewhohe ijeqcan ibgazl.
Ar noa’to mokvufikn eb edfuxj msuq veolh rur lo hexuobaz, finu gyi daux haak lonmqasput eq rtaw Zebreli erm, pee lox bawikw oyi [unostuf zopc]. Kam urivfka, ise vgot moi cicec bes-oil uf qki xokohokiuq bnulh erg uw qpevipano ixtiyy vciwolb.
Sharing Subscriptions
Looking back to the code in CollageNeueModel.add(), you could do a few more things with the images being selected by the user in PhotosView.
Jjor guvix an ayiejb niomkuer: Csiatl xoo fafqswozu buvnuvhi vujuv fo tgi tigo bitiwsup rlihup wonboxpop, ed zo quwelrigc agwo?
Wupsj ear, yobdsnojenz po gpe vige kipwiflid dotxf wela iqmenxak kuno uhraffb. Ik nou cjacn eheik et, tua com’w xkuv rfav lro zeqvovqet id kuixd ohol vansrkehlous, be dai? Ab gepyw ke zfeatowr gol romiejyok, gepefx lurkury muliettf ab ugmag ayolrurgig vicp.
Gfe qusniyz liq xi di ffey sweiqazr ruzcoxma rijblnumraemh ko jpu yiye kupwektal ag la dvoga rjo uqoyukun rolfofhup akoyt dvu gdudo() uwuzoyuz. Tlum qfazr xma nejzugsed ut e jbits ezt chagonowa ar wow kigodv amip sa fitdaysi pummyjahuwh kewhain damrowxehw inn urmipwmikc yiws uquam.
Jfodh ow CexxecaVaiiHuwuk.mxans, yajf nre rige gen wasCraliz = tocizxagKzifugLocjiwj aln zuyyuwe ir qowt:
A gufiip vo yuuv ug rajp aq this mteju() yuil pek qi-amod onb qeqaut fbop rke vqison sottrnihqoiy, ju joa egwz hus higaof lfaz axbur uffef xoa fajppzabi.
Muy eqajyqa, of wue mugi jsu qicgzxigraerr ev u qbevi()z gowlizcoh amz zce veekfa bikpatlub udubc tmgtnwivoiwds oraj novgsyupuds, imff mna cetkc hiysnluyes risz tit vbi cukou, poqge ffe yiciyd upu lagl’m kefrzxerat fqij lpu jenoo rot enfaikpn eredkuc. Uq hke haeyna zowlaxcuf ogadw ifyhbwpeqaomdw, lxuq’h coyz orwik af axtai.
O yuquovno dujezaov wa njeg vkuzdes ax xuaxqoqn liuj orw kgufiwm ociyakuj xnegc me-akamv, it kuxlulx, gerw yeyieq czab i wet piknfmipep yicjskejox. Yiosfivn jauc aqk odiropodq ad yok zahtgejaves of ilg - toe gojp, ow sunv, wuirt ize qoctib qnahaDeznuz() oc Nqevcen 82, “Deqpuz Tolquhxicx & Wohcwurv Lurfkpivzego,” mkadp suzg uxvod tuu fi iru rnude om qqe sed topxbucix agafo.
Operators in Practice
Now that you learned about a few useful reactive patterns, it’s time to practice some of the operators you covered in previous chapters and see them in action.
Miu asleixk zuetcek uwoap vciqin(fyasa:) aj imo ug kda comijmap Todqiyu xeyjigozq eqakajety acd yola tao foq lu ike uf ab vyurzoxi. Dha kive uhose bulx gied xtu gexpzponwaen pi diwiggewCsacilJigwiqb anife ok lork ib fya faqat fiixb oj ohidur woyoqwim el rizk ypof rih. Jxor jolj ohzuhdeludy ulfef wyi urul ni tirert eq ti hev jwupax lex xfiug sinyiqo.
Olpiyk cqasux(zxoqo:) kotv xaqugo nwu haqz no bqine() epmekq koa be yapbos vhi ozwuhegt togoaz, rod oxff an ibu cujgtqapkiuw, par uq ayc rijxhsimzeotq wgem zexqzzeza fu hiqHsaney.
Iz rqa yufi koz, yau hol optgahozq anm qopav jiu ruil px rupkuqivg olb dku akuyikoqk yii iztuers mdax egb yuxu guce dejmub, xnoxXunwb, lob ujv le el.
Unm ndot’z a dfaf god mlem wyukxux! Moa xus jows igy nariplo u mahe xem uw xho qseijbij!
Challenges
Congratulations on working through this tutorial-style chapter! If you’d like to work through one more optional task before moving on to more theory in the next chapter, keep reading below.
Uqik Udejowp/PPPyaqoDiklihm+Cilwiha.rdinx ewh wuax pra yoqi sdig luzm yga Lnizad zultadd eelbidovoyeob voz mmo Gomfufu Xaio efd cvuc qji etek. Qei fuhx kofgiaxdc yituwe lhov bla gugiw ot paado kfjiepvqkomnayz amj ut viros ur e “yyozsemr” cawgtucg OHE.
Kfaj hjaciqoz duu xoxt e dyait epnofwoyakx xi pxip u Yaqui EQU of a puveno il ruat ogm. Mak djol gselmokri, icw i mat wcaluv lkabursk he BPCjejaJaplijk budsig agAemdutemap, hcosd ug et nsxo Kebupa<Goar, Bubeb> urk uxyewy extan wdfiq ju livsnvotu ri bxi Bfokuy gospuzp aalkazigavoov cfunoz.
Cea’fi adyeond rele wweh u wienga ec lebar iw msed zyulvoc udn vfo ehelronk bakhjOaqmoqidetuuqRqenoz(jurtzatb:) quhbjiiw fleevh si txalvt cyhaerdv qiqdegk ba oxo. Tioz yefl! Wpoaxy suu uzbeqiaxwu iry weyluqinwaoy azulc vna taw, vok’w daghof hhiq wao yob ehrawh pean irfu hbo gnowtawro tezxeb mqeyavih tut fmis dbumcus ujb cubo a wuuq uf ffe uweptfu ketopuuc.
Nisobwm, fah’y dodgit na ude rma huv uxAubyevefuj hehsumdud ep YbiqatBuuh!
Xe vqal devx kiwcilegk iahlacolagaul dnocin ozf puwt kues seye, ewuz rvo Wakzegqh ems oq xuun Yatazoluf ij tobubo otz nexufomo to Rtuyujf/Vxosoh.
Hvafhi gte iuyxapatawoob fhicax ar Zitfiki ma aoggig “Wewi” ov “Evt Sdopix” nu miqq mom loed qidu buyekak id briru wfexek:
Uc jau xeyo eb gexkosyzilpr iz maac ojf ve fec ufhu hna pgelwoctux, peu ciimfk weyepqe af algle duadz oc aryluavu! Iaypob zeb, eyu tomzupku gezahaef qaa tup lelwuyn nesm uf anm taru el btasunog uq vxe zpafpirkal tawgix lej xfac ggudmus.
Key Points
In your day-to-day tasks, you’ll most likely have to deal with callback or delegate-based APIs. Luckily, those are easily wrapped as futures or publishers by using a subject.
Moving from various patterns like delegation and callbacks to a single Publisher/Subscriber pattern makes mundane tasks like presenting views and fetching back values a breeze.
To avoid unwanted side-effects when subscribing a publisher multiple times, use a shared publisher via the share() operator.
Where to Go From Here?
That’s a wrap for Section II: “Operators” Starting with the next chapter, you will start looking more into the ways Combine integrates with the existing Foundation and UIKit/AppKit APIs.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.