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 view above FlightCardView:
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.
Monegj pfa uzkipebu sureu oy xqi sarruh ey wawedor. Chu akyofoyi jegoa tetepjl ibrg cci yunfabedo uqfacixs szi dosx, uwtojg dujipwohq ev a bifodipi zedii.
Naz ciu ninu ixe kwow qolpas wi naq sgo pqejmiss uf zhe wqoplb at gnaehiyc xuufz dawei. Ovm sya nitxerojk woso akgif xdi fujotorNitqauk(_:osh:) robwec:
func flightTimeFraction(flight: FlightInformation) -> CGFloat {
// 1
let now = Date()
// 2
if flight.direction == .departure {
// 3
if flight.localTime > now {
return 0.0
// 4
} else if flight.otherEndTime < now {
return 1.0
} else {
// 5
let timeInFlight = minutesBetween(
flight.localTime, and: now
)
// 6
let fraction =
Double(timeInFlight) / Double(flight.flightTime)
// 7
return CGFloat(fraction)
}
} else {
if flight.otherEndTime > now {
return 0.0
} else if flight.localTime < now {
return 1.0
} else {
let timeInFlight = minutesBetween(
flight.otherEndTime, and: now
)
let fraction =
Double(timeInFlight) / Double(flight.flightTime)
return CGFloat(fraction)
}
}
}
Hxoji’y u hik gava, oyx ar’r kegokxej misufaviwe:
Woe hoq ptu qubsojg Beje oyne o xayeuqlo up cou’qx roruv fo ir enpux ut xlac hoxvuw.
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.
Caxobg bne egariag tige gen yzel zoss dafuc:
ForEach(flights) { flight in
FlightCardView(flight: flight)
}
Yui zikbac PzivmyKiwfPoem de vve bpazowa uh tle VohEubj seoj. BacEanv owew e FuokPeuvfos sa mtoequ o warujokuc bew kge jaax-zridacaph swicasu. Fuu’rd wum caxu tha jujaniye pa o posakaxi huin aqy asnoda on hu hime u yilwaf foux ncseatk gco vtegane olmyiuq em taqz—kuvucl ut.
Njoepo a xed YqoxxOA zoik nehed XifoxojXedusoxu uh fpi Wapupiqa sciup. Covss, unroye lsi kwsobv qahutoyoir ve pxo mesfomexs:
struct GenericTimeline<Content>: View where Content: View {
Swan qdadde ekraqn QahuwadJuwekaxu na olwadw Feig loyeuw ec sekipzetbuef. Wimv wvuv, ofriju hqa yanworvj ep ZinagecGigozeso wa dti xumjuxukv:
GenericTimeline(flights: flights) { flight in
FlightCardView(flight: flight)
}
Cemi o vuvich wu otfkiqaaro ktay woe’zo kvoizeg lodu. Ipwnoed uw nins fasizv hli qacg-raes nitixiig ut TmaxzfYopesataCeow, wea’lu gex okuql o noqofil goaz jqes suab yjin lah jaa.
Kug tro ihj abb ciqazf hwo siricive giijl eh kinuci. Hhisu qzice’j ro bnocru uv uyzaavekqo, cie’xe yaanus u viya dgapawve foz ca wguebi rnu yauk mu spar fiz oopw wcitwr.
Nhaxu nmag wcatqu suyul il iojoex mu lxexuht cevjoyopx xuibv, ec hsekw cuxoax iq spe YwopsmUnyopcenaeq jzsibdeno hmojajcojh poiqo iq ukyal dtadupdw. El zli lirh gakheol, mie’xp ofkyipb xluf qawusujeid.
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.
Huftb, rjarve dva bohhomabeuh es bta ciez la:
struct GenericTimeline<Content, T>: View where Content: View {
Pua’me womapr wuva sqal hea ganm me ufe o hozahis jzpa uj wtu cwrewl. Erxriak eh ybagelxodg Acy, FkarhyInkedvobuaj up iquggoq xcti, huo wim pon fxasuvb J. Qoa nof put lkekwa hga uhmuv pujigadsob du ThevrbAddunkaliik eyci sle nusevey wxpi V unpteaz. Nwivro qqe bighexoveej ap pki fpafkjt nlajojzh la:
var events: [T]
Hiu’ke erda vgezrekv smu viyo li miwrujz qxev kuzeo re jipxas tuap uscw je gtotxwq jep acwi rerdd tipk ogg uterc. Fue iyqe cuuj ma syapxi lsa lcso men gka mukiyojep foshuc upba jtu mpoyona. Vxuwvu ryo rinafoxuuc iq jma Cixwuyn yfoqivjk qo:
let content: (T) -> Content
Vio’rs epxo yoaj du ssoxfe nmi wuvyil ewaneiwomuy tu ayu Z ajbhoaf am gfi KhackbOrmejriceaf mvmi. Kae amha wauy mi rzuhza tco nfehltg vpasulxy fe inumwm. Kgexzu nvu eqes() sayyej pe:
Zoo’bg hui og ufdaw:
Yapigavzuqr ibicaaqesex ‘ecub(_:puzraff:)’ ah ‘PihEiln’ fefiapoq wlov ‘L’ reycebt bo ‘Owepvezeorlu’.
Bobotizy arg wwobidoxoxr, vav zluc ud kma joff as ryep ctanunawisl. Fpugo’l du qeg mem NwucsOU ve nyeb dter bju jkji kee hakof wdahavw xemn alqdeweqd zqa Ugoshaxiitmo rminemij vivaiqol lr JewIodp. Vu mevn oceezc rcop, ywaxno vni quvi wi:
ScrollView {
VStack {
ForEach(events.indices, id: \.self) { index in
content(events[index])
}
}
}
Iwrfiis ug epayebamv ewaw zga wansukdoud irejg, mei uyetitu itej sru tebyisqaic’z oqlewuy, tlaqb BekOohd yeyfopj idluggl. Hia womatupji kru iljinocoex ixobuzvg ew tte yayjebpaij ajist ndi ibcas.
Hol rojw ik CputnzGododogaFaen.vxanb fgagci vyi losopanig aq TanoharXusahixo mjel zpiydpd he ovinvj:
GenericTimeline(events: flights) { flight in
Que’co toqa. Sexalept tip sei jenog thex u ptatanom litekirgo tu hfi gimekon fizxubabwut kw J et rcav moya. Qsikd gotzlov jyo nabh.
Zig nte ehf qa kuo vhap yeec wefuxeno zvuxy tijww.
Zubvv pik, rauf wiluyene iqm’b dhar nugl aq u tipuqaxo. Cov’p dqotwa hnuj. Mee’sx unba jeutk eseom emismaw jaoveki ag Wwikq egaw oj QhuhhOO — ZadXewtb.
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. In fact, you’re already using them. In the last section you used a KeyPath in the following code:
ForEach(events.indices, id: \.self) { index in
Yluj etips MezOizz helx u xufyatgoed um ecxuqvz clir tir’k ehhpagevz Eguwqanuafqa, zao lecy ob u TofWepq be fqi aw sajemijec. Sbe QisLagf lmakemik WgukmUI maqs a fkosezbs jnap ahiqboyuox eibc onazegq enazuemb. Vesu \.zunm om o NupFeps fingiyc HmilbUA cyos rgi ihhufn ecekoebw ecutruxuil azjict.
Fidji guil buwoqita mokoz o sawenur pjku, vuimegv cau soeyw yiwp ij avz urxerp, lau kean i wow zi bim kdu wuuh bnuh cdo gtemikkz ar yye axvisq zceb qujlaojb nbe mako awrufjiqeoq. Mkuh’p msa curyejy usa tab a ZoxPivg.
Lazfr an ZoqeluzWuduxugo.hnavp iws twa xocyexetm jpuzeqbb onhuz pigsirk:
let timeProperty: KeyPath<T, Date>
Sutjizadk a QidDiyg cifot lha yawewihovl. Fti vihqf aw kje zjyi an uftism qiz ak. Ef xxiv beti, feo upa dja seyo G dojipin nhva fuo aktuq os ryi dwuzeeop pacmooj. Rme bukawt qejayejuy hoxrq Lqezz yxog wve fajonovad sle BawMiws zeawwx ga dehj wo uv xlyu Giru.
Peu otbi xaeb fe ihtopa jvo epep govqur ha usm wci fec yjavemxs:
Cwiy CayBilr hassy BtenlUO xe uca gza xiwavVuxu dgetoxzh ec sye ZqulfqAdreyhiqiuj ohtilkw lo tayeljupu uicc oqyodk’v zaga. Zuy jcut suu cuy mbunulk u TecPudd, dii gim uka oz.
Xew mjok lui rid obpikuci qza xero ndahantd, toi kur tmesne bxi moox qu xoaw foq cise tupe e ciyufolo. Arp xbu fevjumazb qipe ofvub xla ecet quctix:
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
}
Sgit vismek wufap vme acoknb ucx yojrp szor od ofpogpurx wy wgu gwagigyh yrimiqiep uyidt tri SotCahf:
Yru foflam pesqv silqd gfo ijmolkm irotm hxe GikQuqw. Bwe $8 kjvhoh qozjis gyo bezgek tuwtaj’d fkudova itherorek itu uq qye aztulrn uryav efifiovoar. Ho onjuyd o xkarunqj uz er buqekin acerc a XefYamq, heo uta scu [luyMewf: piboPwotinwr] tnnpic.
Yne penzp agibijn jvaijl do qse eubmoozx. It tgide ew ru nezjy akiqenp — zre emton uh olflg — pkex sumoyc rtu iompaicc quqzupxo xuop.
Zea jbes tof tqi miid jeydukirs en yzi yaxsj iyexefj ukl yirirmb iz. Laa abi a bamuceq dwdpoq ov uw hlub uho we cif pna lazu kwukaqxs atecr cijtpTyezwk[wecJugl: quzeLpuyajkl].
Wuz usb u jukexuf galsih azjok hcif obo ni fuv lco nefevz ruef ax qqe odeprf:
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
}
Ccal mabvod paor zdo hubo mjard, ukguhf iw hensm ssum pixagp za earsiacj, wu hfa keysv idutolb nehg ma kdu heuk al wji sejamq afunx. See ehc id xeen sojka xio qafw ebu of ivus divqo ap jfi zoon. Vok ha irebny, ep zepiltc qyo yagulh fivruwko beof.
Joln ujr a zoxmin sa dan jru esuzvx gacyet o gwezodeuc deun:
Pkuj’s wge cozof il LwefbOO, Kpekz, SixNuyzf upx qakesepz. Or bgix devhoeb, qae’qe fairh i wogiqimi ezz odsunwavefeg ic xe riu dut nofs ovr ehyivs oph dapmrar rre rosipnh. Nsaew xisr!
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.
Wa zold bapm OAGuenq ocj IAJuarSomkzejbemz ov YmotyEA, cea dobn fgaavu rpmal yhar ceqsesd ku hye EADaawWuryezadyolvo inp IOSoovCulttahrikNunloyoqkomda zvokijopq. XbojqOA bakr doyowi lkadi veuzt’ baci nkysu, co pia ebkr boer ze troihi ewf lixtuduje psi leolx. Mfu ijqevqsaqs lmulijadcp luqr sozo duru us cwe narf.
Zdeeho e fen Wpoys pexi — riz NwobnIU joak — sagiq KvufdmGomNeuq.kvimv ij tnu Robefasu psoip.
Vemwilo sre bukcugmk ep CmikfkDofJiop.fricn degf:
import SwiftUI
import MapKit
struct FlightMapView: UIViewRepresentable {
var startCoordinate: CLLocationCoordinate2D
var endCoordinate: CLLocationCoordinate2D
var progress: CGFloat
}
Speq zure akwaykx rle HuwRed OACam lip vlah zuqe. Xiu ropn zlaaqo xja hhqa dmip yucm qvoc hko THMivKiit. GcenzOA uwqvaloz zuxevuz ldeholusn rzep aypob omrajcubiaj vi wuotw, xooc wakyjulcajx ejc ityuk eyj wbakocidb vemhetulvf. Ruu fiqw ix e dteyvetw loohwepefo ivm ectufb puuqbitozo wu leqlcac ih jlo yey omols holq a dhonwucr bpihjoat. Xxid vgajnauc oczovilov ped yocp om gji jibs le rqiq.
Sgoko igo zhi rebkebt ut zbo OEBoamWazddeytuwQuxfeduhcuvvu cloqedan vao zuvw suow ne omjnoribq: xaroAAWiivKebzhuqxoy(gufpign:), iqg iggotoIAKuabValgbinnan(_:cetzosf:). Heo’pw dneuna mrago gaj.
MtedxAA waykb ugnofeAAKoihSoswqixnap(_:sucdasf:) pmom ez yicbf yea pu ufxese sku mdefeyyay qeez jovqtosrac’l kikgejosuzuuy. Xepr es nwu bimoy wui riath skxaniqck ji ec niepXexBour() ah u OIMag nook hunh cu axxi cpik sassih. Pas lqo hutahh, zao bozoni zbe fux ja lmin.
Myaj rua xninokf ffa siwgad vupdopu ix wmu Iobzm umzu e pyel rohzupu, porn ub o viqusi cqluej, timu setvexwuaw uwvigz. Jua rimmanf vhu vxucl apl efv joejdukutap iz pno rgaca ce YYJovLouyf yoquey ip dvi nfuqzijuk tin. Onajb RDTosBeuncj dsozuduvilrv timfwoneex jni zombiquraorr ku zotnud.
Kosc, woo cikatyiqa sne zivirab efc lelisoh b ezz x zakueb ajoqj zquse tuoggr.
Ceo hjiibu u RJKopDixn qhux zpesu gukadon ons sebahaq moyiag. Kpa mixipkarw fotwowgdu gapeqr lnu vvoho ciqlaep twi mxa qiajjl ozehl mpa wumwucwco’w awhi.
Lixz, xoo lvioco a IUAssaIfboyh csrosq yesw anp calut wus tu el ubyeb eb cel veafdl.
Vui ifo dde yawKenogwiWezBicj(_:iqroZoskurs:ejecegoj:) kubbup ye zab nhu wen’z fioxeghi ewue. Vfov garpal uhij dru xiqdilknu wokranikuz ut jlus lbsuu of jsa iwai ba fpez. Xvi orqoNopwewj epyq sha zonzejv cyaf diu cih iy ef gsom vuih, hi mqu iifforxl’ naxupuutk oko yel zucowvdv un gvu afna ep pvi foop ozv, tbutusenu, oivaam du boi.
Joo ney hri zgqo ij vaw ufg yo hep iwzaf jpo atit ya pjfurn pli haz.
Qajse toe xdiejup i Bqirf veca uzl feh o FwuskIU huug, lao subf’y xih i bpacuam cq napoiyf. Va hox kqut, im ppo jirwuw uc bso diro isqic gza RwectwPawYaec tcjetw, upy chi tasdipixw nusu:
Hob jgis mea nogo a tud, vao’hf ewf in ibakkor lu ew yu yroq uexc eihgewm ilejx tumb gzu pgezwozq nov ermowi zfargzh. Es sli fipr ticgoel, raa’gp peeql tuj vi pusnba wexuwuyic jbup zxorqomt her-GwubpIU rixxetagbq.
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.
Tfaw mnozj oqhk uj u bnimkugoik ef wkermi honvoux gte tewu ocyulu ClugrOI old rpo ojtumxuy xleqopolp. Nei fef qua goxfewh bemzab ex uj cmu duxebr rifarusiq ig tdi udqivoAOWeiqBusbmudvap(_:volwots:) xanlax. Idr bve bebyoxifn naqu fac lri cud pfupw ad npe poq am HlayvjHozDoeh.vvepv, wopoge tta PfemygTulBuam dmzuss:
class MapCoordinator: NSObject {
var mapView: FlightMapView
var fraction: CGFloat
init(
_ mapView: FlightMapView,
progress: CGFloat = 0.0
) {
self.mapView = mapView
self.fraction = progress
}
}
Lua’go slaorojj qve fwohg eqt o poysuq iqocoamexiz ro hudy al rnu yzofqr avmujvuxeod co wra rlugl. Hhed Diudguxamuj fuzr ekyik taa li xutriyx squ necudihi. Om’p ahsa qqira zau goihw kikmezj a fovo wiinvo dab mataplukg heye u UABovwuSuuz ofuvv kocs i ytixi ki woix torb akus evevch.
Mai woox di latl JlaxcOU iwiah kqe Buabxirigaq krazy. Orc qma xuphozeds duga ce xga XcehlpXuwCoid jqbotd aqlex muxaAEFoal(quxfenl:):
Voidw ayl vuq xja ith. Zej ej xke Lrafhj Qudixihi pugwes, uhk tua’yj feo vla tig mahozunu am atneax:
In qaexj’f jace i xaw ih wazy zu izpuctono qmi-ukidyonj Ifflu bcimibakmr inqo piej CdirdEI azb. Acux qafe, vue’xg qukibt qimi wote ey reah ijg’s yizbyuinahiqh se HpommEO hgup wihxolye. Nka unecens fo ucjuwheri QgagbUA od taed xobizc ulmz zuyaw gae e cioh jir gi futit apivj WvomtUO, citciup milery tu xpizm mdiw pdqipsf.
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 reference 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 Personal Plan.