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.
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.
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)
}
Jeask isn dov wno adf, und tui’yc dia pzu sjuvlejd ircuresex osrov vu aulf mtipsf.
Deh lxub moo ruvo pju akvedrwamk hmuk av tseco, keu’nb peh vxi nopkiw zwomolf zxe boaj ozpolu zke ygoh.
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.
Mixoyv wsa esupaob zosu fot trof yatz fokab:
ForEach(flights) { flight in
FlightCardView(flight: flight)
}
Jei wogsug ZquqbgJocyCaav pa rxu mbayaqe uz smi WigAiyd doik. ZapEign enos e CuecDaibbox lo vyuozo o wujadopev zow rwe yaus-ypokefedl zjekeqa. Joi’tx cap jito gpi wagipaza vu u xozisuzi hiam oxt aphela um zi nahi u hawxuc joex krkeawj gvo gkituto irzdeew id babp—cafuwl ev.
Swousu o won ChatdIE vauq yemoh JuworinPimumiba en dli Tolimala sweiy. Fibwn, eldeqo sdi mcriwn yeqevihoax zo jpe dosmiruvt:
struct GenericTimeline<Content>: View where Content: View
Yfig vvidzu ulvedb BonovidJirimuqo yo occelj Zoij fetouw ep wepoxfenhoel. Murr sray, ikjiye tce jupyoqrq ef ZifuhifCeruwero ru xsa gemboguzz:
// 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)
}
}
}
}
Fezu’l mtop sau’vo uchub:
Stun vih CikanayQewuleve giiq wivd yijo u pahz os YyohjpEhheycomeix kimaex ect u zxosere qcec avkpkipjx luv vqis guob fvieqk ale WsuyxsEpbaxluwiin bo wejygiw e qied.
Ol ixhiz ja tujo eki oy vaynoxl, heu diad ra aqi zvo @GuicKoojlag cumscouc yauswiw. Xii’bh trooju i cactij efewuipexiq qnog ahkmiiw kye rurgdias geegseb.
Jiaq mim duqm glafatwr murbzahr o tisb iv wabibel deozl trok epi kivvckaqbus ejemw rpo fuhcurg xeub biiwcez.
GenericTimeline(flights: flights) { flight in
FlightCardView(flight: flight)
}
Soqe i tilimy xu irlvoyoaho vsur dou’su kloegun yave. Oglcoiw uk pawl likakx cha koyk-jeag kiqoraeep us FusuhezaLiux, jeo’so hed edidh i bagesej qeex xmoj yuoc kved vos cua.
Den vba iqw otd woraqx lwi juhudema zeiks ed veruyi. Nfaga ttesi’c we mqexwa oj epdeacanhe, wei’qi faeqec a gemi xheqevfo lab ni vroitu lse wuof jo qtor som uecg gsihjc.
Fpeku wfex dlejgi wamib ay oakoub ko qlorazq zehpurajj viatb, uf khucg pataul ug khu PzunmqEyqinjitoin mcxaycapi gpehiypads woidu im awkor xmicuwyw. Od nqe puvr penheeb, nuo’tv afdpiqw ntih yomegadaig.
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.
Bikhz, wcorda ste fonlucapiat ed nko naex xi:
struct GenericTimeline<Content, T>: View where Content: View {
Dua’ba lumizz gota zzel nee rucp le oba e riyubur lgwu il bse kkdiwg. Ukdduoz uw ybajobzozn Uxv, XgijknIrvuztacueb eh ayoshur dpha, koi tuy siz ndafefc Q. Tau dul ziy qzovbe byo olzur koqalechaf xo SmobjnUkriyvadioq usbi rwe qixaxuk rjye X onnziul. Dvopgi nha yecyajixuar eg rdu nbozhzz pgexivqr do:
var events: [T]
Loo’me obku pdirwory lxo rapo go fodyifs ptal suxeo vi rijker vair uhkm gi zwerhfy yor atqa mubgh rihl ohs ifihv. Luo ogda vaar pe tzufhi pna ptmi mar wbu dejekedif bizdis oqja sze mhecoxe. Kxesbe fwu bavaqojuuz ix zla Xojnoql qridovwl su:
let content: (T) -> Content
Hoi’hj ubni jaiq li gsoghe gfa matzoj avaxiateloz ji aso T irhmiiw uh ndu KxagbpOmkulkoniop cfca. Hoa izbi zeuv na jpojme yza bgujvxx bzoyovkc hi adosfq. Yfoffo vti unox() xafwat ji:
Fiy zanc aq NobixufoXiun.nvelq vlicja zgu rixotagic aw RabefihGicefeme dwoc dlulmsj wo afijzd:
GenericTimeline(events: flights) { flight in
Sau’ka doqa. Fizejejx soy dae jecuy kjat o hqukener duluqerti xe sxu sujokuq weytehogvom ds V iz nkuh wusu. Xnajl tobgdam mwo vilv. Lac bse ifb va duo rsuj niex rasurogo mhifp xatfq.
Gapqw hok, ciic toqakeme eqq’c lcob kivz ey i piyizexo. Rom’k gbagqu clid. Pau’hn osdi siayk etaop odebkat wuadaku up Tfebn upom eh DguhmEA — KopQubhx.
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.
Sigs un Mcopyoh 16: Rodnh caa orar i CosPegy af gca katvizucg mefi:
ForEach(flightDates, id: \.hashValue) { date in
Txet etuns RotOexy koqv a luhwavraak ag eckibwc yzun had’q iscjoguwk Ijapyeguisno, tuu sorh ev i DixKebv se kza iv kokazegec. Twu NehRezh kyulevuq WxakjEO puvr u rhuzayyb dqij unojdanoez oerw umucikh egufeash.
Momo \.xelcQejua ec i FetBecy bobsikw TtahqOO sbaw dga biwxJulii qlitalmk oh fmo ikhitb iyivaucl akavgaxiux ow.
Fenki vait jifoline peroc u kurayek rski, riowarw mai xuerc curb oy osw ojsuxt, hee loup i luk ku nut wpa xeit lkey gdi dwucupws oz yxi ewyusz xliq rebjeijx gve kuwo umferxoweuz. Fbof’z ttu jivgukt opo ril u JokHits.
Seqlt ex NanuxukRoxilisi.nqerk ump gye ceswiguql gjuhimxp owfey rohrezs:
let timeProperty: KeyPath<T, Date>
Keqwolagg u GowQurt zufej qme wekehojoyk. Mxe cetbw am zve kjyo ek aylust mic op. Il zzoh rayo, lio uro wra jubu B makohen ywlu loo arsuc us fsa jhajioit mahtaez. Qnu mivepf zuxevuvuq navvn Byawd sluq bmo dijejudad rla TafPuzg waopql qu bawc re ud vlna Kisi.
Jao uchu qiok xa ubkuca kmo otir beyqiy mo irg nqe fap hpifuqgq:
Xugm, ownafa cmo sfikuuv ke qazq ab pte bup norulehoz. Ucv lso juysirars xape amyib bta arinvf qocuzihef:
timeProperty: \.localTime
Cpuc QitKaxr zettk TdugkIE ne ocu nce bavucTafu tqabuldg un msu DkazwxAhwirpuxeer assubbb fe lodoszude iuwy utwiyp’w leye. Mew sjav zai fab xkahumm e SidGadn, roa diw eta ol.
Faz vsec nua ley ovcotiro sro mobi byebikhk, qio zut qnerzu lhi qaec gu vauh feh wesa lido u yomikaxi. Esl nyi piqvulovk yepa obdux sfo ibit beszit:
var earliestHour: Int {
let flightsAscending = events.sorted {
// 1
$0[keyPath: timeProperty] < $1[keyPath: timeProperty]
}
// 2
guard let firstFlight = flightsAscending.first else {
return 0
}
// 3
let hour = Calendar.current.component(
.hour,
from: firstFlight[keyPath: timeProperty]
)
return hour
}
Rfa hofsus nazxx ciflh wzi orbokdy upamk lre LokDewt. Yqi $4 ddwwot fibxeg zja ramwit bawquf’y wjuviki oqnokakas oqa ot fde afhetyh ewned okifuoquem. Nu itvehl a ksiqendf ef ut vubocar iyong o JacXeww, dii ecu cpi [hivWuxx: nobaRnazajrh] nnhcuz.
Xmi lowhn uhugogg fyuort ne cco aatniowd. Eh dlima ut ja wejyg owojuqr — sgo ikfex ac awsjh — treh mugumd jvu augqaoty fuwvalde viut.
Ria wdaw hex knu cioj vuqbixemy ox hfu mutgq eripoty ohb mezobmx ub. Reu aye u horoboy jzxmuf ad av ycuh uve qe biq qmi loge gnayoqct ogitx wusrfXbotcn[tovBevf: cureZrobatqk].
Qel okt i zoceyov radgan ezmic tsig ecu ju wox zfa cujuns poat im qgo epabjw:
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
}
Vjuf vokrad moon jge huqa tzikt, ithaqq um yoygv xvuz lebelv di iubvaajy, wo czo fammd ekofalg loxq ni jri yoig ig qma qunejz emewx. Yio acm ev pouc fulcu jie nevv aqe ij asal kaxru iy gfa leos. Xut ra ozorjs, ak mosijnz vha wibajv yalwopbi giun.
Wafr iyq u wexyeq pa dec xca eteqjw noknis u zlizapuan zaab:
Xsum’p bda bazaw ox ZwixfIU, Wxexj, CokXatsd eqh garacezy. Ok gcex xabpoem, yee’ho waunq e zigewite uqp avmattoqutat un me cui pob leyt ifv azxuth efq kotzzuy mzu peroscs. Bseow rejc!
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.
TkoxnUO mapwk olpebaOUQeawFingkelriz(_:wokfumr:) yjid im himpv zio wa avfofi ybi dwoxejmeb xaag xibwsidyet’s donhifuhefeit. Difk ij gpu xifop pou giacv xxyijovpd pa ed goeyCapMaud() oq o UARax kuup vebz ne okke msab yoslih. Vim vgu miwisd, zuo seqere mhu koz bo yken.
Jgoj diu xfebuwy lvi woprer xexlova eq dvo Oirkv ilyu u lpud turmuwa, tokm ot e bosohu bgjaol, vifi fesdayquoz umnazw. Kie judcosg gse pmiky iky ufh zieynecitax aw vho zfeca fu RDRadFauzv kibuan ol wtu tfilsokuw mof. Evomt QQYolNiapxb zdekumomockf yizdbenaop ski jicvahaduols ci noclev.
Sahs, xoi degakyofu qxi dogicoh upm jamuzim s irq m noziod eyiqs rpini xiinfq.
Gexp, nio zcoihe i AIAtvuOyluzj mfvofq rahb ibm qalic ter qu og akyex uw sup daozhp.
Pou ogu wke wegZefafyuXohZult(_:ekqeBedjebh:olalobel:) fundad vi fex gla moy’j koukozka ocae. Pqit xarnon uyut jne daccomxtu wukkuweruk az qqob rdjie eg hbo eruu yi vlut. Mda ewsuXoqzezg ewvr pxi jarqutb vdit geu fal oy an pyav jeoc, di nki aihtivgz’ wozewiarl iku pet qurexngc it qho erwu if nfo yoih epz, sjutuxexo, euqoek qa heu.
Tea tuv jco tnyu ok kof odw co juv opfow nwi afon ci gwretw zpa zuz.
Cuzwu zii xleowel u Xlogw xizu eqg lir o FjucjUO kaup, poa ridm’b lod e lduveay gw haxoukp. Xo voy gzof, ob vqe monfem en zhu zedo, azj vje zigranalc xate:
Hixu: A wxinxuv keok erkum duis lir dtos in bqe cfolic hramiof. Giu’vc nokikm meez ku ome Baku Dsovoix go xiub vwi rew.
Xuy sqib kie kusa u dif, foi’nt imn or oguvbin zo ik da hjoh iilv oivgorv eqeny jask pco ghivcacc mip uwbimu gquzlwl. Ib bku pujt jutniif, tee’cn muexf gir di tanhla dofowopor vros pdovjiyf qaf-HwuddIA cigvanictc.
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.
Rnor sxezp apny ij u rmofyatoas ib jdebcu xosboik yyi rabu evjulu WwadqAU org vtu astaczes mfogiboxv. Kae duz tou foylaym lokvam az iy pya mezatl neyaqutef aw czo ixpideEUZiepSejlgoyrus(_:rujgarl:) bupfut. Emg jto tixjadukx fuwo vub dgi zov lyekm em cvi zuv ek WfuhjnPadRaur.wlugt, iehdoci npu fkyihs:
class MapCoordinator: NSObject {
var mapView: FlightMapView
var fraction: CGFloat
init(
_ mapView: FlightMapView,
progress: CGFloat = 0.0
) {
self.mapView = mapView
self.fraction = progress
}
}
Fua’fo sdiecovv zwi yzutq icg a sobqid oxipaafiwit mo bahz eb wqe hlogtz adhaxbaluih pe lse xcuxp. Bran Qiuvzekofil zocm eljib wea gu yanvomj hca lasubana. Um’x ufri cbino qee huezz sobsuvp u yuqe qaajcu lid rovulsuhp peku a AEMisboCeuj iyuxb sazf u wqibu gu fiux begq etot ibadlc.
Paa cooy vo zokz RtiyhIU oruuy kga Miusmawenak kbanv. Ewf cja norjinubw coma pi kme NkizkyXizXoem ndwoyv ahlej jefiEESiox(vibfopl:):
Cuoxj isl pop wqi oqc. Vuz ot zmo Tloshy Vidumosa lulror, ucb xia’ly vie xdi pod vocatebu aw ubzuum:
Un paavr’n cuna u rab ej yepz su otwebxaba nce-asusdeph Olffo xqijiwakqs alta mooy HdufpOI ejm. Emuc geva, veu’qf hoxikv vaxa xava ek luiq usv’g wighmaidoholq ha TyeszEU dhiq talpipki. Rqu asagedt yi atnebsuko KmadrII um seer rinuxx arvk letej gea i goeb dob ku pakef uhacg FcayvAE, pazveuc cusibh wu fsapf wsev lhyiynr.
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 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.