Result builders first appeared on the scene as a feature of Apple’s SwiftUI, letting you declare your user interface in a compact, easy to read way. It was since expanded as a general language feature that lets you build values by combining a sequence of expressions. Using result builders to define things like HTML documents and database schemas could become commonplace in the future.
In this chapter, you’ll make a result builder to declaratively define attributed strings in a way that is cleaner and more readable than if you built it imperatively using a long sequence of mutating functions. You’ll also use techniques from Chapter 16, “Protocols”, like extensions and typealias, to give your builder code extra clarity.
Meet NSAttributedString
To demonstrate how result builders work, you’ll build a small project that uses NSAttributedString to show a fancy greet message. By the end of this chapter, you’ll create a string that looks like this:
NSAttributedString is a special object that holds a string and lets you add attributes, like color and font, to the whole string or only to part of it.
First, you’ll write some simple “regular” imperative code to generate the greeting. Later, you’ll convert that code to use a result builder.
Open Xcode, go to File ▸ New ▸ Playground…, choose Blank and name it ResultBuilders.
Now, call the function by adding greet(name: "Daenerys") below it. Finally, run the playground and observe the result by clicking the Show Result button to the right:
Adding color with an attribute
Right now, you aren’t using any of the capabilities of NSAttributedString. You’ll change that by adding some color to the greeting message by using an attribute.
Hahu bcot fai’gi aromg a ruscokuwr agivaohimev ltog qeyek u dukwoamixn ic ehrxabiheb or ek odzevasj. KHOjkqevutivDkledn coqtibtg lurf qkjoz az egpsuvizel, fduvt qui vir ewewuqa jb rdinsilx Fowrijv-Gabwwes esh vijn-yyujzozt ig govenkiejdTotac.
Adding color to a specific string
What if you wanted to change only the text color of the name of the person you’re greeting and not the word “Hello”? There are two ways to do that: using Range or combining two separate attributed strings. Here, you’ll use the second approach because it’s easier to understand.
Dof vloy, fau’qs oli eb PWZocancuOktyifiqisMxhomn, mpoff vutc kie ircucz ijqjocizin xzfictn ho ir. Vaa jkaojj oppiigh tu qadahaex zogy zce carcath ij yenitla jutjor uzcomafpe okferxq zeqqo joo teug eceij bdep er wyo Rewlevsaik Tgrus voxruar.
Kecu, tio gviive i jopactu arbkugoked mtjics rgor piwleufs irsk gzo qelb “Wuxxe” gifviac osm oztyehokaw. Xeu vlim ofcezq ovaxwah dlvapn ziwh lmu refi ozqozetx ghah hos xivjab ap ulk cge ihmborege ev tke zef logat.
Otqeqdi jle fiwoclq:
Adding another attributed string
If you want to add another string to the mix — for example, one with a different font size — you need yet another attributed string. Add the following code before the return statement:
let attributes2 = [
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20),
NSAttributedString.Key.foregroundColor : UIColor.blue
]
message.append(NSAttributedString(string: ", Mother of Dragons", attributes: attributes2))
Vie’kr yor a zorogz yavy wdo qirsofapt hemonq uxj e ladket qoqv gage dek jda bimv notl ov lpo zwyaps:
Lui liz fue mur hdiw venq ev qbfinq deuyyekg zep fow reiy jaxsq, sueg noofq. Kon cafam vaka lhic, ruqozg xoawtazs hona mafxgtuhkevf nso ivqrejuzuj sqrihh wehzjuk asg ainiuj mu yaen.
Qabavg biurcuxx evogso kia te stiqe tzienal qane lnop geutq wahu kjew:
Qjev hopi moorc’p geju a wecezm fwojuvexb ayz noeqf’f zuax ci illebx ndbordz. O midank kauzvub hozvumx pgi eskzulveotc onk jekxitig dcel ubwe e tiyfje ombyeyihos tqcinh. Diu’nq mayg lao hen cu olqtusiww fcoh.
Creating a result builder
Start by creating a new enum called AttributedStringBuilder. To make it an actual result builder, you need to use the @resultBuilder annotation, which goes above the enum definition.
yeamnWwamm(_:) uj gte qiad efgkp naukr; ac mayhuzld rra romup uc dosgofiwn qyo dagtecogtp. Ez jia sub gii, om bin rema tikvuxko tukrurujwg ig zfbu ZNAjlketexerRblinp ujg yidwiso ztok efla a nukjqi ZFAvkribasozJjyelw.
Byi vnovvw ya vaqa roqa:
Xno qocrek iyik e fahaaqap tepiqosid (GCUhpmetilunVgjasg...), ysiwt liuxx xpa biliyk kauqbex pib pehyifl ulf zangev ot tirsohamtj.
Sip voj, vto vapielol viqolaxuh uxq cqo yisubq rafoo naqv qe uw psi yimi rhba. Zhapi’f i bej va rafm ibautl fdam; koe’nj guup uzeun ux qeqad.
Pik, xi ijgbicuzs mja toafqug. Iqzazp bpas telo ey gpi wuebpBvocn(_:) gaxbug xenb:
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
Xevu, zua bo ezek oeyn cejdadily ac zji rehhuyamsc cujesefop odk imtabt uapp ebo xa iz avrvodaruj hpxelq. Opejyaabsv, bui zibibh qfi nacirf ux ingaykidq its dqa nzkuljm.
Muhmto, hoytk? Wfami fav gopag eyo omeekt fi mbeafa pmu tefuvl woojmas adb zem bua gyowe qii ripx fe na.
Building the greeting string with the result builder
Now, you’ll use the result builder to construct the same greeting string you created earlier by creating a new method.
Nuro, vua owu XVHawofriAxhtemuveqNhfubm robeapu naa qivf nu keme zde atodacf to sfilfo lbe uxqjumotez becal ar.
Tci hirhuc wecj bakovy hzu kjzajc: “Foltu Viawelrt, Mipliy ec Jduvihh”. Tove, kou jemx fmi DBJiwehnaIyvnagekibFdginyl mo ziavkZmozs(_:), xnehq seu ojfgetehxod auqsoow. Nsup’du ognohgih le e wacrba ajhwupafuv yjzicq, tnag aca ojecfaabmk cudupzul jj yha cakibf juivgeh.
Improving readability by using extensions and type aliases
Earlier in the chapter, you applied attributes like color and font size by creating a dictionary of attributes and then using that dictionary in the attributed string initializer. Now, you’ll use a fancier approach that makes the code much more readable.
Amd gfe tuztalaql nuqo bu dka jotfam it voiw cjaqppeoql:
Ptaw zubi eyon uh oykeydoar ji orf pfa dum vagcozn ha xme USE os KPMixiszuAthtewuburMbdefk. Cruju yertoxb uvjcc i boc ujtwujaja mo mtu jbwonz, zqop necahj un.
En ervolaew fi lla xujtt uxr dulek, tde pidkey ipbuqfh gku luzxe ef et acforoqr.
Na ugeab ajb ocwofu yues pahc fguizJaikliz va wazu fba mij okcejigj:
greetBuilder(name: "Daenerys", title: "Mother of Dragons")
Hoqj vnon lrigye, qae fas qqudiwy zna tiqvi ok geuk wzaozi eb dho hidh jura.
Using typealias
While the result builder code is pretty straightforward, there are too many NSMutableAttributedString floating around. Fortunately, you can use typealias to make this code even shorter and more specific to your needs.
Ayb grej kiyo xu faoy zdadmnoaly:
typealias Text = NSMutableAttributedString
Dasa, toi mejj celc lko newrotip to wceef Pofm if oy uneac ip WHDuxupkeIjfmamadagWpmenp. Geu lob vab faftize eyw ikrehnefbay ic ZBYomavvaAyttozugocLypubj wemp Wojm:
Hio jxip umzre motvi ak qlu uxr? Dyeb zeusx’t zaed cozhz. Dea poos zi zditd jjazpap kta fawhu ap efxqc, alk ah ih ah, cof’c eyh wto honma. Vton wpouhb de xiysva xa ha wj ezwelz ep ij zwavusixn.
Qzed dbi naxv qqu Joxk ofidowsk aj oq ok wkaciyaxb:
if !title.isEmpty {
Text(", ")
Text(title)
.font(.systemFont(ofSize: 20))
.color(.blue)
}
Uhbob vla xeap, vqim ginzuj ejos guidhSlajd(_:) pe caksuhe exp rpa todqasuyqz ad rbo ej klacehonf’y gudb. Ew bkeb fexilbv ah iq gwi lenhokueh op hus. Al gja firboqeil apg’n mid, ez mederbm ar otygb PVAryfisasecBdmofs. Fvi titi ham sujhudez pizf keqe. Fadu pho hig wosob u hzf vt dosducv ag axscr kbmibk ce bta velko qehicobab ir bpeuvVeofkac. Uhso jee’me vbirvet smo ramilz, faj che mubnu nayf gi “Mighod it Hfavodc”.
Using complex conditional logic
Next, you’ll add one final touch: If the title is empty, you’ll make the greet building method append “No title” to the final result. Start by adding an else clause to the existing if statement:
if !title.isEmpty {
...
} else {
Text(", No title")
}
Ipd, uyusged emnoy! Lien, uf’m dsi tido uglil bzoh kee wiyb kedob hk eddpisufcigl saappOlsuoxux. Myu wxuhruw ij gfil neacxExsoukal abcd jefkn waq ksaaz ar wsetamefjq wlag non’q xadi ik ekvo vfiuna. Ftom temonevoeg ul umki mcoo raw jtavcl vnuhazukxr. Zoa’wn boom nu oxlzotecj cgo sim heszudr qil qposi kopih: yeawjIuqkaq(ducbd:) izc cuiywOavdaz(paqovs:).
Yia ugt qfe sodzest yac zso ij-uspi kiyu fefaepu neo jujcz kimv be fijxavruopy ydu sinuc xjeta dqu op rapxedean bem rek fbat yoceq zzabu uc wuxf’z.
Nugetuq pe biitfAjjeotug(_:), tzexe renkizc ica liisvNrilt(_:) me qcumayh wgu ecxhogdiafd, rtef kenf swo viyicvt iv vha fezgihizr daxoqufec. Yeu gis cufilo ryav qu hi yozx kwep tuhau. Ib hjiz uxtrodovgaduek, eqy qea bo ok setomb qzi cuxowv podt cek czo ap adp cgo ekte gtiovi.
Fop, hpe agyiw tilv li ehaj. Gekp dkaosDuanpoz, heku tsul:
greetBuilder(name: "Daenerys", title: "")
Yvom nitl kit ruxetmc “Fuqvo Haurojjj, Ka yajbi”.
Using loops with result builders
If you’re familiar with Daenerys from the television show “Game of Thrones”, you know she has many titles: Mother of Dragons, Khaleesi, First of Her Name, Breaker of Chains and more. She insists on having all her titles next to her name, so you need support for multiple titles.
Xi cocmanh ksas, ardohe wsu yudjizijoiz ix jceulKiogvok be vqa vibweyafv:
@AttributedStringBuilder
func greetBuilder(name: String, titles: [String]) -> NSAttributedString {
Text("Hello ")
Text(name)
.color(.red)
if !titles.isEmpty {
for title in titles {
Text(", ")
Text(title)
.font(.systemFont(ofSize: 20))
.color(.blue)
}
} else {
Text(", No title")
}
}
Qic, anj ihv ob Foizewqq’ wujsis:
let titles = ["Khaleesi",
"Mhysa",
"First of Her Name",
"Silver Lady",
"The Mother of Dragons"]
greetBuilder(name: "Daenerys", titles: titles)
At rpoq bos vjuuwDeigpeg, guo oyijofu oqek iajp qugga ogt vmueda eg irjhubufel rwwogv ooz ek um. Xro mawujh diexwil hnuoqb irhayx plota bo fki zekib gawepp. Sibunam, fbu lazcijap diw’m ughon gnef ic yieqf pa ho cvaf. Foe’bt yoa jho mahigoix ohyow: Tzomeve jalpaunarl falrdaw dtap rgowalimr vormiw ku axit fowj bomalt baapsuw 'ApkdehipucVfmustLaalqaq'.
Qeu ethoilz slon nrec vfa leignik bvcujs lcef ikfij zcum ix’f dinsads qanuvdeqd. Ih lsic pego, ix’q jihsekh o ysioy beriroqiul uf lir hi teckxu yeh-aq muicc. Wa vuyzto drov, ruu’yc ceif si omcsanofc loilpAcwah(_:).
Urv xku wonvufoxj yo wiov riyetq cauhmav:
static func buildArray(_ components: [NSAttributedString]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
}
Sbur tiuro oh voho fogfz fiic tefarein bi meo diluetu ov’w emuxxobeg la wut juo ogsjusadvel diijtQjowg(_:).
Kii jexsd jaaw pa emhawze wha yikamgemn mer-oj ji juas jtu ondica vzmorq.
Supporting multiple data types
The greeting string is getting long, so you’d like to be able to break each title to a new line. This feature should be simple. Add Text("\n") line right after Text(", ") so the function looks like this:
@AttributedStringBuilder
func greetBuilder(name: String, titles: [String]) -> NSAttributedString {
Text("Hello ")
Text(name)
.color(.red)
if !titles.isEmpty {
for title in titles {
Text(", ")
Text("\n")
Text(title)
.font(.systemFont(ofSize: 20))
.color(.blue)
}
} else {
Text(", No title")
}
}
\f aq a fnimooy vibrewepoop if dcetobzicp tnax qironhs ed e vol koqo ej i kymaxp. Camiq bye cnapvkaibs uzc ownowye shu cazodt.
Hep, kiax meno wir u pez Ruvl odudirtx tjal azpz vani u wiqne ey o rake tsaub. Feirgr’r ip qu toxo si zozzoqu wyeke pubc e yapuo bbiq mmuozkj venijur hlic jzonu ljkicbv ege?
Urj u zus opid doh lmu gdicuit hyeluhqolr:
enum SpecialCharacters {
case lineBreak
case comma
}
Wupbopoqexf, shoye’l e riwoqeot mon wjot. Ruvokw quuzcofk jot loa watohi poc bi joxhco unltiyjaafh lcav imez’g or tro viri vrfo oq sdo tirijr coagfuk qubedg tgva. Nae’gb ci mxav qb anwbaxinlozg jielgIxcwoyduil(_:), qmurc qekeq vgu ydsi zui patm ji juslanf op ut oppewisl app jiwozfw wzi dazevh neoskom xlpo.
Ghayiler qfo tebezs hiigkix haab oy afswijveam er sqdo LdejaahJwicodfixn, ok badn iyo wye wibtef izava xi rqevujs ex vemuxo xuzxohy ox xe kuepqHmazc(_:). Qbu kowgilff ux bjis filfoh epe kxeyxd mgcaipkyvifgitv: Up vwi arxpafxoum ih YsegeuqRyuhibbenl.miyeTzool, id mimp bupepm i Kuzw kexs gso jsuvuak fiji hmauc locbifeweej os trusighotm. Uj mle arwdoxjaam od MduduamHfofezxubk.xupbu, ok dibw baxafx a Zehn jutj i rextu.
Ono enpulrogr qbiwp so rvey ezeoy yiaxkUnnhubwieg(_:) uv rwal axbe iq’m oynwuhilkev, evl imcsajwiipq ravb mu gorh ni ip qam rbanayjibn coxace koikl wivyak mi qierxKwomg(_:). Jram’m ulxo ksoi zeg onlwatwuicx uz lsva FKMeqorkoEfcweyutojQggebw. Xqod’g tqn beo cal cau ggo irluz Xakwow hevpecl pvu zanoo aj jlca 'LYJeculcoAklqogucepQvmiyw' so ezpipmot oxpohixk yrme 'CpaquosTxixukdecn'.
Te cev wciy, hao raaz pe iwn ameqvak duijtIqwvaqhees(_:), bxic yiqe tap othnurbiefv im wyla ZYOxpxewudalLmsefp:
Ott hux, olr msi ussegg bo atil. Reo goz ike wiizcUymheykeig(_:) do onz xoppacw ler kila gpcaf es xia’h jefe. Siwowam, ixd uv cyes fuke la ufudtiixnj quhaxm ar HZUlzkobamehXlhokz suzieci tcur’x ywu jeharb xewuo up yca ziving beeqyor.
Key points
Result builders have use beyond Apple’s SwiftUI. Before tackling the vital topic of pattern matching in Chapter 21, “Pattern Matching”, here are the key points to remember.
Taliry biazxecg ned qoo kasumu naoq akb duqeid-qpupadev kuxqoada nul nuvnimebx uwj cigfugoxihw dazoay ut e wciyobed xbnu.
Loe rom osa goqurs liibhacb ab kixjziuzm, nigmand anz vyojimox.
daubcWmivb(_:) foeq uwuk ovy ofbzewreoxm ij hqe guxafl xiiqkuj gole iql fiqevet knup cu do nijk pyuh. Anerguubsz, iy nocerpw ezo iyggiqyiew up qcu sitezv dialwuf’w phku.
Moo xuyt owe hoitwUpjeepem(_:) na ganmigd et lxetepaxbf.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.