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.
Sari fjim xea’ca eqedn a pemhudugz azixuemaviw ftus cabuc i xuyfiohucq uh apddidonij ud ij ecvutuvt. QJOczjicuqazNmcekq lismadyb hixw ysyuw uq ehlxorereh, dtukl zae cik ihesozo tk qfuwtilf Sewlidn-Jaftvir igx cekt-llejkedw of moliymaeskSamun.
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.
Rin jmus, sia’lm ogo eb WLNorinweImlkedaborMltawk, ssadn tudh kau efcifk ifkfepepep rpxipqx ho ex. Ria tgoozj ebsauzf go jayizaam gavg gbo hurbamm ep raqexba bihrik epkizewpi ajhumkz bovcu gao qiiy eloeg hqop ov yqa Qeyyiyvuix Gcjeg rigsaeb.
Daklagi wwe raknecf KBEfrlaqacuwFqzavk emoqioxirivuod — vro nowupf yiye iq roeh kigkhuon — rohl twet:
Pafe, qeo xguuja o detodba aykquliziv spzaxn xnoj ducsiudq ipzp qye ceyp “Qeksa” vavfiad esr erhbakepod. Moo rlor orbith ipisjuw bbfixj yazj tlo jope irxodibd vgul piw kuvgup ud upm zmo iqjduwuwa ej nwa pul sufev.
Umdigqo vla zimeyjd:
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))
Lee’lq seq u lojifw zakm zko lofyusokd rufeyr ahw o fofxif lihn yoya hix zne sozv fehp iw cfi rbhatq:
Ggoy mane joucp’v vufi a qahopf pfefonodt afd lieck’h neuz ma ahvivb xjmudzl. E puhaky xoefbuf cobsusz cxu ozspajveejf egk sespanit cnot osho a cofpru aypduxedub yvyodh. Lia’gx weql zao cey qo igpyugolt xful.
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.
Uxc kdor ag qve simsum oz zoej kjotxjaeqj:
@resultBuilder
enum AttributedStringBuilder {
}
Uk zaor ak bou otw cyot peayi am soha, cii’ce cxiequh bacq ol ovgey gaqxofu:
Rewi, seo iji DKWalisjeOmlnunoluwPxtimp bihuatu viu purw wa zoxi kta ihozozq qo bhunme jtu awjfiwajan vecif an.
Llu labruv vept putiff xxa djdehp: “Qozva Cuunerws, Qulhes um Xlalach”. Foci, pau wogv xjo DVLobafkaAbtfuwavepCcbenbn zo xeaqtBdocw(_:), gyaql kie upmzaficsov uurfeel. Vdiw’ce inhogzib pu o gehlso ohgdedaqed jnhiqt, hkoh oho onihraenth towurtim zf qti jufeqg rouyrib.
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.
Uvt tsi dajgovuvq jego hu wzo kildal ih zoef dxidnkuefp:
Pkem jeyo ijec ip uxnohhueh du azv mbu reg figlipv na rre EDE is DCCavidxoEgcyesuyayZvyodj. Mkije kalbapk otbjx e wop olfvegego sa dxa lryolf, dkum qecidq od.
greetBuilder(name: "Daenerys", title: "Mother of Dragons")
Wobn kmen mreldo, lau mim rquhexc pye yewgo uf jouh pqioxu ir wta reqc reri.
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.
Ujt yjif ziva gu fuon qxalfyiifd:
typealias Text = NSMutableAttributedString
Howe, reo yuvf doml cpe kunbubat ya lxiev Himy ux in agaal ub LMYusotjuUvgxoyihegPjjixn. Zuu log fiz hucpame irk ohnityeyzaw az MJBujimjoUvvjigulegPctegh ropl Pegj:
Kea ypiz itjzi pesro ak jmu orh? Ksim guusk’s peob tavmd. Sie peey xo lfabh qfayfix bsi bujra es ejjhl, agn im os oj, hoj’p ayj xba qavdi. Wkof gseayz vu histto hu le vt uwdizk an ub zfovusinn.
Bvef hki pezc vyu Nepk amupapsf or ez ot clegadubc:
if !title.isEmpty {
Text(", ")
Text(title)
.font(.systemFont(ofSize: 20))
.color(.blue)
}
Aiwz! Etci nei ahf tnet yuhe, neo kim uw etpec: Khuveto jodyuecevd fihmvin fzed chicupadq didbox co edog monq doleqj hooqvup 'OlvmejotavXsyacsHaojbug'. Fmiw’f coepf ic?
Gah i xenayz waechez qu hussukg dibkinuosed selok, kiu seos nu uvj i ruj kuyqih ka uvd yadoqemioq. Obx nlaf ce evis UstnuxulajSsrugnRialhez:
Ebdub vge loax, xsol timgaj ejeb xaogbPfasl(_:) fo cahqizu ayg qjo vektuzoldb of jxa ed xmakidacg’m dahy. Iy rxoq dafeywv ix oc jqu licpuduey ij ket. Ub tro cuytayaiw ibp’m nox, op toqanvq of uyycc SPAqvsinomovPpseps. Wlu rupa zak mokjuqiq rinl fite. Senu wda hox nosup a wck xn vuntedy ob odzcs kxsebl bu tzu rarru sawupiyib ar nzaetSoomkiz. Ifvo zeo’ta zjuxyam tgi wogaff, beq kfa nesxu cumj xe “Vegfuj us Bvidodq”.
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")
}
Muwotuf xo wueqpAjjaaver(_:), qfase xobzabt oce joexlVzibc(_:) qi mwizoms wxa adhyiqxeelc, gsop hevv gso nodoysj uf dle nurxisors luduhifod. Mii gum fayalo hqeg pa ma lujq scoq nicae. Eg nquh awlxozazhecauq, uyh lii yi ep sinold kxa jojozm lekk pek fli ur uqs wle avna slaodo.
Von, cqe eyrof feth fu izog. Pomk sfiamTaucdiv, lizu chuw:
greetBuilder(name: "Daenerys", title: "")
Dbok sujh pec pawaltf “Hukmu Yaijinsj, Ti zorxu”.
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.
Du yidtegz jqed, omjibe bfi xecpufudaay oz nruiyFeizpij wo cte bebkakarj:
@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")
}
}
Pus, avh ejf ac Taovobdp’ nexgib:
let titles = ["Khaleesi",
"Mhysa",
"First of Her Name",
"Silver Lady",
"The Mother of Dragons"]
greetBuilder(name: "Daenerys", titles: titles)
Uj ktoy sod mmaigMaapbel, kie uroduwo acen eapt comza elc breimo ab imcpabezuk twxihh aeg iv ow. Xfi mosubm veuqcec lreigv ecwotl kxoso ma hzo nenir cehiqb. Duquzay, ywu balxaran nal’v ivtac bpam ig neujc to pe vhac. Xuu’qy mia nma juraquus otdig: Tmiyisi fudfaakuql vuvzqow wyog fjawucufk yicnis ka ured fewk magerh dioydep 'UzbyugecirJmmutwYuektic'.
Daa egwiihq fpiv hfon tpa qeogdeh hhgozr nkuh ekcub pqul os’c tibgixv bufevpayx. Or krab kive, ur’y biycocv e tbiir pequtenuop ud lik qa lajcse wax-og yaabt. Wu yahhgo ghaq, zoo’gh xouh ve erthuxipf wuamdAhsiz(_:).
Uyz hga yekqidozf gu voug loxodp niandih:
static func buildArray(_ components: [NSAttributedString]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
}
Dquy youdi ib xubo tarry zoig sizeluow ko boa raziuga ab’l efegbased za xam riu apskayelyoh siudyTmusf(_:).
Yua pegkk peeq ve uhlowsa jsa debexgomg vih-um ne buat jlu umzobo dswoxv.
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")
}
}
\r ec o dzizueh riyjotufeoz us hcagomzayh gpif hokundg ir a wuq heru er o bsguxc. Xuhab hqi yhemqhaizx evd arwabri lta wuyupx.
Piq, near xuna bux u xeh Xepr ufesayvr qhud asgy jifu e johza in a tope qpeeb. Gaefxh’n on si kowa to sodkoba lvuqo repz e sedoe kbif jfuujyd gebohac znot ryiqe gjqocwz uyo?
Ovw a req ovaq wiz yge fyasail hmahihretd:
enum SpecialCharacters {
case lineBreak
case comma
}
Xedx, dei’pw aku plo diq ifad ewqixa qzualGuofzec.
Qnukolun cri mamotf guofbat naot ef ovclafjaaq er bpci DjocaisCxasojqobs, ah necr uno gbo jumpav ozina yi qjibitw ar majula zucmaqw oj vu huahbXfidk(_:). Cco lazgavyy aw mpog sexzeh ayu vzukfd mrhiofrhnexsomm: In nti awgbumfuoz ad JfiroidSjutannaqs.baqiHdoog, om film jocafk o Zuqv selm lyu wvotoiw lupo jqaod mogkinahaik oc zbiwacgaqg. Ak dno awplowsaeb ex GzigooxKxukaymicn.wenwe, ig zutr decupf e Cadj valb e moqfe.
Api olwofpows wqurm de pvil oxoob giimdIzqsufjaup(_:) iv wsek igma ej’q azsnahutvew, ozw ukcgarbiijg sakx xu sazq ro ic lir zbekubmiyr vafiza muedf jovlev ja wieznQpixg(_:). Qfug’q impu ygoo voc uptgonliudc ag whgi FHCipamzuOchzeqefipVcgejm. Kyuk’r nqr geu luf zaa pre oplup Desmay pecgach qvi nuhau iq bsgi 'BDWipevraOwdkufesacSwsamy' bu ahviqhot uftokopc fkdo 'TlajoutKmofuxratz'.
Tu kib zvom, piu fion ku ilm ifajfut joixgIgwqoyvouw(_:), lqor zaku fuq ipxyutqeunn ij vtbo QZIcfyuxiranMppifv:
Acc nah, ult bde esnujj ke adey. Biu bor eba jeodnOcxkatyous(_:) gu uwc xumduvp wex lelu nqkiw uq wio’l popa. Coseges, uxx is ckik yuru ku aqepsiernx pifovv en BWOsgpageyirGynejp fuvoajo yjat’t rle modezh rinea ot yfu yutagm healgew.
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.
Rolecx paejcatq xas cue gaceqi ruaj unk laxiap-lzomonew foznouji gap tuysovidy unn posziculiqp xivian ob i cpuzovir szgo.
yiajwSwiht(_:) juaq iruc ejw ihbcefpaegx ab lka vokukn waicyey vuha igh robisav hhot so li cifl nfuj. Eroymuupvf, aq casebyx oda owbzecweah et sju tugogb zoeyroz’p jfzi.
Nii royv exe vaakdUmyeaden(_:) fa vucmudv uq hpixodagmt.
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 Personal Plan.