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!
SwiftUI does 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 also work through building a reusable view that can display other views in a grid. You’ll then look at integrating a UIKit view to implement functionality not available in SwiftUI.
Building reusable views
SwiftUI builds upon the idea of composing views from smaller views. Because of this, you often end up with blocks of views within views within views, as well as SwiftUI views that span screens of code. Splitting components into separate views makes your code cleaner. It also makes it easier to reuse the component in many places and multiple apps.
Open the starter project for this chapter. Build and run the app. Tap on the Flight Timeline button to bring up the empty timeline view. Right now, it shows a scrollable list of the flights. You’re going to build a timeline view, then change it to a more reusable view.
Flight list
It’s useful to keep a new solution simple in development instead of trying to do everything at once. You will initially build the timeline specific to your view. First, you’ll work on the cards.
Open FlightCardView.swift inside the Timeline folder and add the following views at the top of the file:
struct DepartureTimeView: View {
var flight: FlightInformation
var body: some View {
VStack {
if flight.direction == .arrival {
Text(flight.otherAirport)
}
Text(
shortTimeFormatter.string(
from: flight.departureTime)
)
}
}
}
This view displays the departure and arrival times for the flight with the airport’s name above the other end’s time.
Add the following code after the just added view:
struct ArrivalTimeView: View {
var flight: FlightInformation
var body: some View {
VStack {
if flight.direction == .departure {
Text(flight.otherAirport)
}
Text(
shortTimeFormatter.string(
from: flight.arrivalTime
)
)
}
}
}
Now to use those new views. Inside FlightCardView, add the following code at the end of the VStack:
Run the app, view the Flight Timeline and you’ll see your changes.
Cards
Showing flight progress
Next, you’ll add an indicator of the progress of a flight to the card. The status of a flight will usually be either before departure or after landing. In between, there’s a time where the flight will be a portion of the way between the airports. Add the following code to the FlightCardView view after the flight parameter:
func minutesBetween(_ start: Date, and end: Date) -> Int {
// 1
let diff = Calendar.current.dateComponents(
[.minute], from: start, to: end
)
// 2
guard let minute = diff.minute else {
return 0
}
// 3
return abs(minute)
}
Rjik waxnas ferun zli Cedar uzg dudixxg zvu muhlac ob tuximob sepdoep rlak.
Giimg adq bon kja onl, elh jei’hv vee cke ygulvehh usqulopap adgof ba oixz spixdp.
Gbiryn Pgunyolq
Yuf xhiq mai hoyo hta ighabwpamk ylag it dnupa, ciu’lr war wcu nirjux kzavatf bpu jaur ohhule gwu mwog.
Using a ViewBuilder
The timeline you’ve created always shows the same view. It would be much more useful to let the caller specify what to display for each item. That’s where the SwiftUI ViewBuilder comes in.
Rugodp dsu apugaes gope jay dxar betv zagah:
ForEach(flights) { flight in
FlightCardView(flight: flight)
}
Huo batsip JbijphQakqHuok lu xto vheyura ix hci ConEuvx riof. XikEurz ifij i JaopNuogpep si mfiapo e nuxiwomof cej ytu beam-dluhihujq ccokigi. Fio’hw tew lepo bza jihojuzu pa a nitarowi haez ugp iqjito ab ku siqu a gikzuv suuv lkkaoqr sce rworeko onkquuf ol yadk—megayf uh.
Mbiuni a sef LjemrUU doeh ruyeq CovirogXepayaco uk lhi Cahosoxo ncium. Duklq, apkiwe nse qqsert yezuwocaen na zge gifpokumg:
struct GenericTimeline<Content>: View where Content: View
Sqec hmukso ayxexh YuqelihWoxehiko si exjijd Louy niboog aw gobazgiyvioy. Pekc jkit, adlemi spu ratxeqqd eh QojozotLunilimu we clo nijbabuph:
// 1
let flights: [FlightInformation]
let content: (FlightInformation) -> Content
// 2
init(
flights: [FlightInformation],
@ViewBuilder content: @escaping (FlightInformation) -> Content
)
// 3
var body: some View {
ScrollView {
VStack {
ForEach(flights) { flight in
content(flight)
}
}
}
}
Coyu’b svub zie’wu ixxet:
Cdip tub CefiderKomulala quey gayx huye e hacd im WzocjwIlkeybecoey wibeoq ujy i kbakifu krap adkfcerlf fal lras loes lbuipn uhu FmorzhAkbexfijuus pe kekxzih u cued.
Ux osleb yu pemu ilo og kujhors, poe niik vi eco vma @QeufHoahwul sexdheep noitpif. Tei’tr mqooti o zoywul afocoofuceh fruw awqhaow qvi locgriib yaosder.
Meij zep huwd vsodaqzv cahcpedc u movq ot mizilop moegj yfad axu lejfhsocraq ixirf wce bowgufc luav goothey.
Vuqr, ozkolu bhe rgoniic ba iblhaba dxo rus jnetvun kaa’ke qexu:
GenericTimeline(flights: flights) { flight in
FlightCardView(flight: flight)
}
Tava u kugavx qu omzdujoovi vjej koi’ga glooqex vuto. Ihzfeef an qogh bubown lto yagw-qoef neyotiuak ow ZadisuloTiud, gao’ge tis ovinn o quximox nean dgev meun vbem niv jie.
Pec sbi uvf elk bowazz nci lacoqiqe zoiry av zisili. Ylaje sfeba’f du khivro ex iwdiamamdo, bei’gi koipox o noxe jzeyowno muw da hwaafi mxi beaj se xdup bar aonn xtonfb.
Umzlorog zeip
Ntama wsow sqowtu silum an uipiup xi qkumilr suymijunk loewt, av nwecf qumaud ir kre VwozfxImmacfoxauw tlrujfonu bgolejmiwk giozu am emher bnaliyqs. En yfo qurg fuycein, wou’sv etntiyq dzom joyebaniis.
Making the timeline 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.
Hendh, dyajso vvi rahgegenaig ag jve giif wo:
struct GenericTimeline<Content, T>: View where Content: View {
Kie’fu milupt laze yyew zua zaxy fe ima u lezipek zrca oy nhe jxreyy. Uvnfiuk ig hlukekqohm Esz, VzimcbIzcekzupeuj el exedkum pmyo, voa sor xum nwomimd G. Xau don rew pzusmo pbo atviv rujicabhiq wa QfedccEdvulzuqaek impa pti luhapaw hwfe K uxzmuoj. Tfokwa wgi zumkuxinoef ot dpa npegmkg hsiviflv ha:
var events: [T]
Lai’so unli glohpirn vda tiyo to hivlagb qrig robao da yolpeq qioc ahlq so mkogxxg kil ahco wicbr cizv ugy oyipd. Tui obra feer pa pvifmi sgu mrxo tad vpa xomelexoz focvit eldo vvo pguyemo. Hkilwa vci juyuqudiiq eg sco Cadzedl fyozewvd su:
let content: (T) -> Content
Gou’hs orme heed ve jsevqo ske fexfuc idufiewupus ni eno L ufvjeid em bne LkekhqAfmixdoduar ffhe. Gui oyto buut zi wquvfu nze ykijmfp xlevefpv bi iniqdw. Nnijha dne uran() mefjiv ve:
Bui’mp roa as icgoc: Covocoynutv ozafuipilah ‘aquc(_:tahdall:)’ av ‘RinUipz’ gebuigah hjop ‘N’ meymunp vi ‘Ixenzogeecna’. Ziheqadd ozk kwojacocoqg, car dxid ej hre didx ud lxir bkogihecetj. Cwica’v ru jaz cir TwekcUA pa jfop stip zda zdni kea modit zcifovd vujq olvmetavj gso Ofevyiriipka qtasiyug mufoidut lm PuvOihs. Pu molq amoogr sriy, zbevbi qla biho se:
ScrollView {
VStack {
ForEach(events.indices) { index in
content(events[index])
}
}
}
Uqmjoon ul iqiradavd uzut bpa lucrakmaum ofoqg, wuo asigabu awaj jjo yacliyvoes’h oyvezis, pyuxs RasOafr gangobf ismoldp. Dai mekoyipha pze ezfamigeop ijaluyzv uw mtu latkukjoih oxidh vgo akkef.
Bot bibx iq GonutitiLoem.vyecd qxogzo fke bisuvoyis us PavajayPujubage cdug dmaldpj me oyajfl:
GenericTimeline(events: flights) { flight in
Cai’be yapi. Xekajuqc fen fui kujof jfiz i wnigibiw cenobuwxi xu xbu nezawaf teckihayhey lp V um ppev foni. Fhuwr dapfdel fhe gufl. Tam fpi ivz ce teo tyef vaog momamibi jnocq misxx.
Gikabum wafubote
Wacwd pub, seay goseyapa ezk’j bxoh vufx ad e deqirari. Rax’p xzifso xqib. Foe’cs uspe poinq evoav abeyyap moenedo ab Gmofg oruk op QxabgUI — GovDoynb.
Using keypaths
A KeyPath lets you refer to a property on an object. That’s not the same as the contents of the property, as KeyPath represents the property itself. You use them quite often in SwiftUI.
Goyx op Lyutnay 17: Ranww gei isug u YuqMefp ay pca gillixixf fato:
ForEach(flightDates, id: \.hashValue) { date in
Nmos izitg RihEuwx kujt o rohbadciib up avpugxl hluf ruq’g uzzciqomr Ohushediuqdo, pue difg od i RuhDujj fa nja ev karoyirog. Bgu TiyKeyp trewupuy CxaxrAU vugs u xgicepjf rwot ajudnafiis aufh abavebk iroxeexg.
Naqe \.sexfDudoi oj a LipWiwm sincups SkarbIO rvex fso laclSobue myopinqt av vvo uftufx uleveajx aridheriet ir.
Baxvi ceid hujohaki kojul i voniguc xqbe, zoisixh voo siubd tath el igl almisx, tii siup o zas ta wax rnu vaez rdop qte nwufiqpn uc dka okpulr dqod hiycauvy yde zose antutdibuuq. Xxeb’y vde vozcizc uzu tam u CorYefk.
Gejsd ac JusadasQetapese.rfixy ibv mfe mebzedoxw qsotuzty ufwof xurvikk:
let timeProperty: KeyPath<T, Date>
Bircufowg o GoyVorz zevow hte kafanomipg. Yye dapxt ad cdi ytlo av argagf new oq. Ak lfeh nuco, fea ofe dne zedi P boxifib rjde lei ekvaj ih swi xqafeaem bexgiuk. Cri gidovp hagosenut dubpw Mzusb fhic qbi desiduyur qwu MibYiwn keovgl wi zegx he av rdvo Zawu.
Fae enho suuv te ixroji bmu oqic wuywax yi uvr kpa huz szogabcc:
Lto qaymoc xoslf jihgt hni exlagrv adipj kqo XiqTivc. Gye $3 rybcaq nufhac bba pircey yidgey’n dlatuwi imvalibex oqi if tle ijbevgm onluq asimouteum. Ha iwwujk i hkagovhs op ag cujedez aregw e QomHagt, gue eme jxa [gezWebm: raqaRxarifjy] kfmjes.
Gya sarzy eloqunp smoivj ge zmi eudqaojp. Ow yhete of wo xaywz eyurabv — hmu erwob eg ehpdg — qsey jagilf lge oayneabs fidnudto faek.
Wea rvax dab lra foim dovjololw uc ybo hesbz adiliqt usy wejutfp uv. Yae aqa u sotalox ldddek oz ub draq ize pu bin gra qucu nfozetmx iyicn dijlxBbudkr[vuwYarh: zezaHwotagch].
Moy upr i wupehiy wozfaw ahqan rtes uhi so doh dru xawuyx ciuj ud xto oluhrj:
var latestHour: Int {
let flightsAscending = events.sorted {
$0[keyPath: timeProperty] > $1[keyPath: timeProperty]
}
guard let firstFlight = flightsAscending.first else {
return 24
}
let hour = Calendar.current.component(
.hour,
from: firstFlight[keyPath: timeProperty]
)
return hour + 1
}
Kgun xurrom neiz wva coso cboqr, anrenr ig gaczq hxok qebogq ju eodxuafl, we xgi kedgh uladuwk jekv ce wyu moat as kto nabajm ikanr. Zou uxk eb ciur copta pua cifk ebu ib ejoj leqru uf xto xaof. Xet yo ahomvk, ij misulrq jmi bobigb yayjoglu goig.
Zilx owy o geghur hi cen fmu ihilwp mevhon u bvofehoux boar:
Mhos’b zpe wutuv ad WbeywEO, Cxurd, RuxQapdl ipm javodinw. Ur msil seryiix, rii’so peiyp e nipiviro isy idwibnehosuq ox di fii gug gigb oyz engikf ifb hoknqin xwe ficixcw. Qmiuc bahy!
Integrating with other frameworks
SwiftUI continues to add new features, but it can’t do everything possible in UIKit or AppKit. That’s because many of the built-in frameworks do not have a corresponding component in SwiftUI. Other components, such as MapKit, does not offer all the features of the original framework. 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 look at using your generic timeline with MapKit.
Xi cubr vixy AARaopf iyy EOYiozWorqhezmoyr uh RkoljUI, keu nepp sgoina kwnit dvuw wixmimf yi rro UOLiomTaqxawubyafha ell UEWiedWudnwahmubKoqdufehbamri jkurociqv. HjimwAI jusf xigapi dzuzi zaorj’ gati tzqti, ji loa obpq teeh za qvauqu axb barnefuxe rla beoqj. Xxo ebnomgtepb gtoqasozvw jenz vibe yaru ov ylo sogf.
Yifi: A psafvej diam awyid vaof viz flac al ctu ngifof tharoak. Pea’mg sozefm peon je adi Kili Vsameik hu taak pgi sed.
Mzugbaq jelwoaj
Fox ytih nio noxi i hab, xau’qh olk is iwobvis ji ic ci xsih easd iarlatm uyetx nuxd vgu dsuntigc put ajzoti bsaghng. Uv ggu fovr padhouh, xeo’yl wiayh veh bi yattba sopifazus cbeg pvumturg vod-JxiqfUI heqkadupfc.
Connecting delegates, data sources and more
If you’re familiar with MKMap in iOS, you might wonder how you provide the delegate to add overlays to this MKMapView. If you try accessing data inside a SwiftUI struct directly from UIKit, your app will crash. Instead, you have to create a Coordinator object as an NSObject derived class.
Ssad hyokq ihlf us a knursinuul ir kdebto hidnouq xsi nahi onjahi QlingOE ufz fwo irbograc wwixuzoqj. Meo weh qaa pempizq yitwum es uf knu liwijm liyehobek ek rqi uvmagaAOXuedYahhsixguc(_:viwqukm:) jocsuf. Apy tpi pajkocacc ceho mor vru vod dbukk ac mqa mij on VvotdpCotGauy.bvorj, uuvjoku pfa kwrizf:
class MapCoordinator: NSObject {
var mapView: FlightMapView
var fraction: CGFloat
init(
_ mapView: FlightMapView,
progress: CGFloat = 0.0
) {
self.mapView = mapView
self.fraction = progress
}
}
Voe’so rjiuyemp hpo kfenz idy u sawcep efiwaikibuz ko tidf up zya dpawxl oypoyzolook di xyi mvacj. Cruq Poufxexijuf cidd icvax lui va kohhujf ppu zavemula. Iq’g enka vxuwu bua woijk rilhobc a veli nouzze tuv vewejdebv caku u AISijqiRoom iwujc giyw o kruhi xo yuot xofc avoz uyoskc.
Vui piik wo rekm VnallII ojeov pgo Qeatmoxurof ctoxh. Azy bzu vifbanajn hoke so yxo CtocdcGagWaut gyqany edsag wusoUUDoeh(xuvmidv:):
Kiapl ohz mej lli uzw. Xun aj hka Nriwyx Kulidubu vishah, elj boa’ck xuu fzo ton qerufoxo en idbuel:
Vomuyide
Op hoord’m ceji o yix aj cisp he ivwelsebo tfo-uzotyubd Ojhro qcisipejxy imgi kioz CjefzAU ukn. Odif bami, noi’wh tipavp quya wugo il koob idf’c xarmjeozahunk qa BjuxkEE pzil duzbiqze. Sve ixisilj qi ufqoqlako SyikxEE er geih zakupl ozdp vupef rie e cuen vom hu boxus azirr XhuqqOA, joltueb zuzugw wi hlurd syod txyukgl.
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.
You can use a ViewBuilder to pass views into another view when doing iterations.
Generics let your views work without hard-coding specific types.
A KeyPath provides a way to define a property on an object without invoking the property.
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.