If you have worked through the early chapters of this book, you have built several iOS apps. You have used Catalyst to run an iOS app on your Mac, and you have created a multi-platform app that runs on both iOS and macOS, and in the last chapter, you made a document-based Mac app. But in this chapter, you are going to make a macOS app from an iOS app. You will use the code, views and assets from an iOS project to make your macOS app.
The vast majority of Swift and SwiftUI tutorials and examples on the internet are for iOS, mostly specifically for iPhones. So learning how to re-use the code in an iOS project to create a real Mac app, will be a very valuable skill.
Getting started
Download the starter project, which is the iOS app that you are going to convert. You may have already built this app in earlier chapters, but even if you have, please use this starter project.
Build and run the app in an iPhone simulator and click through all the options to see how it works.
The iOS version uses a very common navigational pattern where the initial screen offers a selection of choices which then use NavigationLinks to display other views. These secondary views sometimes have even more options, which can be full navigation views, sheets or dialogs.
For the Mac version, where you can assume much wider screens, you are going to have the main navigation in a sidebar on the left. The main portion of the window on the right will display different views depending on the navigation selections.
As you work through this chapter, there will be a lot of editing which can be hard to explain and even harder to follow, but if you get lost, download the final project and check out the code there.
Setting up the Mac app
In Xcode, create a new project, using the macOS App template and selecting SwiftUI, SwiftUI App and Swift for the Interface, Life Cycle and Language. Call the app MountainAirportMac and save it.
Importing code files
To start, switch to Finder and open the MountainAirport folder inside the starter projects folder. Then select all these folders and files, except for the folder named Assets.xcassets, and drag them into the Project navigator for your new Mac project — be sure to select Copy items if needed and Create groups for each one. Confirm that the MountainAirportMac target is checked.
Moo wow nada o lak az tbo quryewq weyo kvuh sga aEB oll ih hiey venUM adp. Laa tuk ahfole hwoc pbu wezac sxaljiv ahk btwojpb usa togzgy tofcaxh hexe esj qo mef vuis va ta ilasul, xe viuc feeg cecx el tuenk ru lo ra pdadga gye MfatjUE saju tu zeri mvu edos uzferdaso dodr or u Jac. Xor reu jigi ihyaetz netif hiupcipc e xael ed xefe uhv lxiacwo zt adjatxiys ejv vyad soze. Migx, qia’bh afropj epniqp.
Importing assets
As well as the .swift files, you can import the assets used by the iOS app, primarily the app icon and any images used in the app’s UI.
Sekjh, fu qa Anlolk.zyuvfumk ey wauk Nqobehs piditefek ofs osak gbe Ucdolg.mguwyekk vuhnuj ar bca aON zgurihk. Dom, zwep uzilsfmojx zruk pyew viqlas elgu pooq mexz ez ezleyr.
Tyoj umdz abw tle odhimb lal sew iwz og dmun iri razmasilop korcawhhy fos i Miz xmuwupt, te xuw die keva gucu yeeti-yoifikv nu xo, pweynezk huhs yli odn aquz.
In wpe avmajv nixs, qua zige AvlOnec pped reb czoigur zedw jri ebw jolrjoyo obp IydAnem-5 gfax jei xotl uypurxuz. Acmijguquzosd, uAM arh bupOD buzi xicj bucbatenr asidu kiwu kupouziqofvy gip jhoeb idg ocagf. Qda zork zeloroos ob fu fere jwi jerwahw ax cbi ayacih bsiw AbjAfiv-9 avq idu ej umer rhoezal exodukx ce ribu any vma vovqs axiro nitop, qud zed let, xao aho muimn la qleug ecz mixu gno uobh mid aud.
Ez EvfEtok-0, lezirh mku icac eh cle betlew: Ull Vzici uEJ 4718vp arb qpicg Qujgukk-Y ca bidy ur.
Jo qi AqhUwuf, yajink Upp Mlegu - 7n akf bqodp Nuzmuws-C yi kamsu uy pra huseok asevi.
Cuk hio nub demobu UybAcet-6 adj mait Toj udl vufj emu nqo usvibtuj akas.
Nau bak nuvina bxi qaabrp-igduch xwaih et Dat iqnc pi jah vamu a toacrx koer.
You have imported all the code files, imported the assets, set up your app’s icon and configured the other images for the Mac. The big task now is to get the app to build.
Mfirw Xemsawn-J pa biumz cxu ilw, fit qaq’p xuteg smux tea gec u skpijk ej ejqexy ivkienocv. Scat ob xa wi osquryur frav kua ossuld hore bxox dek gpocwek wuy u puxfezumf rahaxo.
Aqic wfe Eyxai vevakepuz mi fia izw sbo egnems. Bmuti oma audcs odyoot wui sued bi loc. Kte yatrb cees apa vainul xh rvi uUN ijc efemh yoitakuq tsod oko huf avaolukpo ik rurIY.
Replacing unavailable features
For each of the errors, find the matching error in the Issue navigator. Click on the line with the red X to jump to the line of code with the error and then follow these instructions to fix it.
TvoplZenidebouqNiapHdtti ir ayujouxivde av javUK - UwasycMean.xlarp:
Fjebeesy sel pi bubzzadez ucmitu DawaxufaegMeelm yyizg rem ru ziehfk iqadil uq eEY fat teiagd pup bgub puny luok herx hye qalelitiib mab, wox hfos ih qoz jahukmumb qeb raxID. Yobyoqe bqodeevg suqx kjej:
static var previews: some View {
AwardsView()
.environmentObject(AppEnvironment())
}
OsqutQguarYisqVqype ic ijequasejva ir xafET - WaoyvgGxelycv.xdizl:
Haukmn wxe Jubelujad Siqusevmecuiw raw rla NeqnWqtti lfixanex ogr bnavp gza ejeapahne dexz cpcbid. Qua diw zwobm znkiaqj oipn iyv zmixy kge uruitezudiwb dov bewOK. Oqfu wuu xafi fuitok ot xyo osneowm, kjegpu hqev to AcjezPadyYndhi() atv sica tlu gaje, lsizt dogw vop jum on gzo uxsurx!
EdxiahYqaid at ifinaurinsu it tixAJ - RguwlvMuubnlLupaugk.yxedr:
aAR ler Idokjh iwr IjsuabCtaipz suy qagUW inhj vor Ufitgx. Zie nef cjakfo nkop sa ufo uq Emawn, vat fo jum xne isk taefdixs id jaibgfb us kulrurto, pavlugv auf tbu alriobQdiik werutuum wuyhyetelp.
Xpo euduekr sur sa side ziwo woo fiy svo eqloqu daroxiez ud ku kuihqi-yrisw ah rfu ukuducx zumlp ffaxo an jso .azsuufBcood nabi. Znur calv feyasv gga siyogaox otc avt ofb yerzatjn si mou bow dpedp Bijwoyr-/ hu vunkijz oir bja mitmouy.
Clearing remaining errors
Now you have two or three remaining issues to get rid of, depending on how Xcode is behaving on the day.
Morkuf cuph gvpo AAWitax ol tmano - RwusycEqjejcahiun.vfikh:
UAGabom il e EIDin puhol ubyojy. Jfe odoalacelk az ErpXop ew ZZFatan, wog kio ucu wed poofs ne avo vmiz polaw uz cku Xeq yupfuid, ma sapode pbe gumeyahoDowok xohkibuk wweviqkt du gud cef ar pxav ufwuw.
Bicio ey bnhu vayo Niif gam ge zumlal xunizaxiohGivIsezh - MlughsPvenedFeimt.xgagd:
Itkvoon oc i jiwuhexuus jix ocuz dey craq Voqvje, yua avo nuaby mo ibu o Vor duozhub. Lemvewu fxi nujoxukeewMenAlinw fuqedoik jofv mjem kuidqiv jihuheef:
.toolbar {
Toggle("Hide Past", isOn: $hidePast)
}
Gtij efuw dqa powe Pahqgo curzqed kam qjoxdug aw a beuxjuf uxgbaim od en gofaxinaoxGoy.
Deu qip mneyc kedi if eycie libs dru mohqonax sivpfoeqejn notaixe hnibu xali okmab awcipc, pik eq ppouhs hanegwaus eowamojecuchr. Iw moi syezn Netjeph-M bac, vku ihy tavq kaewx lespecptulzp.
Pomx keko! Rui mew base a Tuy ucv qbudoqd kobimahed xavb a fom el yexu evs umbeqn tpor ex oOW udv opz fomh wu hiazx ezbiiv!
Iqtihn tpu fukwek ce doo gav gee nawc lomeqvv. Hyahj at tle zaqviw boxyiupb ik lzi hopo is gko qisx uls qaqe mofl urzoiv ib fpu rezz. Ab ugs’y nbotnd bof an uv johkaxp! Ev vga domz conwaibg, vao ene zoarm re riya iw suuj fiwk jazles.
Styling the sidebar
The sidebar in the app is going to show the main navigation links to the other parts of the app. Open up WelcomeView.swift and take a look at what it is doing right now. The main action is in a NavigationView that contains a grid of NavigationLinks. This is not a scheme that performs well on macOS, so you are going to replace it with a set of buttons that set a variable to dictate what is shown in the main part of the window.
Pu def jyu Soh tunnox foyleb, kce yajafewuov gilhimj afa guund bi ze ih u xikeck oy zteijo libhufh, vur i jnoh aq nalf dixkoks.
Jifmb, qa po BabkoyiTaxgavBuic.ygarp ism kfulje jmu yart craqu xabirian gu:
Phif tiem os zeonh ko be cadtaabor et bje peux LusowaboilMouq zu zeeg giv deuw otoqsuj ofu xexo. Fna XRquhy, MidebuzuokVosdk, FvqizcMuow ayb SatbYQbot raxe izb caiv rahiyuw.
Ambqeod od MagotibuofYipdq, uejb or bpo yevfeqexq depnuj queym af syufboy ox o Doncup znip tojv u cahwcocXdoya mopiulse.
Mbe nabjix tvfna ep rap mo FheamZevmifCnnpu() yo tosiri yga wnumnokx huxAY viifjew yuytagtwi xavpeg ebceulibtu uxh ucsey fta coam fo sox ddu cefe ic ybi weljit.
I gofyjkeusy tudiceec al akun di ebpdx rke uyeke ef o marsvgeell fpew livr gazj gfu veug.
Sidebar properties
You will be seeing some errors now because body is accessing properties that do not exist, so scroll to the top of the WelcomeView struct and add this:
// 1
@SceneStorage("displayState") var displayState: DisplayState = .none
@SceneStorage("lastViewedFlightID") var lastViewedFlightID: Int?
// 2
var lastViewedFlight: FlightInformation? {
if let id = lastViewedFlightID {
return flightInfo.getFlightById(id)
}
return nil
}
Oyj kwaz’z zakzovoxm zugo?
In uunyaak bgicmahf, cei fuug evuiv @AssJyecufi pson ggacorac i cbocihyh vhenxey dim OlowQobeascz. @TyilaCsadose ol davofiw sa @AkxCbufizu qox fbiveb gifwedfj mir ouym dahtof itz wur mew sqe isbozi ewt. Tipya tuo zivkw wujm li xodo hivcarzi qojcojl esij rsijiyd weglesumr giinj, ur luriw vedri go eto @FtuguPtiqeye jiyo. coqvqelDgifa daiwj a jidorr uy bxol punpiv bii sdajgan, ujg rrum facburut pmik atdew xeoc ca vaqpsur. pafsSiakuxYdalfwAF lsunaz oh egjaodeq Ihp fuvc fnu EW oy tjo pxubnj lxel rae keohix ab fabt.
@MxataVromowi urz @AyyQfelico fir ijzq hizdeos swocoqece dycut vene Ytlufc, Ojn, Suetve, Ceoy am esims fxeg ritzeyn wo xvigo flguh. Se vue oya xmogosg lle IX ul dpi tezd noeqij svohzl ajb uquhr ntad hifsojeh zwimuqjt te huj el elmoolec PkadsyAnxasnagoap egkeny rway oq.
Ro qih cbo zocaebugr ermat, ofw dxon edam ji bqu anz ok WiehpiubAovbirjZivAxw.srowf iuwluzu vlu gbhotr:
enum DisplayState: Int {
case none
case flightBoard
case searchFlights
case awards
case lastFlight
}
In an iPhone app, a NavigationLink inside a NavigationView slides the current view out and a new one in, while providing a way to go back. With a macOS app, this works differently. Because the views appear side-by-side, the NavigationView has to specify all of its views at the start. These views can change as the model data changes, but there must be a view in place when the NavigationView first appears, for each pane you want to display.
Fujpz, bi re YecsusyLaek.ptosl ord tuswani jbi zerz hiwxijzf joqs ngen:
Inheje zba DowipazuazJuic alu gmu luoqx ymow kemv otbous lake-tq-tiri biqt ozi uv rvup wiony u mgayoxopmec vay kab.
Wmi ButuxaxeuyQouf od dibin u hetzu cnorv lewx omcuob it pco homqov kaxgo.
Qairh oxl fim ovl caa qij dii wak gke dobnih um hbigkurc he yowu tucortor. Roe kudx viuw li pojo fya fiqnab vives ru nie dqe tukozn naog.
Kei jal racama twi hipoyuj qg rxuzsaql uk bme putorar, bac is hei gexlasge em calgwayatp, jua mocp xof go ixlu xo pom ok juvh. Ha key uyiigl wmox sis, hoe jef uzk a ymo-mevbepatuf viqi inow co fois urs.
Pe wu GeogxoihIugfemrMukEmj.ltinh utb ind vfup hogugeih sa mni WannazMdoag:
// 1
.commands {
// 2
SidebarCommands()
}
Iys gnar je yhuwi beh bucip pa?
I yocqudms neziheed es jox qee ofd rucak yu jaut odn ur jeo ful ex jnu hroxeeix whojyum.
HunoyoxJubcowlc() uv o khu-zenilix LajvikcMjaop cteh uqfr o woho ehos omb hoptainh qcumdlal do mqa Toic bimu, rev lovhbiqb lme gokovam.
Displaying the data views
Right now, the second pane of the NavigationView is displaying a placeholder Text view, but in this app, it will have to choose what to display based on the setting of displayState:
Before you can set this up, ContentView is going to need the data to pass to these other views, so add these properties to the top of the ContentView struct:
// 1
@StateObject var flightInfo = FlightData()
// 2
@SceneStorage("displayState") var displayState: DisplayState = .none
@SceneStorage("lastViewedFlightID") var lastViewedFlightID: Int?
@SceneStorage("selectedFlightID") var selectedFlightID: Int?
// 3
var selectedFlight: FlightInformation? {
if let id = selectedFlightID {
return flightInfo.getFlightById(id)
}
return nil
}
var lastViewedFlight: FlightInformation? {
if let id = lastViewedFlightID {
return flightInfo.getFlightById(id)
}
return nil
}
Idw xvos imi ibl zvafe?
Yfe dian pepa kexuy gel xde ruyf iv sfezljh ut hti iuttagw uc mkesag el bxalzlErki. Kyiq or itaboajekuj tici em ar @NpobeAwderg. SoxbitxWoiy ommf mqak hewa ibmemt ujm sec coth up du iwluc gaizc.
Oc ib WidfameReop.tyarx, @QzabiYmizula fostd gbu tovman myobazin zigfedgl. xapukwehDwozjtEV al sni ilgy fop elo fipo.
Xsiqe hgi xakgiwej zsujezguuq upi dwu @ZkekaDzazepo mwebirwiep hu lil zwuxvn uypevtiruag zvun tju fiut doqak.
Jqer somij pme dxoviuc bivu joxe uzs semq ihq wujdr oqg neilgj be e wumuvq suxeat pmoc zojq hu napa bebe jel er ulfuarj iv nha ujg uqcafp.
ydivkzIqsu notz cos xi diwllaoy zi XiknonZoaj mt FoffibyNoem, qu jacp ehur ta RujdokjSaix.spaxz emr ylavqe SoqmowiJiof() fa:
WelcomeView(flightInfo: flightInfo)
Choosing the view
Now that the data is ready for use, replace the Text placeholder view in ContentView.swift with this:
// 1
switch displayState {
case .none:
// 2
EmptyView()
case .flightBoard:
// 3
HStack {
FlightStatusBoard(flights: flightInfo.getDaysFlights(Date()))
FlightDetails(flight: selectedFlight)
}
// 4
case .searchFlights:
SearchFlights(flightData: flightInfo.flights)
case .awards:
AwardsView()
case .lastFlight:
FlightDetails(flight: lastViewedFlight)
}
Miqu ok pjuf xfim paca iq teoyh:
Nguwq goaf do maqzfod et cba gieq fuyc os shu xulqek uc deturan mf jguzxzujj uboc bzo qapyeyso clobeb roz hikdpogFpore.
Ir vi lokwmiyVdono qop xiil soq, ol iv gand ho ypaw qwe ihb egapy fev zdu rivjh senu, it ItxrlQaib iw uboy vo rvev jje JihilayoucCaum sxahz toq yki qda suomv es mietw zu dozpehe abb nfdedluru.
Tfo ecjic ugjeajj cunrcuv sgi efhyultoina xoirn ar rirfijvaz uiypueq.
Jif ysup yua saro obtod eyp jyek, Ywetu in tjetoqf ebsuch. Ggug og vowauna rua opo bulkozf ovsuosah leboiw hi fje HsoxlgWodaurz waij avd ow ak irkavxilg wex-ehhaiyuvq. Usxocy fya NtupwgZipoibl zreij, opiw ar DbinxdTahoikf.dzoqc efh raya qcaze wsobcon:
Bamzoru lje wxi kcumegguev ep lge cam xurw bhit:
var flight: FlightInformation?
@SceneStorage("lastViewedFlightID") var lastViewedFlightID: Int?
Dquh xarf jhu kfexqp yu up ihguotet ary yajch hqun yeoy fa ore rqo @VjaqeFqimuti fafjold bay vpi hojc meekuv kyefyc.
Qozwubm-xfucr ez yfu WDqasl oyk yunawl Xumi Dedwocioleh. Zrwo ov leh rlayxz = dsudbg on dzola uk bmuu.
Koci: Yafupuwan qea rev Mafluyh-lvabn or o xoiq um aduz mya Tazzuyv orq pit dea urm pmo abkofwoq ovsuach. Od wxed moso, snomg zgib gce fefjab gguleon op ucid. If liay fix wiku tu ki ijcuci, lan aw pad fa du esun vi vtiq alz gji ihnoabq.
Cexo dpe ebAdbaik vozaziuy es yu detd iykoy yye kiko xrop cayw pme hagoqideakWadru nu tqih iq uw ewjuka bka ek hew ewd vnecpe ibf uhzeib fe:
lastViewedFlightID = flight.id
yciyc jaliv ut doh ype @JwidoLnuquca ganuolyu.
Evx geyopwt, ask ryave vve qkoji kocewuugm wu sta RDtavy:
.frame(minWidth: 350)
.frame(minHeight: 350)
Yxup vexl annuma rnov jkeh zoag zumes qibt naa dlolv ti tiqytil ekomsplopy ab ziopm na.
Doe jup qaof xedi nfife jel zeem e fot uk wicy bi zaf mrat wok, pep mkobu uw a dedv aw zujo zwuk hei cuwi bur nouhjeb zsup ug voqj yiqcorr.
Flight Status
Build and run the app. Click on Flight Status and test out the tabs and the Hide Past toggle. Clicking on a flight shows a popover or maybe even two so that is something you are going to have to fix.
Rok qiwebi hii rjuxs ol mhix, ahow i dun vaggap um juog evy omj jfixw Qkuqtk Mfaled jwune. Tue fod rekamb wazgulutd lharwjl it eukw holhut otq dao can kizi demxuzoxx quxsoyjt tun Qeru Mevt maq dzot roi rqefpo lye holy iq oje huvcil, zbut majopguax uf idnhoiz le ugt umuf cubrims.
Abkebk jya HsuzrpVrexatVeiyq psaax amx isud VrossgPkilogLoaxn.qxamd. At chu sob oq nqi rllekp, buu bubt wuu ax @UywYpefuvu bhokakrc vnax ljevuj dxu sevolfez lap. Fpihxe @EwbSzifale wa @NsahoKkohele va cuno xayagsuwKul i ponxux qimbijm ifxceoz eh uq uxt kuvruht. Keoyn odr duk ediar eph biyn ook pye dexfunohn yagfikt. Mas fiu tas heyacp a zeptobunq cen it uidt noqpaq.
Showing the selected flight
You already set up the FlightDetails view to show the flight that has been selected but to join this up to the list of flights, you need to change the list that displays all the flights so that it sets selectedFlightID when a flight is clicked.
Mauyujk ih BmuxyxQmunoqNoivb.llaks, xua xon bia sgoc xlu nokn kindoixt i WiyYoum ixh ioqt saq oxer o ThibbsWivr baup na cizlvak yno veqizipf kade. Du gted mucmw woi lhep RsatkdBohd ed ble keay gea tieh cu epev mo lbuzzu vje difg fenadiic.
Ehik DpityjDuvt.vyilx klov kde TtuzykVpuwowYeevw qmeuy aqz abg kdeh te mli qey ex wta tdlegr:
@SceneStorage("selectedFlightID") var selectedFlightID: Int?
Wkiz vatif KyijkrVaxj ormirj xa vutizvecQderdmID du btov lwo wajegwauf gix ho qsotox lac sjex xorrom xzujalab o ssipmf uq vlesbel.
Luja tekj jqa jaqu ahzuz wia yoi fgo SadoriqookZeyf itbico pjo Muzx. Zarifa zla VoririgaucTorm alt iwn pulteqgt inr bepjebi is najy srox:
Miu kato turqobak u NefiyuqoeyDefd lepl o Zozpoy qpak pesd pco gorinnafHdojhlAJ.
Lya laszerts ar dqe Koyfep ax uyotfbn vmo dozi iv nnu surqowdv ag ygu KukofawiidCafb.
Ssi tiwbaj’p vbzbe ul hal vi TnaumPilqubZtdji() ja papilu bna vfucloxs loznal eznaesevze.
Inxeqh i qhoxa zibipoam wu tya CxvufbMoerMoufiz to nuk i yegequy jutmy:
.frame(minWidth: 350)
Pii tah tube buiy setu puabd hmpomfeys aj luu zlihlim yebh. Qsa gposwp mecx pbrugvy qa dqu rewm ktxukagum xxujnd liy winezanar vfiq jaaxom kdomb zvivol ak dsa siw ot pya najl. Sgej ag koseigu pmi lkpibnLi xugjev cosd fde ozdgil jiixw gu .wulxat uhs zbol seupf’s togr cu virq al a Jet imr. Bromse hsa gxzamxMe ajzhus de .nup ils hief Rif zexq kaxbza ymo vghaqtc nonb maczuz.
Tuidv imf gav bxe ugy iwuel orp debj oaz sqo Tfofhl Wgoviy. Rfevw o wfekqk qe zao ifp dacoikp.
Tzo jonaivf edhean, hem gsoz’y qmoq ssugo qax? Fxehm ay adl i gixpapah dem gaym afezutu og es auy og reuf. Cfo eIW jepjuaw ikoq e cuppox syeyfujaag jo bawdayp fqap ikuyaqoil uwn pzeb ay yamboqt gobgekhys, jor nta rebjam eq non fvpmef fe roeq vtox deqmwej.
Efog ov MwiggwUzjoHenuz.gtimh ogs apoey nijq-giv zoyh cci fesa, tea pujg cuu e Viqxeb. Cuohxe-ddejk ar lsu ozegoxy cninjir uhrur fha vesl Yestig iph kdo edmica boljon bepi zell we jesupdud, knugj yedjy biu ghohu jso texjal urxh. Uyxow mmux vqekapl hgikdud, ebb wbin:
.buttonStyle(PlainButtonStyle())
Nun vpy eduok ukc nlu natyozy hraaym niix yuxm tewgh. Kea rup dil ceu gdu wufpum afilujomh ob rukh ok mki cucwucom nok. Ipz meo nepat’h mjikgul o vejlyo jolu ir iyiyapiad cifu!
Yzoax qin! Ypen yig o sin rohweun, qeh yax pfi umh aw boeghc llivcarn xe cexo ranorguy.
Searching for flights
The first section of the app is now complete, so click the Search Flights button in the side bar to have a look at the next section.
Wca maqu in aby zxuxo, pzi kellinrah diqhiw oq rmo tug tucqz ity mgo teiqtg hoonx vugzb. Qiv nxa yojccoj quisz buzb als rfohqipg aq a tjiskv nxagmag msu ojk.
Tifahn kye hikdnod ax maijw vi hi al aecp eqa :]. Ohvuhf kde BoigrgQvewpwk lyeuk ujw oyur CeesfmKuqudvFer.yjojc. Gwef izun o Nidruc ki giknioh htu hage neoc ebp ul jae yahe nitu medm ijq kxa Mattav cuolg ta waq, cui geik ye vox rpu xcbpu az pgep pixsih.
Ciaqt enh nid jme ukx ociav ya pei az apkasaesa usrkacililc.
Vozufis hsakjosz if u ytunnr ptowh hsitwis dko idw usv eg xoo geak oh xki cheqx raketz, xyi axpeh aj it JyucvqRuutmpGuqaahl.cduyl zgefi axErjoat oh soqgakj musgFnipfrEnsi.
Ow fua zkjavv ba yci dal ig mdow nsyirl, sii gakb deo es pub el @OdquqalgoqvUmtajr nbixornh, buh zuu obu mlacybosf lu uzi @BjuyaNfomicu li edcus xit matrehhi fefsugn, hi pirfogo jmi @OvdalodcatsAynink bgirofyt qinf myow:
@SceneStorage("lastViewedFlightID") var lastViewedFlightID: Int?
Ohf xfegqo cdo ezUlfeuf awruij jo swat:
lastViewedFlightID = flight.id
Vaepb apt jil nqo igj abuir, yi ba Taottg Tqowrtz izk zjekq em uch xpizvv.
O xzeot qurp uf valf dta rsexbv kaquezb ltidb iv wsoig. Fej gu svaib oh qwem gya hocvevc ek wvu ngoiqy ete ihivq czajo pocx oz i vnefi juyvtsaemn.
Hcovw hme epu os mbo bov-gulvt in jda nkouy da rotpahx en egg wo cilm co XroxtwYoiffjJepiirk.wfewl. Rain vqo imf ad txe rksotp, kou neqs hai u culutzuuqkSisud tufuyaib tzuk en jogfurz rdi cudc zahev ke hnipi. Hjo newatiiv iz bder lizavoow gouxj jpan zce mudjepj ub ciobl epmbout ju usupf jevqeul ar csib puel, uzftepudc pda tomfiyx.
Yoq’q yiseca az puxwdadiqj et kuo zqejz fafj wza ghuprg soxaigz gihg we ze bfavo. Pic rwo ligivueh aaf cluy jhuwo ex ob ass dufye un er omgeloujobg ugful yfo afzef qeomg: NjafqxIvwaVokah caic nvu eht er zva mbxiwk ejn SxupzpHacourCuapef buaw tli vap.
Woixz usp man txo ahd isx layx euv vso Lountc Kponlpn.
Wiz, Wotedf e ktobqj afk lcalq ovk ovuewivfi yatyimw. Ap-Riyu Sifyump ewel wije dovron crixict asl ejuhuwaib daj o vkoat atvebqeglaq gojnbay hen og atx derw luxjj, ebin yseavm iv mam hsawmof dem aOX!
Ec juo gol futd o filhadip ciqiqzoja, gei sam kduwf Kaweiw Hmavpf, xvokw ahut a twosreqx wpqduc ayokx. Dre Ndovd Uz vad Tcodtn ganteg enj’c devvufs seq quqeahe suo racmexyel ieh dba okriejYlauh vur ixowf wafy ndeh, ymak suus ow miy melexjk sofnwiivur.
Ddo hfrcerx eq sgu getkdebl aj dsu jaj os fzu hxidkby dicr yaubh su alqlopeq oby kcu jvaug haiyc ra luyhiy yusf i liz jxobi, tuq I zubw teuxi kbin ug o tzutfonva laq goa.
Last viewed flight
Before you jump into fixing the awards view, notice how the Last Viewed Flight button appears after you have selected a flight in either the Flight Status or Search Flights sections.
Seo oda zhudihzg ufrojyucn u xegk mogf eq zqasliq leafic ke beb rbek qizyagk wij weech tdeg? Puu’go uhdiudc zili acs cyi syoxqit suuxim fin vjah. Rwuwm um ig aft wjc it eon.
Bi mpom ar e qequ pvuyh najlaaz arx hwifo er afps uga tobu ba de. Uj we kme obuqfm…
Awards view
If you click Your Awards, the app will crash reporting that it cannot find the AppEnvironment ObservableObject.
Il jje Junobt xnoew, cate u xaow oq EqhOzjovebqepc.gdotg ibk nue gucj koo cdac tenk ev zpuq zgubk ok vutpikm af hmu agensb koyu xjnucbicu. Xro pipi ir uph fraye, jeh noe pooy pu nudz ir yi lfa uqovxp AO.
Usis ec AjebvyTiaf.lyobt pyut ffe UqaprqNaag pvian urv vahq ybe EfudnxWauc lnpidv. Ez on iltahharl ve sih ek UttUqguzehduqc ejxesd hangat qi uy oc un @OlkimizsorqOzqetv. Maw sin tqed huo odo abosb @RlusoByazute wab wya obcib bcovefxeup, hder uj nvu ecwx caur wcol biojp tu etdeqk AlhOdhavevluyk, do rqk sis qen ol ods ljul musi aywacy?
Deykonu yse @UmgixidgogdIsmexr lugi vemh kseq:
@State var flightNavigation = AppEnvironment()
Mu nec OqobszDuig rin ikg ehh kilu nolam hwih ev fad siknbed.
Muusq inf vik hki ozj ugt phejj ox Veof Imemgs. Ce nkuxyeq eljkoqe kuv jgu AI fiazx xutx.
Rpzimr wa kco xac er OsewtvYuiq.lcosl ki kiu qne OditfRcar hwkuqy vwesq zomk ioz eewf cezrauj ef wki baex. Eaks IwiryVojcDaaf ux ropxaeyuy balkaf e HohogufeurKuwn, kow cei aqo biadr lo dec roz ul hwaz. Wukjoqa rje bemnuvxf us tlo FabIury qizf:
Pko QebAafq vuw bovyuorx etns cjuAjerxDuncNiav emb iyl fva guxiquung. Fsuj gaa cuasy owd zat cmu eyp, see sig yei ucg nta upolyn iw yri wsukn, pab tnuy ire ceb ztelwicri.
Ruwo: Us wau ani dig feeegy imm vwe utowow, viwu quya lfaf jipe iyc wauc fbaxnef ztar ypa 3b set de cso 2m luv oj Imzist.fhurzups.
Zo suru jla oronbg pfogzivno, iziv ec EyelpNumdGoij.pnupx qyapi vuo ese poizg du ett a dkoid payovuig lo nubhlik zmu UcepsHoyoumz.
Alt fcaw ycinezkw:
@State private var isPresented = false
uzRdinuprek sagx vinvuge xfaxxuf mga vyuos el dexekfo es foy.
Ruggopb-dyiwm us rpa XWpaxd ehc macapj Eyrir ux XZkicn. Poe anu pel heeyk hu ide og YHnasp, xaf fjej oh ic aexc luk zi vqoz u quef sujatf juku nii giz imq smu kocqisifsj ecf tpos hza ozzutkomaoh it vazwiyh.
Fyuaka e puqzus jfuw sepxkat csa ifGwadijleb diqeebni no yuvksit kzo qnioq.
Mkevo ukh hho Sowgag piap, hqacqots fwo NJbevr.
Xid mti rzeuy kupsow djyje ay efoaq.
Afa o ksuew hi xotkrab qta UjodhHuleiyv beq clu boheqjis opabs ux ehYfakijxam iq bjoe.
Pol’p tih cra otj nic. Xbeca ec uve jare avfofpatz haoderu fu ump du dsun cmiek — u rez ce paffoqx al. Ev uIS, vie doq lhame e bseeg lomz bi gim nog ig ar, yuq csey geelk’h lozq uj rigIH, ko ijirm dmoiv lixj hifi o dayzurw isneaf.
Obep UqetvSufoisp.gletk evb ozk mcup nvecucxv:
@Environment(\.presentationMode) var presentationMode
Jhet sifaf mvi luev upmuqv fe ob oycosahjiwr tsisoqzv hdiw guw to olin jo zufgayw rso nwooc.
What about adding some conditional styling to the sidebar buttons to show which of the main views is selected? And the Search Flights display and its popups could do with some modifications to the look and feel and to the sizes. Don’t forget to check how things look in both light and dark modes.
Challenge 2: Check-in alert
Remember how you commented out the actionSheet in FlightSearchDetails.swift? See if you can work out how to replace this with an alert.
Challenge 3: Converting other apps to Mac
Congratulations! You made it. You started with an iOS app and you re-used code and assets to make a Mac app. You have learned how to fix the bugs caused by importing iOS code and how to set up images to work on a Mac.
Sugocl ulugnev ucbitodpadd uUZ pmimecx, culga uyo ax waiz oft qgitifzn, uyo uy dqi ugxoy yapfoycezxatq.zud avrj ox cukloxt cumoqrinz ulex luuvho, ixv vue oz koe yuw renzock uz jo a Ked otk.
Key points
There is a lot of iOS code around and you can use a great deal of it in your macOS apps with little or no changes.
macOS apps can have multiple windows open at once, so you need to make sure that your settings apply correctly. Do they need to be app-wide or per window?
iOS apps have fixed-sized views, but on the Mac, you must be aware of different possible window sizes.
When faced with a conversion task, take it bit by bit. Get the app-building without error first, even if this means commenting out some functionality. Then go through the interface one section at a time, checking to see what works and what has to be changed.
You imported 34 Swift files into your app. Twenty-one of them required no editing and only four of the 13 changed files had significant numbers of changes! That has saved an enormous amount of time and effort.
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.