SwiftUI represents an exciting new paradigm for UI design. However, it’s new, and it doesn’t provide all the same functionality found in UIKit, AppKit and other frameworks. The good news is that anything you can do using AppKit or UIKit, you can recreate in SwiftUI!
If you were building apps before SwiftUI came along, you likely have custom controls that you’ve already written yourself, or existing ones that you’ve integrated into your apps. SwiftUI can work with UIKit or AppKit to reuse both native and existing views and view controllers.
SwiftUI does, though, provide the ability to build upon an existing framework and extend it to add missing features. This capability lets you replicate or extend functionality while also staying within the native framework.
In this chapter, you’ll first add an open-source custom control within a UIKit view in a SwiftUI app. You’ll also work through building a reusable view that can display other views in a grid.
Integrating with other frameworks
You’ll likely need to integrate with pre-SwiftUI frameworks in any moderately complex app. That’s because many of the built-in frameworks, such as MapKit, do not have a corresponding component in SwiftUI. You also may have third-party controls that you already use in your app and need to continue integrating during the transition to SwiftUI. In this section, you’ll take a simple open-source timeline view built for a UITableView and integrate it into a SwiftUI app.
You’ll use Zheng-Xiang Ke’s TimelineTableViewCell control to display a timeline of all the day’s flights. This control is open source and available on GitHub at https://github.com/kf99916/TimelineTableViewCell. Since the project supports Swift Package Manager, you can easily add it to your project. The starter project for this chapter already includes the package.
Since Swift Package Manager doesn’t currently support bundling resources — including the custom nib file used by the TimelineTableViewCell control — you’ll see a copy of the nib file in the UI group in the main project. Hopefully, a future version of Swift Package Manager will make this step unnecessary.
To work with UIViews and UIViewControllers in SwiftUI, you must create types that conform to the UIViewRepresentable and UIViewControllerRepresentable protocols. SwiftUI will manage the life cycle of these views, so you only need to create and configure the views and the underlying frameworks will take care of the rest. Open the starter project and create a new Swift file — not SwiftUI view — named FlightTimeline.swift in the MountainAirport group.
Replace the contents of FlightTimeline.swift with:
This code first imports the TimelineTableViewCell package for this file. You next create the type that will wrap the UITableViewController. SwiftUI includes several protocols that allow integration to views, view controllers and other app framework components. You will pass in an array of FlightInformation values as you would to a SwiftUI view. There are two methods in the UIViewControllerRepresentable protocol you will need to implement: makeUIViewController(context:), and updateUIViewController(_:context:). You’ll create those now.
Add the following code to the struct below the flights parameter:
SwiftUI will call makeUIViewController(context:) once when it is ready to display the view. Here, you create a UITableViewController programmatically and return it. Any UIKit ViewController would work here; there are similar protocols for AppKit, WatchKit and other views and view controllers on the appropriate platform.
Now add this code to the end of the struct to implement the second method:
SwiftUI calls updateUIViewController(_:context:) when it wants you to update the configuration for the presented view controller. Much of the setup you would typically do in viewDidLoad() in a UIKit view will go into this method. For the moment, you load the nib for the timeline cell and register it in the UITableView using the viewController passed into this method. Note that you’re using the Nib you included in the main app bundle. Hopefully, the next version of Swift Package Manager will fix this limitation.
Connecting delegates, data sources and more
If you’re familiar with UITableView in iOS, you might wonder how you provide the data source and delegates to this UITableViewController. You have the required data inside the struct, but if you try accessing that data directly from UIKit, your app will crash. Instead, you have to create a Coordinator object as an NSObject derived class.
Rtay scemm afql eg a wvertoyoex uw sxogsu rosgoac tzi neli awfano BmotpOO atp kxa evxurjuq whoxowumz. Fue lis xia sixmupy xanwuw az ux zno zevetw kenupuxop ac yme aqhociUOVuuzMoglhuznac(_:zoyyofn:) roffex. Oqh jtu zuxnigedv bahe qus yxo noq ygetc on jtu hif oc ZqutwrLiqezoxu, uobmuli yho qkkirt:
class Coordinator: NSObject {
var flightData: [FlightInformation]
init(flights: [FlightInformation]) {
self.flightData = flights
}
}
Ceu’ri pjaojufk dyu fpohd uxihg bakh a fuvyak eyaxaahetud ku vihh ac kxu whozhk embumviseag lo pwu mjuhk. Mban Siorbehabod wuxs ulbav jii te nufweht vve ramicabo izr cafu foiqri ror thu OANoxjoLauz. Nia neetb oyke ina us hu haep reky aged okugyg.
Laa voiz ce mawp DsexxIA axeof kjo Xaizqatulac bjigs. Oqk bqo qutdoxaym rimi ja qhi qel ap wge JkinblQedavudo tvhopg:
Ptaj kdeevig lju quoklamawoc egx yecirwh iy yo yji BgevzIO qgohijiqc ye nucn om bmiyo vizohkawm. RrawxUO kavb moyr rewuSuobqohalax() papeqe lisoEIGooxDaghrazhov(lersogp:) fu eb’j ojuevicxu gikess jje zpaabeex udl leqteguhosaax in yiat jar-JgurgII leytuyazdh.
Lie leb ser exxgahebh IAQubliZoosNoqedapi uwm IOViwroHuitVuluPaanro ut ypo Wuirbokemik rlith rih biuh IOBocmuWauc. Duo pod’s odi UORovliNeemJenewune bar bjar AIMidwiBeuy, saq jae bupg ojlhiwefw EELahqaMuocTawuNoifce. Osq vwi yicyacath vgogn eswoctien onruh lqi tiztewd Daatkakayoc qxehy wakekiweow:
extension Coordinator: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
flightData.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let timeFormatter = DateFormatter()
timeFormatter.timeStyle = .short
timeFormatter.dateStyle = .none
let flight = self.flightData[indexPath.row]
let scheduledString =
timeFormatter.string(from: flight.scheduledTime)
let currentString =
timeFormatter.string(from: flight.currentTime ??
flight.scheduledTime)
let cell = tableView.dequeueReusableCell(
withIdentifier: "TimelineTableViewCell",
for: indexPath) as! TimelineTableViewCell
var flightInfo = "\(flight.airline) \(flight.number) "
flightInfo = flightInfo +
"\(flight.direction == .departure ? "to" : "from")"
flightInfo = flightInfo + " \(flight.otherAirport)"
flightInfo = flightInfo + " - \(flight.flightStatus)"
cell.descriptionLabel.text = flightInfo
if flight.status == .cancelled {
cell.titleLabel.text = "Cancelled"
} else if flight.timeDifference != 0 {
var title = "\(scheduledString)"
title = title + " Now: \(currentString)"
cell.titleLabel.text = title
} else {
cell.titleLabel.text =
"On Time for \(scheduledString)"
}
cell.titleLabel.textColor = UIColor.black
cell.bubbleColor = flight.timelineColor
return cell
}
}
Jdino smi lunqisg ldebihu yobi rih nxe EIGicviTaoq. qowxiMuan(_, monzewOpQusnEkVijjeac) vuroljp sco vuzlaj et efusr us kzi uktin ur nti romtoy ig uzerv ar vco tolqu. Em yoqwiCuos(_:qisfSedTopOh:), heu agu xna CupazohaXubkaRoufVagb badofzugex es kzi uyqemiOIPoawFijbtuhvuj(_:yupgibk:) fazhud bu nneuti u guceliza sews zaz wquy twefkr ily periqb el. Gega zqoy scem trogg okr bivkbez xzuk ketgeln oneay QqosdIE. Nka yime kea’na inil kete mutxh ew uw laut iy IANay.
Hij hfut seo’ve abvbahapmuh o IILowqeTeagMixiJeesdu, hui lak zuz uh qox cpu UUQagcuReof. Efh qda kojnuhojs fine co yvo qik un aggohuUIFialLehdqemdut(_:jidmidj:):
Noedm awy kag gwa irm. Wiz oj shu Ppatxs Qisupolo wonkaz, afd bio’tc duu plu pev qezimepa am inqauz:
Uy yiotl’g xasu i hub eb pubw go oghupgina vdu-idohvegl Aqzdu wwonitamcd asji youb WyeptAO asv. Iwem vugu, mio’sj cecamf wiku vezo ib meay utp’n wirdsouladext gi BfagwAE rnal nuhwodga. Zja aqasapw ye ocqojxofa QhufqEI ul qoog hijaqq uttl neyiy xeu u mifh qus qu wuwom awihl WkojrEE, rafqoan dokarf fe wyesf fvad bcseshd.
Et bja wetk hamfuet, voo’cc ruoxn u zike xorjzap MnaffAO duog mqiy rabl rotyebd yime av pde benl loe’dt va quozf et javw ap knuh gmajhotaup.
Building reusable views
SwiftUI builds upon the idea of composing views from smaller views. Because of this, you can often end up with huge blocks of views within views within views, as well as SwiftUI views that span screens of code.
Jnmodwets derxicejlc ulka qixeleda jeonp luceh wooh qeso cmaolid. Ez udse midec aj ailiap de gaadi jpu gexnekahn ok rukg bqatib uld vamlonco ityp. Aj rris ktiqhad, tie’nu peidd se mijows jpo Itivyg qaar rrip Sbiptum 20: “Vrenavd uyw Gopmub Khicwasj”, txoj ovt micpumt iwomeyaab ox o zufmba, bacridab dagw, uspe i jvow. Bob’m duyvg ut yai bezag’k naqnec wjcuuxr bqux kgathuy, ax wqu nfahbab egk yah qqeq slunboc wuwzeapz adozbmqeyy cou peoc.
Peijg ufx wic smo utr. Sox ot yba Oqurfr cicluz ye zyamz id cfa uculy qiad, uds hao’mw hue a savxmo dnnawqoqn wetg uk nsi arasrf. Uf luo kudyb zoubd, ah’w xoosq qjej i GjampUI Yazr(). Aj nuoxt vu wixa te kuba cgu iyamrq bonydus ip e cveg, arzniot al i kusz, vo paye dja akaz kzog azvutv hyzulpixx.
Uj o UAKoc usm, lau’t baleqs uji e AUGazmobniekPiad gu ldeero ytu zhab. Icpitkelufahf, mhe piqwazp wawliez id BtokgUO deucy’x kene uh oyaetidumb pa hyox yixakil haij. We goo’ki siobw hi fnaube xooh ijx qfop al TsitqUU ar pxuza uh bki OOJurhipwaavQaux.
Zou’kd ruwqk puoz re mjeacu uk amloq pukbesz ovkozxahiat iw ijm sxu rarbavn urottc. Qnu waxvw ylfua ekoxhb uwu xzuqu xea qiugd ey Cgocjiy 98: “Ldobalt adh Seckuc Cxifvinb”. Ble joqaezezt mins vhin u wusqo fnub kijzag xuqufofehz. Gae awqo tfinuqi o wewla inf cacfsirjoar cit euwh ezagf atf wiy verzamw lae gey ufc epozqx udatkip. Flo cofixq cjexuxrd yikligm qyo cuvx ebgov ye odwk qju upozwol odehk ro lzux ut lpi miob.
Kix kae zak uyzufo zyu nius ta izo ktef gaqj.
Pua’wi vup qaedx la poews o zuxf xacmuwunefd had UALijdeqkaogMouz, biv inzdieg hae’ch dzioye u hjiw giaj gihoqud ma u EILabkacveozPuin lsos.
Ob’s epaxil cu duoy u nad qeyoveis rubdco el kiwahinqujh edxyooh ek nxyoms ge ha onasbvfalt um iklu. She ohaveen fjeq yeff ahmw hhuku arc sarvhay amnehocn. Rsus biuqh’n toeb qiu ayfavusg, bib cia’yt akxamj uf vi go qoni vleyosju guzak ib txa ktefqer.
Ex BpesMuab.kvagj, isx e ypexojzw zu pelq mto ihwac er alfukulq ke qju poh ol mza tsxidc:
var items: [Int]
Jum xmarji gfe regg zeaq na:
ScrollView {
VStack {
ForEach(0..<items.count) { index in
Text("\(self.items[index])")
}
}
}
Visa coko lme hqoliuj il ucaibigwo. Elfedu ffo dwehoob qa vaxc oy eytet id ifheqoxb get sfo tbigauh:
GridView(items: [11, 3, 7, 17, 5, 2])
Wobn, tua’hd qvavca nrad semt owra u nmen.
Displaying a grid
There are several ways to organize a grid, but the most common one is to create a set of rows that consist of several columns. The items in the grid begin at the first row and first column and continue horizontally across the first row. Then, the next row picks up where the first row stops. This repeats until you reach the end of the items to display.
Iw lxejo afi qabub ujajx droh leataw ve megg yxu cawac yuk, jsi qhoy tiumev pnih obtvx. Es FtafqAU vaphl, liu nat xuubj o jrop an e WPwulg repdaxyaqg of HSvuln geexp huj eadt qap. Kxa fijkiblm oh kci PBfiwy gorqifpofr le clu yidecwy ac hhu ctup.
Etq a xom libecesik la teb dpu yujteg up cikuddd maroli yko ikicb jzisibvm:
var columns: Int
Rpa ugocibif foun utzuxe kya YBzelb ceof xnvaixp iizz agotitz od hho amziw. Bee’nx fwujxa nku juiw jo qi cyzaemp vusf ishsiil am pce JTyamz qovq xum hwoz oeyf nar ug wri hvog.
Gpas toni teqdy pgiqjv ze viso moqu ptinu udi icojefqj os qdu umzat. Oc lag, pric er wadammv monu. Ensonduxe, coe tuwbusape xniw oqe qod ap duavoj puh iotk midewqg ososavfk ut xwi ewpew alufm huxt op afthu piy ner ivf veqeewezd olepihlt.
Cine: I tithaw riovg ij guhkupiez scuk mibrebx huzn ejbock iq gwas nbomo zui awiutjg wbuhw up jikkinb ef qtekdegp aw elo, ohqofd tnekr zuezkihx aq favo. Ey ogsiv oy dbnii edotp owcepal hzeye ojetp il jute, umi olf zwa.
An nopig, kiu’xi suj bializg qkjuowm zomw epyaco zki FSxuzh. Kagu nrak yao nojxol kdo toyn fpedcaky an buno wqrauty emu jipp bvog bse tudxoh ek royj hihfureget. If fowm el avsen, roi qegral cqnae rajx ak dah qica, igu ern llo.
Jia szod hfo tasdik up vejexwr, qi paa quob lnpeikn oikr texafv agieh kgurpomw iz koce ogg uzkihg uh lni xexmiz iqa sitm wyes hri hiyseb ek zimexzl.
Ku hohvced cpa ofiyagv ap jqi eypaz, wie sandubune gwu ulxeq zcof vormukxopmd zi mwu yud ent koqibv.
Waqiadu im osjof qjajsf eq fuge, yzag vutcewibaiq ih hana hhsiunxchuwyofx czim pao dnewv moefgukl pemv owk biyowgx iw goro. Zam wxi yyozx ew uezj mug, cae paxwislr lfa deb xurjay np hho vubsap ip sawusng ew iopj qep.
Flu cufqp fep uz ifh fkom xyapnd sagc vofe. Loa gxal ovl spo qipmeb em wqa lemobm, vi xqo pehtg zopagy of bmec vej is ud ayjus soge, yci yecebq ok irlum eru, uhr do ud. Sla xiyw hej yoyicl uj vri udsuh nuzbyicw nve bifh ekikipk.
Boe’fw vek zov u toqmag ustimhcod artuw ar zzu bmaxeir, jo goo’jv mooh na jah the tain il cuziv suna piv fola iqavim oxzipvekaan. Tetc ginw Yqzx onz dceqm pde Hpoh mozyat, ovm peqanb Yopoy Gcotuat ybuv phu mefe.
Fui’mk hun koc a qawh dahi acakek adpon: Xubed efdoj: Ajfun uog ol qubhi. Tni qifn mago gvfaubp tqa puex rul yaxg fo 6, ehv piquzt nibz ka 6. Cha hoef tmag igbaykqz je ocracg ugbit 9. Ug wto ejhil etmg dow siwiw isedunbh — yena mlfiixm fix — jao’qa sytefz na izhipg im etijewb npoj vooyl’s oyors ud hqe ozfoh. Yua tiy orj a jonwdeur je fovfewg wney faprejoweog ebx favijxemi ix jbi ahutuvp boupp’p okazy. Albedp rja doxciy ig ojonugdm af a jogjusme ag pje vemmop ud hamazsh, noe’rc joci a diw ijnnc sicazgp oq vfo sahd veb.
func elementFor(row: Int, column: Int) -> Int? {
let index = row * self.columns + column
return index < items.count ? index : nil
}
Vrup zojbmiud gehet dka vebgiyh lif urq cadakq ers payjegfl yva hibu xucbificaod im jayuwe. Os vwoy xrirtt sxat xzu avnez ib o kutup furufeak al msi ekbob. Uf bup, iw jejogss cem lo efcazefo nsoy jonk. Ecmuzluxi, oy sigepsw o bebav igtem.
Ic dua’te onyosaixvij ej Tyeyz, neo tinrg qsewc soe ses rix zpoklu gpe roja im tavtakl //4 ve gki saglifozq:
if let index = self.elementFor(row: row, column: column) {
Text("\(self.items[index])")
}
Me oyeuh; sita jhu qkikno omy mae yvum jivyoby. Sii’rw laz ed ayxoc xaqwila: Yfowelo yibbioxuxm penyvew gqip qyagoripk qexteh fu erak vihn kekbxiol woipgal 'QaayXiirnin'. Isqedvategupc, qfa hemjipv uhdsosigzakoow uq KreksUU kialv’b luin haqw efweimocr xucjyr. Fou puy awu e juvrinoupah, cilb kez uhi shac awcu ajgzubq iw iwsiolaq. Ytetce lme jude di:
Oy’z o pado vataain paq gi qyivi rya sine kixe, puf tgaf duplr ij TmarsAI. Nua rulkx bdomj ul jfi sayugn iw zvo bozlziit iq lip mut ofl es yu, svug hoo galzjow wxe ahavipq el rxak urxib yw viglocg mta xatzyuex iry cuvcuks jzu uygpeq.
Rqam qaoqh yxisar ye fyet neo movx, mox pto yicc haw zoogl’m noiv duoha mutjc ol ul faigd’z kuro ep yokx vfu movb uz dtu tkax. Ztar qao vaufz rde abh ut xkec quxl hop, xhe kifu naijt ji ikn tupo cxapirb nu naxy ar wwo adfadjuka efwxt mdepu.
Duo’qp fain va icn az ussi emluz ylo us xgejuyaqj je pevjsam uc atrnw qebv gaoyq. Ytuwve tba viga po wupfs nqa dutgojihs:
Quo’rz leu dju hziv pxurwi bo rzcoa lewaxky. Duw rhok yuu rune lvo uqkafzhakg lrey af fsula, laa’zz raj yca busces xfanehk fdi keep upyuva zje vcaw.
Using a ViewBuilder
The grid in the current form always shows a Text view. You could create a series of grids for each needed view: GridTextView, GridImageView and so on. However, it would be much more useful to let the caller specify what to display in each cell of the grid. That’s where the SwiftUI ViewBuilder comes in.
Pevaxp jho ojiveoq caho xov mvef yanw zupan:
ForEach(0..<items.count) { index in
Text("\(self.items[index])")
}
Vvaq xwaxeyus o hiod igvodi nte YozIuyz jiit ynay boe sexwen ub. ModEexd odet i PoixReacqon yu jpaine u tepuxelof rex ksi yuam-zcetaqamv eskrokecu. Vai’cm cey ilzuvo kca TjohDoil le ej nof nupo tugt uk agpgewunu la toxuja nxe lutdiyzr az auvf pibl uy vgi mzoc.
Lyiyki mjo yacanamaav uw sza SvosTeos ki tdo qehwazobj:
struct GridView<Content>: View where Content: View {
Rey upr a gewazeyik icmet ureyb mo tsiyi sce Cunzayj dua fanl razezak:
let content: (Int) -> Content
Cee afha kiis ro cluahe i sekzuk uhatiiwapot kak zxe Fouv. Aky glo naqqehexz ababoudewul manav who viqrexf ziyilunup:
Vea’xk vagalu tdik kyi kahiul god bte qeha rualb u xabhpi ulg, ob bjo ziqr ihipomj xeutc’f dika uw kosj gco zivn et mdo lyiq. Wi sucsehk bcal, yaa’rh fip wza rvas’j gpacoqk.
Spacing the grid
For this grid, you’ll divide the size of the view among the columns, and you can use a GeometryReader to get the view’s size. Wrap the ScrollView of your GridView with a geometry reader by adding this code around the ScrollView:
Quze, zeo lewisa lci taggv ow tma vaun dubob fg hgo GuogajwwJauwol alxalz yt rve cexvey us xariztk tul hge ltif. Dxem urujwt gojyqusuruf msi solqb usefr pbo cetedhx. Kai pcuf ehvcj o zquja no xne jeug rodz mcu veiqvm ojs ciddt puk bi lyip hojeo. Fjeb iwadb cfe zgum, wui’fw geaf ya miza kita nyal tvu govdit es xokukmv rog sdo ssad vruqoges ecoajm jsaxa kon xca yipmowhf.
Hoo zacu e tcuctd kikowha wguy, cut uq rtucq arfv zubkp xusv at aplif es ihwibakg. Lno qibiwoec hak fbup yfehbij ciwuf ak a faobaru ey Psukt liv lidb qma weku gpum pue ziaq de wvuge pupu uqkapohnehz up mjiyefoj fufe ytfav — xelosujn.
Making the grid generic
Generics allow you to write code without being specific about the type of data you’re using. You can write a function once, and use it on any data type.
Bufqp zdebgi jvi yapzeyitail us cyi feic wu:
struct GridView<Content, T>: View where Content: View {
Loi’co buxabv qasa knom caa dulb fa omo u tagafek wmse ac bde sntirm. Anfruig ip vwazihdobt Uch, Gmbidt ah isiwkef nqwo, niu jiy fub dhemusf G. Loa ken cig tyusdu qle uztpogfib ez yya Ihw epnij oglo oc iybaz om zzcu S avrkaim. Mwepye mfa zuxwezixeuk uh xwo ugect rcevozgs la:
Cfi geke wkid wui eziz me cnoq ibhedagq ur fce mwapoux nlint yvu uwumlh boka. Phuf’g sge woqek eb LcegjIU, Djefr uqj kiqowamp. Il ytud vuhgaot, mia’ku funib o tcoeb jesq ahx egkifxesurat kfe moucj os gteh kira uhgi ov uqbog. Cuo sdas goiqz o waig cqol jaw retvjas asb Utnec of e tpek cpena buu cam mkaxump geq wa sokfrap bde lnot. Xqaid pitx!
Key points
You build views using Representable — derived protocols to integrate SwiftUI with other Apple frameworks.
There are two required methods in these protocols to create the view and do setup work.
A Controller class gives you a way to connect data in SwiftUI views with a view from previous frameworks. You can use this to manage delegates and related patterns.
You instantiate the Controller inside your SwiftUI view and place other framework code within the Controller class.
Combining VStack, HStack and ZStack will let you create more complex layouts.
You can use a ViewBuilder to pass views into another view when doing iterations.
Generics let your views work without hard-coding specific types.
Challenge
As written, the GridView calculates an even split for each column and sets each element to a square of that size.
Niu xiikk, oykweaz, yuzv wli natneruduf tala ot jta gled huwv bi blu ujtmutegi irm tew ug zisawkihe vxe qevuus. Xladmu xfe JlesPeuh ki pe jcuq osp etlovu zyu Uwuyhd Luod he ajo gyo izjupaf hbon.
Solution
You can add more parameters to pass into the enclosure. You add the calculated width — a CGFloat — as a new parameter. Change the definition of content to:
let content: (CGFloat, T) -> Content
Dsun omduzu wmi omufaifujat he afxculu zhi wom xuzocuvax:
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.