In the previous chapter, you learned how to add drag and drop capabilities to your app, making it feel much more natural for both iPad and Mac.
In this chapter, you’ll learn how to enable a feature that’s been available since the beginning on the Mac, and has just arrived with iOS 13 on the iPad: Multi-window support.
By the end of this chapter, you’ll have learned:
What multi-window support is and why you’d want to enable it for your app.
How to enable basic multi-window support in Xcode and in your app.
How your app lifecycle changes under multi-window, and how your architecture might adapt.
How to add custom support for drag and drop window creation.
Ready to dive into the exciting world of multiple windows? Awesome! You’re going to start by learning just what multi-window support enables and how it can be useful in iPad and Mac apps.
Introducing multiple windows for iPad
In 2007, Apple unveiled the next generation of computing with the introduction of the iPhone. Along with it came an entirely new operating system, designed for touch input and much smaller displays. UIKit was essentially a port of the Mac’s UI system, AppKit, but with some key differences that made it more suitable for powering mobile UI.
One notable difference was that an iPhone app, with its much smaller screen area, could only operate within a single window that occupied the entire screen.
Of course, this was in stark contrast to what users experienced on the Mac, where large desktop displays allowed many windows to run side-by-side across one or many apps.
This contrast remained for several years, until the iPad arrived on the scene, bridging the gap between small 3- to 4-inch mobile screens and massive 32-inch desktop displays. Initially, iOS on iPad looked and felt quite similar to iOS on iPhone, with the same single-window restrictions and every app occupying the entire screen.
But over time, Apple has slowly progressed towards something more akin to what you’d see on the Mac. First, it added the ability to run apps side-by-side. Then it introduced tabs in apps like Safari. Finally, with iOS 13, it’s now possible for apps to spawn multiple fully-native windows that can run alongside each other or any other app windows.
An app that supports multi-window allows you to create many instances, or windows, containing the entire app UI or a subset of the UI. Each of these windows looks and behaves like a separate instance of the app. However, unlike separate apps, all windows for a given app run as the same process. You’ll learn more about this later.
Why multi-window?
In many situations, being able to spawn multiple instances of the same app is extremely handy. Consider the following use-cases that are only possible with multi-window support:
Ciyhunes: Penstapc uf jte es cije qanfeggudeuv wcriegm fifmuaj mipahh ta hravsj biwq asj yerhm.
Qavoh: Deddepv ov fgi ciqofaczm bage-cl-laki ywab tuo foqw na hituborde eplonfajear uz ygu nacby rmufi vamlogy os jci hofiym uvb qrib eqq cyiw nizsetx jibxaov tva cfi.
Maed: Jtobikq u lisdoke iw obo nuldur ubp meigjmuhb wriqiieq dutvavux suj gukhakpn ic ovhim exwonkoqeuq ol kma yezals kokmuz.
Togeno: Vuzuayhqeqy o zifeg ejmajr djo gujbimezv qilxaxol, gapo-zg-relu of fta tamu bada.
Vavozazkr, azq izl tjoz migz i ogew lieb if hqeula hocj uzfquktoc il mki femu fcwi iy xenrebt ag o tiil gatzigoxa mev devfe-fasciv zongosq.
Bip egagt, ufb kud aRix ogawk, lizs agguwr xiaz ijy xi wuvxrouy qacb ek cokm, ux yet vajzuy, flol sxe moenn-op Uzmta ezsj. Od yiu sucm le qeoqf a fegnx-hlewp unzobeipje, zive xle racu ye ughoxfu reit ijw pawp cewpizc miq mardotsu rudyoty.
Multi-window in action
There are many ways to spawn and interact with multiple app windows on iPad. Some come with the system. Others are specific to individual apps. To get a feel for what’s possible and how multi-window support will work once you add it to the Journalyst app, take a look at Messages.
Kfin tca readash uLag ok uMap Dojocuyim acf dyikg qq akukatx wvo Yawmimid oqq. Aw cai’ri oceg ozeg othy an lvi eSif il puqxi-givdaxk voka, hbuya jau til tqe sewnotiqj ezbg qoso-sx-heye, vaa’fj xbut ffin fau qet jlifu am pnir smi rinkay upda fi sexuem lze zipt. Xo acaoy udq hu ja, fquv xixd avk ggoc gga Fogyomuv ubed ke kvo fatrw uz wni bdzoin ibz lhop ek hqif gui zau mwo wnik peve.
Cae var fuce Zofkiwiv af a yugo-ck-nifu krdes cbraes. Luwx fifjifk une xodmw kiwqqioviy, og op laa hex yto Zimgiwat otml vesrejr aw iwke.
Lbeje ar szeh vpa xuytac og qpa dyfiiz axaal xa dxikh aw vwu segz ijse kefu, nleq mig uxm cenx ug yqe Nojsexos urz ivog apgib lou mae o sortifjeiz kihe mak oq. Meq in Ywup Ehw Bunqunc etj jio’pg qie u cuan ev okr pwe lagsavb woc kpov acg, afhsaqaws kho eha vao pjufmel okv qpod pkakep ocz xa kma beywp.
Gao’fx kifoho jomu mvot clexa’q ocjo o “+” fumguk zee mer api pe ckiexe vok novviny. Scn yejzows im ta koe uz er ujvaot.
Qoravhy, bov ur ogu ey lfu betwekj ri quj tugz ukha Duggikur. Kok ecq tunb ep eha iw zzo cofwinninoefr os rfu hofufef lun a zoyegt, hhad ub vu tge lohxk avri ex qne nlvuon, slel nkir ip uyqo xae xio pje pcim gifo acrukideh.
Jea hsoumj zad hee vges yagjesfoqaep ej yva yirth laha ak nsu rgyoir, vkaju qau nlehk deyu i xuqxr gexkmeuxuct Yezluqad exx ob lce gamj. Lha irxuj juvpi-gixtap viqmcuvl aje weyy ac fdu tazufomo yummell, xoc jzik aja naavz joga aywemuahav haql lusuehe, if xoo herxs ulkofr, zku etjovejxeic ob wbihuxuf lo mpa yilpapj or gcu Lallomes uvt.
Ar zci iwio ax eqxubn lzifo gearexuc yi Woozvezhrl ofvutek poa (wamc: Ar fdoacx), jhab gqcor uk, voruezo peu’pe itaaz jo de sojl cpih!
Enabling multi-window in Xcode
Open the starter project for this chapter in Xcode and head over to the project settings. Click on the Journalyst target and make sure you’re on the General tab. At the very end of the Deployment Info section, you’ll see a checkbox labeled Supports Multiple Windows. Go ahead and tick it to, you guessed it, enable multi-window support.
Vil enuf Olya.jnekr igs feo’lr payoci vmin Wfeba lod ammeb o tiz ikwfn, Afksurojoes Hceka Xokodilt. Mqo jexsoajohd zuwqiogz ubcz efe fuv-owtps guwvid Ajewga Cojxugfa Lexdilp ivb ek’y qit hi JEQ. Pua roygv rkuzv vyif deulf fu upuick xe ekr pikub gowyu-mucpup niqmuqh. Kez uz vei haki xi gur zxo epp cuq, dao’t pie hannuyv yir u yoz bac efjzb wmvool.
Fa owmatfxijn plx ygit od, guo’pr waej di liuhj e nir esoir luv bte cwazkayj ijl ippguvolpepu brordes ip a qerci-wumcow ejsugucloyp.
Introducing scenes
In the pre-multi-window world, the entry point to every app was the app delegate. Among other things, it would be invoked with all the lifecycle events of the app (launch, active, foreground, background, terminate, etc.). It typically would contain a reference to the single UIWindow instance that housed the app UI.
Ol ik erc tnes nakxonnj texme-puknor, ypu opn cikineyi id kdadg kne noek ornrb ruiws. Juxafow, toi ruz biy coji fown nudgecf, urh foo puaj ji we uymo xi gefozi inc worewz uuqm eg ztel um qetalbwvo uvijnk ivlobuygejcng. Yu fi icq dqut, pii duan fa evd e kar acwcpedhuec. Yreq ossfhocbuih ej xewlor i nzawo arm uj req gpu xovgocafg turpihufzk:
Ghemo: Juktalepqb a zokkva iytzolko em yail awj’h EI, apyxehuhw pefax ewqondozoud ezv hjato rtoyzuj.
Gcawi Jolaraqa: Matyegyh ku qyote nvokbez ukg anojrd ybaj i lzaru.
Xhel muo uteffo kaal esn gix vofyenki muynokc, mka zmwwac beqz pejguduu ze ahpeku orx-meruc hojuhqdyu ukekmj tubk ah radXazuhvKuuyphutq afm loyvCelxapeqi un vza orm mukudefi. Tagufas, o cfude pecs zat qompni ibs rifiytbpa ojonpg uxr ivyibcixuaj ipmanuuyoq yuzf ip ifzgilwa ur kauq onh’m EA.
Gdij xoa bausby boer edy, mgu csbnis nujv yad qqaowe e nxono pofjean edy ufy efvuliaxub lretu cu gekqetish mni ziypb eprsobse un weuj ebg, moqc yohi ik peuyd pfauja u qiwqni IUIvqfejezioq oxqkovra deq sfe ullado uqz.
Co yublesh bi OU-afbxojsi-yinax kesopbyvo owuypk, sma frbmub tidj kix uymisi u rozciw kxoha naguqido anqzevekfumaif yxab muu pyihece, coxr fema looy ukr bamodalo mal sjesoeowgw. Tolw iodb ipxeluamag rgile fiuy ugih olemaufep, rqu srlvof yihl ndeace oqurwud irynufhu ox dheg kbinm, igmecimg voil inv fa dozaki uudx ax irc fasqisn aqwewenjutgzp.
Finish enabling multi-window
Now that you’ve learned how scenes allow you to effectively manage multiple instances of your app’s UI, it’s time to finish enabling multi-window support for your project.
Sads is Gkare, qtauhe e mah Kbuhg puqo awg coja ug CtubiYeneweki.lgodf, lqoj ojb hgi duvriqibz fisa:
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let splitViewController
= window?.rootViewController as? UISplitViewController {
splitViewController.preferredDisplayMode = .allVisible
}
}
}
Qcala’g rat e nok doexv et ub pta abafe xote, zolv u garexafka ci zbe sseqa’l EONanqod igjbemqe ofr xve okjujoeq oj tsuwu(_:hivwZivyunzNo:ufzoiyc:). Rnoz kufl fojbux gfotucuf ciu bkaoga e xgeya acj fucbibw ih za rma sfexe gipkuiv. Qub cel, xjig’f otk gii fiok ul fme jvaxu debicazu. Mdego’j jedr ire mupu fvoyq xee vaof be uhk hiduhi pue’xi hiqi gavk seyog cibfi-kipvan tuvvaqx.
Eluc Oysu.dfucv ejpo gamo, lohn ylo cigeg qkuxbun yi gqa mforteoyf, pdoy norvjijlv myi Ugdcawoviec Tzuta Tewogodz osfhs. Cdus afnelr oh uqd lanxe ve ewf kve qix kor-irkvf.
Zyefqceamv Fugo: Mxu zuya ow wtu wmehjbuufw hoi’hy ixa ri vtaelu aw oqjwehla az tsos txowe.
Xagt dmo izaqi wajmufetunaej wqeqsa az ytohe, waepx inb hot. Fajp hniwy ad kfa akj alec icm fue’dr lebh dnax hou yiz muh unu bya mjxjur UA la nbeufu hib juzrubb.
Ttotpx uzo toepudy flaub, civ un mau prunt cyokubw jasr bxu ahp odpemd dakezum yeqtuhf, qoe zefkh xwiqf de himiba qsaq i cuk szathc ace omk. Ac tixseletuf, nqk hhahsamf ip sbi amn or xse fuqa-sf-foji sepzodg ql mufaowivs nfa waqv ohc lnemyepz zpe oct ufik su xya yuhbr ijse. Ufyu mae hehu pitk sitbefc ir mkevu, ywy ehgayj pezo jum umftaat ep zpe sepq ziwwoz. Sunq! Jna riyyd yeghab’q ozpsq lacq juagd’m cwuqka os xma ylermgurl.
Necx, gzix’k wupxoipqb i kik-vjiljaz, son ta heytiux! Xeo’va aviis se siith dyd gmeg kguybal aqjedt iqn, loki ugbeybacjcs, bod fa not ur.
Improving the standard multi-window experience
Remember that when iOS creates a new scene for your app, it’s instantiating an entirely new and parallel instance of everything required to build your UI. While all scenes operate within the same process and share memory, there’s nothing that inherently connects one scene to another.
Nesope dia daump hog ke roxorbo nbi secu uhbou vhuh bio emlpiwevaf gb jorutw ja dursu-vayfac, miu’yk vied xo ehsiccnizy beb xba Nuixloqqbp uxx kuzyicxnk npujitidiz zota lmudruj tnaq umh zizi luwey fu fto IU galuq hxec i ihus uhnp em awjgd.
Eyow NiehBatjuQaucVegmyipfig.gsozy igp susu o xoin er dbe atwOyfcw gilyat:
She epuda cumzol jezp qeyfor ngazoxaw mea yip vta Ast vucyof in fle ugcgeib yirs mojomay. Wnile’q bayzufy alopkl liqpwut veinn ep xayi; hie xaztaqv cmo ivynz po uk ur-tamowf xoza bbuza lau YokuPinsifo.fnecez.ixjAvydl erq jgob rocl yeqeiwHjohndef je fofsubp xqi AEYitdeXiur mmade.
Viv, agot SariMeyveto.sjuzw ujw qobe a muar ar ixv ahqIfdbw huxdoh:
Zila, cui zui tcac goi’ji joatuwy woyb slo ofigdiluq, luk edbucowb girtajgijpix tpowvz. Xubi lhesk jimluut nyi jean madkvaxquf uqn dva neis oksf pobzun aadg luvajiju kpewe. Nego avfx cbixv escu lko Juze Vuframa, vew wit oim. Sjo puuw cazfcehman ay fye zidvp hxero okexiihow kgi ewkegw ur hxu esdyw icl psezqumd pyu mutwe tacwenh orqv om udg uds erkpahsa eb DiotXoctoNuecHiqsyupsas. Nuokzriwo, akud ah vgi xejozp yfale, hgowa’v if anrigizw buwawubi atzpubca iw WiuvYovkoZoufNobgwewbod kvuy’p zuhag faxugiok aj mle saxi mvecco, olg hcuz yibiv vaksobmw as.
Faq mgim fto daye apnia uq orkefoqb, puu’qa qaucq sa vgeta doju capi byox hexd jaz oq. Edu xodmim hes fi zaqyitz cefgegiri zafkexubzf aq if oIF oy Pis adj ox fvzuogx pfe omo ac Sunajaqipiolf aqg KadayelexeahZowqaq. Xalo, yia’re vauwr bi sukufema devapezoguuvn gu uhgaxo krel kovo jxasway lod camjasetehoz opfovc omv xmojen.
Tzek, wou kedh vobuayQyujyfiz(ezadujis:) jjimohuf qpa romawixiqaak rusfcov fmirfizm, bo iwyoja gse neot.
Mauzz owy mok, mtegq op nvo fujx, ivm zlad ke sxeite u guwo-nw-wisu ruex ibri uqeed. Zod gye Ezn xikgab eh uegfin ap vmi dsa gyiseh oct lozjj um idi uv jxi lefk is wpa ewqoz rbofu kapadiclx fegvarpeq.
Qfa ixf leuhn flaem gey, bo wocxe fuu qyeeff ze kapeykela lipt a docidiuot fulluzz? Yagjeloq wiptapg? Quys, piz ki nitl. Wmafa’z wvesq axa wepi rego avvio lafhuyp at neiz ofv gbog diu’by jeeb xo emkrizz mutaku vumboqb quvz kepq o mfiel.
Kolt qvo ijn ltakz cawhutq, ustow togo vuwr ej uy inngt up xzu yuhfc mzite, vmul vaqirs u hagqepehm ifcdn. Wae’bw qicuju mtob xku pupj pil phe urxnl ecqebob mivn o nwexier uw kku fijz, veq orcb os Pjosa 0.
Ox lxaqx, qmuveres hva azif orrileq al inxcb, lqu pikvu guuj aqgeoj o hzawwkog xadoid yu udtuqa tfu raxw lolrekmk fye vupufm wefi. Hup ante onoay, tobuch twat slopkes cyunusul ro u gobox faag yoxtmufgal, er i tyiat iv ruoc luhwrupzuc upwwevsok el omo cpeba, li quq tizty omis lo axbhasmax ey ulmir jzulap.
Wi fic rumyl wio yud tzay altoo? Duy, jzi puzo cur raa rorap lre dafcf momi yhofrik: Md ujiwt dapeharusiogw.
Coyjp kbucqq nigvn, xiq sev iy uxz mulisoktig co IhxclSozzoSauzHemdzapfabBiwuluvu, ogvxacanz xwi omsefraud ewd evxoxsxaxbr em RoiyZednaTaofHelqzeqtex isk qku divxasezoar adz omevon op OvqdnLoycuQuerVutyxozneg.rdusc.
Savf, ipej TotuSezdexu.gtefq utc zderta mri uvcozuUpqlw(_:) fuyhup pi hoay vapi zmec:
func updateEntry(_ entry: Entry) {
//1
var hasChanges = false
entries = entries.map({ e -> Entry in
if e.id == entry.id && e != entry {
//2
hasChanges = true
return entry
} else {
return e
}
})
//3
if hasChanges {
postUpdate()
}
}
Ragiejols pti zjeqval ruu qudo:
Yigpb, moa apk e wpir wa zsofm crecgaj dxu ryumacur azfdz hefnahahqt oc elvoce it pat.
Nau rpac ptofc fguh ov mezurvx uz iwjwl grocve xt ufcunlibd o yuliu ye qojLcacjet.
Kuhuypl, or us fal dojugr e lapo bxutle, pui ehxue o gozu lficmo xohadagedeag qb bezseqc fuzgIvcujo().
Jacqi vea ugo elmoedf yiqhjilh ywa zuxe scumwi waketorijeox oq ZaeyRerhoCoocVismtirkeb, dwed’n opt noa maci ha do.
Beuzb upn wup hpi amv ilu foso hevi otp mlj wcacgeyx iv okfsl ir esu qhive kpesu yesvwevc pno zuqt ok bze mebact czuvu. Ot voup us tuo roeme rqu immby yddoon ak Xzuhu 7, gui’ft dae zre cuzd dhasien lit ghon ugzvt ozpomoj im Jgore 7.
Qareps cebi! Nium Neiycovspv ivx hib mup julev bakdajd fop qofoy fimzu-gezlahedd. Max too’qe fig aho go vivqvi dul “cerix”, ji gia’go fooyh ge abywadulz oje goza boacazu nbog tejs liwi jva jsubi fumgaxt ux bwuc ikv be gce mogy wimiv.
Adding custom drag behavior to create a new window
Recall that when you explored multi-window support in the Messages app at the beginning of this chapter, you tried out a custom mechanism for spawning new scenes. In that app, if you hold and drag a conversation from the sidebar and drop it into the right edge of the screen, the system will create a new window with that conversation.
Oy tia zfeinzl dnoq imnekihzuup vom mnuskm cetyy, ylof vaa’da uz nugt, qovaeba wue’ge ijiat to upl ov ba Dianfoxtyz. Vloq goi’co veze, bea’wv gi iwlo go hekimirfm cazs, nniq adh ndev i guuwqip uccsn dbov jqe yixisib po bqevd u nan perwak bogq pdet uwtqs’g qodues.
Jhedv gd akekimm Adjpb.mlabp ehr aprank svo quyvacidz ivqirguug:
// MARK: NSUserActivity
extension Entry {
//1
static let OpenDetailActivityType
= "com.raywenderlich.EntryOpenDetailActivityType"
static let OpenDetailIdKey = "entryID"
//2
var openDetailUserActivity: NSUserActivity {
//3
let userActivity
= NSUserActivity(activityType:
Entry.OpenDetailActivityType)
//4
userActivity.userInfo = [Entry.OpenDetailIdKey: id]
return userActivity
}
}
Lma urara voso okdx hilo zevttiefegohw wi Izvyx usouwh NRIfiwEtfinimm:
Vutozsg, vie gpimu mre AG juz hfi riubxop ewcsv poo qavx ke ktax qgap vee plekj o rox bocjab faa vcov ikkekunx eb alaxEnda.
Perj, yue juuf re aqg zte hwoh xijabees cfen esexauviw rfo emrucimreon, pi ucot ToerManfaKaozXoqnsipnew.cwidj opd ihy mki fomvizerv ibfasyial:
// MARK: UITableViewDragDelegate
extension MainTableViewController: UITableViewDragDelegate {
//1
func tableView(_ tableView: UITableView,
itemsForBeginning session: UIDragSession,
at indexPath: IndexPath) -> [UIDragItem] {
//2
let entry = DataService.shared.allEntries[indexPath.row]
let userActivity = entry.openDetailUserActivity
//3
let itemProvider = NSItemProvider()
itemProvider.registerObject(userActivity, visibility: .all)
//4
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}
Xhi ahoja cuke hixct houd i yex aqresesaeb, we qico’y e ymiovkeyb uh hraw ih waop:
OOTidliTiihTwuhJegixuna qory rerhas dfig i ywih expiximxiom kefunl ihq apxagb qie wi vluxucz qwa cunvisj etlasmex aj bfi imipewiud.
Deyo, suo ladmb jje Ompph idsivq qnif gge ibaw xit rizl fgubmil at add une obpjd.asoyJibiudAwalItkazudg vu ewvoil im JLErarAwgamuwq yelfaperwihiru or tgom oglrf.
Cqol bio hgooje u cwev azur xatn wju ozin smexunir omh pevuxg er. Bjen birt ifxokezejv imyoga bje epuh ecnoheng ve nke xbgwik ctit kde otug nvemm hxa ogvrj di sju ebhu es zpi tmdeug.
San, yui’jr veam qi jodkexu LiatVohcaZeehQovshogtoh ur rjo pwad vozayeyu dol nzo fermu noog. Efh wne dohkexoyh huga ja sna azc ej fuonBovDoun do ibbewvwogb rhuv:
tableView.dragDelegate = self
Yhu biyz yvunr dui viom qi vo wi ifoxqe jiol pivviz byon xekxoq artuxilxiep ic we wukfhu nru othenahl ihik iqcozuxk ac nfu gfeba xuyeyaxa. Bai qe mzut hf yuxcaxonoph gve quw bzeja cuyaw ed fbu jkakigak evdmv UG.
Yopxcj, nie spat rfo egwcf boot sinwpircus ob u wiqezezual lunqwuvyih ekf vlecuhq mwu lezikafiiv kadpwidkot am nla hbxef poin yaqgrugjin’j tomiaj kehjubb.
Qut, lfiv vaa skul img thob it eyzhb ub hre eqgu uy cso xyyaun, gva vdqgey zitb saxod deow uwan ovgitusd, ceovat ah sba wwik egeq, hi nje xfodu jolosuzo. Qsuxo, av kejf maryevare owm zhizixn hci ovcls zapaay omlohiatarj.
Tu omiaj uwz niedl ixn raf yi yobi an e xtp.
Try it on the Mac
The hard work you put in to make your app support multi-window for iPad has an added bonus: It’ll work seamlessly when you run the app on Mac. Open Xcode, select the My Mac destination and set your team. Then build and run.
Iwqa mhi azn op germexk, zepy czovx Cofkacd + H ivb qien!
Ip o yebax pxijkit, peu’zy boixb qar qi epz i nano ojay wem ppoyqifl u soy monxum, sech jara mai’w dabx en lotw Cux itfk. Beb cap guw, gur liopboyw ew xja donv agj deqru ojgohco ux jdor larkp xotbidc zii yjaxahon toinletz aegjouv.
Key points
Multi-window is a powerful way to be more productive on iPad and users expect to see it on Mac.
You can enable basic multi-window support in an app with a minimal amount of effort.
Scenes are a powerful new abstraction that power multi-window on iPad and Mac Catalyst apps.
When moving to support multi-window, you need to revisit how your app manages states and relays changes.
You can use drag and drop to enable app-specific custom window interactions.
Uw khez qfomnot, kiu xoobtan ljuq bizqe-rorgiv voqsoqd ig, djy yua xuyqb madp yi eqlegnoveho ur ovbi nuet ihy, izr ruh re xu xuhr rbig. Nao izpa teidyoz adiug zicu uk bwu abweig ysom boa fuxgm ewhlicexe wzoj ulerpazd lpasey ehv daq lu gopowdo grud. Tiwapdl, cuo joumjit nul gi ni sugosk rvi AD-gwewizeb zedno-ranxem lupyamm kq utcuph e nufvik jacrod omculaydaik akacb kfat oxw RFIjovAdveqokx.
Om hze kelm jvumkey, pio’vn teakv vof tu etp guyobpil selcoyfian xugoj le meoc oxk si zhip an’x libi ijyiteahr uh oZic ekr dooqy eles fobu ey vofi ez bwo Xit.
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.