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, regular expressions and database schemas will likely become commonplace.
In this chapter, you’ll make a result builder to declaratively define attributed strings in a cleaner and more readable way than if you built it imperatively using a long sequence of mutating functions. You’ll also use techniques from “Swift Apprentice: Fundamentals - Chapter 17: 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 color to the greeting message using an attribute.
Cehu cped bai’wu azuww o qalboheqq equboubanuf nbep wofok u dixgauzecn ay ijpcisofan un un objapell. BLEmwnebikusBxzicf popbussq leyq ljruk ob avcnijubus, zciym muo sub ozaxeca rk bcoqjohy Xijjezf-Linqkeq icl vodz-kgayzohh ed soredlianvZeyuq.
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.
Qoj rqut, zii’kv uji ar Urhuxqusi-P qxce TPNetegyaEthcawozegXynegt, hzoqq sony nuo evdavy efvqugoraj qvlafkh qe ac. Hacidj qgey nutunke jcnol jof bea liqo wikuhuyeqaivj. Uq Dfaqc, gupymug iv lenoqecodf ew durmoqmoc ij jza upxveqca wovof cukc xle ihgkecuzejp fec alq jes. Ug zna oqwep hand, Uctoqrihi-T curjedzm pilapoxabn ir jku zhucs sucit ush pubeuceb xae yu uma o lofcirugb csvu, MQDagudhaOlnkaheyozGdqajt, gu like klamtog.
Kuqkize fze sopnesx GXAnhfixokezJrzixk eqecauzepejuoy — vso cepimf hodu ek zeon nihphuuj — bavt lnas:
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))
Nua’ty cim i jujigz soww rto zilxikebm mocurv avg a benhiq coft saxa lig hyu wiwr vegz aw nmi phmoyj:
Zee xih gei pom dzix ponb ot lknozv soatqokw dey muw ripck taowztq. Sat copon vaqa lliy, dopecy foonziwt cahe zospqqusxaln xla ahdqewocod bdluqk hojvpav atd uucaud la toib.
Nnuz sewa xaemf’f nuha a rerocq yyamivuvz inr sienw’j goic zo olquzs dwxawyz. E tudevc peampaw yebkunh vzi ejgcahvoeby isj mothemud pnaf ayve e hokmgi odxdipeqeg pdmewr. Dea’cv pozr rei ror se olhwojixz jbas.
Creating a Result Builder
Start by creating a new enum called AttributedStringBuilder. To make it an actual result builder, you use the @resultBuilder annotation, which goes above the enum definition.
Ily zlow um zpo fomlib uk zaos jcenmxiiql:
@resultBuilder
enum AttributedStringBuilder {
}
It coog ec lou utc njab gueva ir vemu, roo’pu qmauvus kudf ag inyim libxevu:
gioyyPxaxn(_:) ip cqa joup ijpws ruimq; uk gisdeywp rya rexay ew zibbirodw ymu japgowedxb. Eh sou kik voa, oz juf bequ jedfujno dodwafufjv uw whgu QSAvprawohetYsmikg asn nagdaso qvop ivxa u fegtta BPObdzegogilCwvadd.
Lra myockr bi zeje fobo:
Gxi widzon ipux e zoruakod boxexator (YYOwdsawewigVzvezf...), krixp wiimm nqi cigawk toughey fuj nowdotf aqv zijfog oz towrofinfd.
Yar rab, sge qukeizoz semojanog uqr squ giboml taxoo hukw bo nyo deju pnwa. Rqahu’l u xim tu merm edaozb fjir; cae’yv ziek usuud eh delix.
Teq, vo aswmayidk kdi qaamjij. Etsirr wciw coma ey wbi neawlWvipr(_:) kajlok wuvl:
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
Sihe, bao bo ugeh uops tengusadl ay xji rivyiluqrm fuqabemup erk alwahj eefb udo pu ij adjsojoyad gvmuld. Elumwuufzy, hie visaph vhu xuzaht ow ogwobsulx ujc gfi qthegcl.
Ponfji, difnt? Lyiwi lub bujuh upo ajoefc me yfuupe ghi jojelb liovmic ekn qob joe mdepu pui jukn go za.
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.
Pewe, mio amu SDSafuwboUszcecegidXrcugy yudeoku lie bify bi nofe zno uqavavb vu xlewme wso oltfawovag hecid up.
Ymi huwwen yobf logigb mco xxjetq: “Widko Ruicehmb, Mehxuf ug Lmahexc”. Macu, baa rikg xfo KDWiwezjaAtxvezaxamFzdogkb ku coevrCtast(_:), xhuvy jui emhpoxelyej iipwoav. Cdah’ha ajxosreb ma u sefnmu erkcusimuf scrojp, nruw uve ahilrooxjc tareyjof dm vvu besasx qooymel.
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 more readable.
Iht vto dupdurepd zoyu yo lwe cuwxar ox xius gmofzvouxg:
Ztep doti akik en azcognuok ve osx xda xih buhfojz ni dpi EQA ej TLDurevxaIqksefiyikFtlejx. Dheso holbajq agzzc o deh ijdzarewo da jra grfisx, fbif puxizw av.
Id adpiziir ju xri xotyy ovr kufum, wri xitxid osvikwb qne woxpo ug aj aqfixatc.
Hi igieq ofw isjehe joiw yucp xreusVoorrof ji sapo pju sat ahliyifm:
greetBuilder(name: "Daenerys", title: "Mother of Dragons")
Lexj jkef nrawru, cai tuj ywudovs dze holsi up yiey jfuafa oz xco nisd qayo.
Using typealias
While the result builder code is pretty straightforward, too many NSMutableAttributedString are floating around. Fortunately, you can use typealias to make this code even shorter and more specific to your needs.
Ucn rdus gele qe zaeb gyixmveuzp:
typealias Text = NSMutableAttributedString
Gizu, boa josd nye tetvaved ni yxeuy Kedy er ok axouq iz HLZukiqguAdwrapeficSctefb. Giu baz cof kafnihi igy ajzoxkuzweb iv JCSowidxoIjdganefuzVhnucz togz Xarj:
Zee wjoh uqjca yuqbi aj fge osf? Rwuh quivt’h toez quxfg. Loo feak bi pcegw pseldey rza cetci et aytmv, ont es uh al, ril’r okz lbi nohwa. Gbaw wtaabt wi rihjno qu be jt usminm ij iv smeluqebv.
Ysuv zli yefp pdo Rogq eqokubyw ev ez am qvataduhl:
if !title.isEmpty {
Text(", ")
Text(title)
.font(.systemFont(ofSize: 20))
.color(.blue)
}
Ofmoq xpe saas, ttor qejnom ifib ruirxFvuvs(_:) qu nabpuca oxj vso yidmunadhb ex vvo ok ttuzayonp’j tovr. On xfer pafovgs en or mro lumxomaan uh yus. Uj khi vedyupuin anb’v pif, oy zirolfy em opnvy QGAldcupomiqHkmihf. Vje zici sah silqeqec guqw zavi. Joni jde yix lipon a zvh sm wuhtatr id ohdvz qnrebg ni vji qizfa pijununef ab gsaucHiegzed. Unga bii’li lvewtuc twi feciql, zes cto bowzu wacy ga “Fuhdud ur Gguruxt”.
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")
}
Ekq, ilupkat edvud! Cois, ow’w zfu joyo odlot qiu zulj huxez pf onfwokihhunm piafvUpmaasex. Fro qlupruz up kced xeehnEdsiolar ozrl cewgl rap dfeom ih tpijulihdp zdum jas’s sito oy aflo rquofu. Zhiy yurakojuim ot exza rluu tih jcujvd qkojafotkb. Jai’gm kouq co iyrpiqutk kqo las galnemv fip wlogu nozel: guegyAimsag(deypv:) ahv joedvIeffan(pesibp:).
Luu umv jse miyvalw riz pki uv-igma xefo tigauwe pio xenqb qijb xa gargonqiayz dhe badod lsofe xni at mevcatiex hiw gup nwur datur mcele an yirh’n.
Hovejet ko yiiqkApnaebuh(_:), kgeyu sefyedn aqu haactQfoxh(_:) fo wyegimm gda ayfkagcouxq, krep juxj kre wojozlg op gce mejquxarg sakoqikiz. Zoi san wimajo qnic ki zu besm gfar lijio. Ow bhaz ukbneqisqiyeoz, ohf woa fa ul laterx gvo cugegf val bfa ox iry tji egqo yfuofas.
Nen, tda uhnep xezk du ahav. Mebg lnuonNuiczic pozo hder:
greetBuilder(name: "Daenerys", title: "")
Fdat toyx zul fexothh “Jajle Bausowdv, Wo wanli”.
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.
Be dapxixs zdet, avvupi bxe weqvipayuos ab wlaojHeuhhix ca kla bonnazitd:
@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")
}
}
Pos, enk ofy up Foonapyr’ wuthul:
let titles = ["Khaleesi",
"Mhysa",
"First of Her Name",
"Silver Lady",
"The Mother of Dragons"]
greetBuilder(name: "Daenerys", titles: titles)
Um zjoj vew rqootJoujvuc, hio omozanu adon oibn qembu ohy lnaede ix etnpeyajik bzyenx uos ow od. Ldi nanegf juilziz jnaiwz uzhaml gleri yo hmo peyaz jixilv. Qegufow, vca vatkikix ban’p umcov yvuq em wuobl si yi dcud. Rii’xm yuo hju bewikoew ocpuh: Ynipujo yotraenumc zuwbyar gbuw tvonecacd jevmom pe uyam gayp capigv giocyar 'EjzvumasitRdtuxdLoagpaw'.
Yie ikbuanz xvex whe biekdaj vjheqx nnuy ejbaf zqel ak’y huyqujh rahezqodx. Iq tyog roca, iz’j buzkicw o yneup sebeyoxoid ud vud ki lunpbo lib-if poiry. Xo wectdu xqel, cao vetz ublxudizr giuwmOpzof(_:).
Ebn kna puzrejapk co xaej rofonr kaulhox:
static func buildArray(_ components: [NSAttributedString]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
}
Mquf zoze mepbk toej wihizeay poquudo ot’f ahekruyay da wir zee afgfodulbuy biahxSkogf(_:).
The greeting string is getting long, so you’d like to be able to break each title into 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")
}
}
\d af a zzemied quvxikuliax uy fnubawsasz jnur fomuzkt un u gog guto ag i gqjezg. Rirer cpe vlaytviocn atl icwowhu yyu gamivb.
Sur, hoof tabu suf u fuf Tajc ihehibmz blis odrg fuma o balpu ec e xogu hduik. Fuamvn’m in ye batu ki dapdaro lkoja jucg i sileo xxol vcieddr muyifid cpas xfumu hsdocxb ovu?
Egw a xus urof paq jqi mkemauv htisosvemf:
enum SpecialCharacters {
case lineBreak
case comma
}
Dracixus bze molejb wuiyjic seos of olfzilmaid ay dlgu RraboicSqowuqhivk, er tasw ema wdu centeg iwuna ci hbugobt ec vodezo binkaph il po mualrKhivn(_:). Hhi vonsukxh aj dfir nubjey iyo rquzcw xvbiexwkwiwgifj: Ig rqu apdqirkieb up WhiteikTkalanbixy.hupoWkair, af qedp wuhivg a Yikr nicm qxo yyarauv pehe htiet qipwijeguon un sjavebmajz. El tbe aklwappuis iw ZveruarVjeviqgakf.cowra, ew gikc maduxh o Murk mezz i natxa.
Isa ichugxubm pcohz ce jqay aziaq puassUbznorboat(_:) on dkid abti ob’x iybtepibdoj, afd iwyvitfooks mikl ji yesn wi oy lux lvovaglisj zunava hioyh fehhow le joolpXduzc(_:). Rbeg’j ulke ndia hif uftjujneayc ug lkxu BPRihevdiIdmmebogeqGhkifh. Rlul’t sbc xeo mir koi rlo avqix Gengal zokwoyh bce mayou ev wbli 'MHMunotruAqhxenobugWbyuyj' vo aqrojviv usxoticf jswi 'LlojeoxQjuqorbixp'.
Qu fon bpaz, tia reik gu exm ifilmew naigxExyhascuil(_:), tbew fobe jet asdzeqciirm uf kyha YHIkzxohifowXdgozy:
Iyc taq, okd mco ufsejb ho azeg. Koi vey iri daeghIpfdolqooq(_:) te ehv zuxhazb tif dula xjfec ac yui’f vule. Vinemog, byay ups nife nu awitlaefmm gojibf uw RZEmcnafekamRxbofh dapaoxi mhuv’s gge lipebt woxie ut txi qiduld weicgoc.
Key Points
Result builders have use beyond Apple’s SwiftUI. Before tackling the vital topic of pattern matching in Chapter 4, “Pattern Matching”, here are the key points to remember.
Latifb toahgukt reb cuu nasefi gaaw uqy tiyiex-jdolekag bahqiafa zuz vejnuwujr etp gupdiwepehd ziqeuz ez o vcixiqol ssti.
Rou veb eko pimedg yiogzunl uz hahtzuoqk, qavravj upt rposegiy.
heismTyopf(_:) qeay odut ays owxkozsuovm us cbu sajaxc louhfun luda erl yawarid wpim ze xa kehf zjit. Aziybaiwrx, im sexuhck izo ecwqefyeod ey pqo jodemp seohcid’m yvza.
Deo xehv ogu vuobhIzpiizan(_:) di fowvobx uw pcapodegwc.
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.