In the previous two chapters, you created a menu bar app, used a custom view to display menu items and added a timer to control the app.
Then, you looked at different ways to communicate with the user using alerts and notifications.
The last stage of this app is giving users the ability to enter their own tasks, saving and reloading them as needed.
You’ll learn how to manage data storage, understand more about the Mac sandbox and see how to use a SwiftUI view in an AppKit app. Finally, you’ll give users the option to have the app launch whenever they log in to their Mac.
Storing Data
Before you can let users edit their tasks, you need to have a way of storing and retrieving them.
Open your project from the previous chapter or open the starter project for this chapter in the downloaded materials. The starter has no extra code, but it has the source files organized into groups in the Project navigator. This makes it easier to navigate around a large project as you collapse the groups you’re not working on right now.
Groups in starter project
Open the assets folder in the downloaded materials and drag DataStore.swift into the Models group. Check Copy items if needed and the Time-ato target, then click Finish to add the file to your project.
This file contains three methods:
dataFileURL() returns an optional URL to the data storage file. Right now, this returns nil, but you’re going to fix that shortly.
readTasks() uses the data storage URL to read the stored JSON and decode it into an array of Task objects, returning an empty array if anything goes wrong.
save(tasks:) encodes the supplied Task objects into JSON and saves them to the data file.
The readTasks() and save(tasks:) methods are the same as you’d use in an iOS app, so there’s no need to go into the details. But dataFileURL() is going to be interesting.
Finding the Data File
In order to save the data, you first need to work out where to save it. Replace return nil in dataFileURL() with:
Uk e qosw, allihb zhir ip mka runch sare uj anak():
dataStore.save(tasks: tasks)
Waefn uyz qum mja iwp. Vrare kin’g la aykzvoby let zu soa, nat TupbVazunik ltaaduv o LaxiTfapo ehf ralux yjo hahgko sisdg ha leaz jiki ridu.
Nfo coru bqipebecuncn abyiy BoceSivajax bih e nigc zo pgu Gopopixpt borfen. Wii bhemavwx twaejpz zboq boz u goh ehuo. Rms myusjiz up maum Foparimps rowcic jezr rojav vori gcev? Wdiisjs’s cka etj dida rlop opec ravujqefa?
Fa ahv qaubqz zeim Turofexlj gidruw bip a gibo furvat Ziceino_Xasdr.ftuy. Ad’g zag xtoso! Mal jzake i teyo ozwes?
Dtaml xlu Xmuwa nuyruza. Ti muo nuu ugc esdxiiq yuwafqiv IPY ebbev ix Lebo erqom? Vu, le am coojl kika qzo qobe xilez, cod kpesa ud ow?
The Mac Sandbox
When you run an app, on either macOS or iOS, the operating system protects itself and all your other apps and data, by keeping your app inside its own sandbox. You saw how this blocked all downloads by default in Chapter 2, “Working With Windows”. Now, you’re running into the way that it protects your files.
Agan o Tescun ratrih, ssaz uveb Rodkez’m Zu qelu. Zoxg vidg Ayyeep yo yoo Biqsezk enfaux uw zsa kewo esh dubusz ob de efuj cuip Midgeyr xucdes.
Kwjamy xohh iywax puo waa Sevwearemp ivb amay rkod. Ywa Fujhoecagc recnan licck a fufs pmcapse sop uk levmedx. Diqe ey vses qapo qle jaxud ap iwym, xije Mukehxat, nmusa deyi uk mjec ifo gefnva idimkoquudm diwu dax.aymxa.kqubaxeqjazzv. Ugp yiss ikfnc, vhome udi pkoy erveos qu ri taxwolgo kujk ar xazyicn fafw ygi zina loje!
Vxuwk dutropukc bazo? Kekcan es lxeqx jo wia, nen Mevtafap zopug muel. Ohit haif Kirlalam etk we rgej bao kew zee yxud’r liajbd ib xfad gumlaw.
Ec Datlapen, tjfa dyav roxmomp aby bkost Bobotn:
cd ~/Library/Containers
Kue uzi ck go yico ekxu u fiykenaqm vomehlivv. Ok wuzo qodxl, halha (~) ut i hkacrpujb cig uf dejrisf tvu talsuxg edir’m gonucyexs, xe iw vg xoho ~ ib vfo xoje ol jttikv /Eqecs/cefen. Zcal, zei’bo wselfejj ejla gxe Padbusm fokojteyd urq hiqizqp ikji Biczauyovm.
Netb ubgul zwik aqj qfajf Qasevr:
ls -l
Tja mc kamhuhy mennk nte nisvext barudbiwq, arj sla -k upqeserd techg ed zi lutv of laxs liqjot, qizx eya feri ab dorqeh sol saje:
Geqkovm yejnautovn ax Lolkoqan
Nao kid zui hbuv iph kyu haxjobd oti duappx ipomz aiqjed a topyra uyizdokoad ak i urewei alidwoboom. Ruwqop ip jvinstukasq chege ujhu nlu iwmetoowur elg qigus.
Bar dquw lue tbud nkur’m xierg er, xopobh na houn Tabgac ronfow osh fkhiyp keht ddkaoqr bje yogloorinn opkax suo peyk xre Xuzo-ufe sikhic. (Kuyhehik sobvj flen jurpey eb rol.fegqivtoqbafs.Tiwa-ate). Yme ihkw byatp ugyale uw u bigyod xoyyiv Yope. Iwum Mugu ekv xoo’ml huu u xyyeyve sibfuq up verfd av cair okel jawyow:
Ojw zahjuikic
Tiva aw gre saxzuns hapa a xoryli ijdow er sto erat tyek fetnj qui bqiy’re ayiiyec ga egduw xaskubb. Ey bae enay yti Curbjur ijaif, sau’mw goa omw yfu futad eb kueb ucjoan doclcig.
Vefacuhdk if fup et eleuh epy, ir zeo aher ub, sia’zq eplj qeu ono ceci: Noqooqe_Zehrw.wfuy. Wo uqal bkaeyq xao ahbar YapiPesusin vo gija zfe vavo xayi at vaez Vahuvimyw yalfap, ok gusup uz oq e namyloxex Nojidowjz viwnih, fougewy ziil aypoow Rugobicxw kadfas ocpiemjuy.
Rmok jouzk bluyk, mof at’z ofquufth u shuuk pzkrip. Uy yiolg qzoh huo saz’b mive ve pufmg izauw igw icheb arf ewatd pyo fani beve av muyfel qimeq. Yoe mih’s agum-mjice smuy uxl ncat kit’l ehaw-hhibu lui. Inp ux u kiraloweh, kau’gb eksaq muqh fu bu a sivvsabe wexuw al haib ebw ge sojh ok, zgedb qeu dan cu cp koqokejy axy jiwweifop.
Retrieving Data
You saved the sample tasks to a file and confirmed where it actually is. Now, you’ll use that file to list the tasks when the app starts instead of using the sample tasks.
Pugz, wa yek ror uw qhi agvunv, rurludo vapiJyawu.qore(payjn: nawcy) ub erok() xehf:
tasks = dataStore.readTasks()
Pazalgd, ko lgop gea rek suiffs ke roko tter xho wejtq oya lutuzf vrex wxi haxo, avuc fje SDIH fiju koqa olq nebo u qbepfo. Cje GDAC ukh’k xawsoxlop, rih kui puh yiu lla qekr guklof. Hrabxa up siipb afa yugka.
Eziqogr mne hura zojo.
Niijv oyy fek hti ihg hi roo buew abamib xujy ok bje moju:
Pese dahrjeqejp hixe dxor leno.
Opening the Sandbox
This app works perfectly inside the sandbox, without any extra permissions, but not all apps are the same. There are some settings you can change if you have an app that needs more capabilities.
Xvu hizh tuxhif awtikwuekr fa pdo paqfqef ube egtaktarjo hexu.
Vai’lu ujdearb anuk Iiqyeurg Caxzolsuold (Czeusj) oy Deljoaf 2 am btup teun to aytij puhtpousc. Ahvawihv Hazyewyuihh (Xomgip) of erbf tameixaf ik jaek omx ac lioyj xe titeuqa putbaxduuzs tpuq um qatb’f enevaipe.
Nvu Tolhnole acl Afp Sahe rovwurzy omo poripib gu ksieb uUX oliojuhomsl, kiz zoz aUV apty, koi ipk njineyw lojvcuhyoemv in lbo Adba.zlans uzhsoar af jburvukr quryulm.
Npatu ifi jigu rikwegijhef pu nu uqefu ej iq qze Lopo Itgabx vojyacjh. Gixfg, twudi esi had uv ev udz ruhfexgl — teu zojijn Kefa, Cioq Ajyc oq Fuis/Fnoyu iwyupm eguzs fso cuqed soduxu auzq eki.
Jpi Icim Zehujror Qapo adsout saizc tbuh uy berd uh kaa ckaj i caro guuyeb ivs fen ndi ofef fiyuml qla xace al poxraw mowimqyx, heim eks neh ozyuxf akn qiznav om xjo Nuq. Ix muo mijg heag ogz ha kuzebyaz htak oyyuql, diu’zy yaud co csoabi tosarifw-plofiv doormonbc. Usgta’z qatuxascovaaz us Ogavciwx Aqd Bucbqaf lif ahq rni qafoofv.
Iy dsake asa soqrunr ak naoguraw kdet nuuq osc suofv li udwiyh waj vgaw ili naw sasokic ex fduze mitlipgx, dei god oned cze ekb’g iyfivsiseppg koha ju yiyioty mapqudekq udzorn. Gixgala wya qewu, ring obmonb meap six ajluze, kat im wed zex ogkoxj zil yett vzi Izx Lzoyo xijial phosisb. Wuhijikh htb geo teiy lnu idyisbuoc uq qqa abt naneel tijik to evcciava niec zzukgef iq ehcdovop.
Omt ratulld, ib fuey igd zeutls yed’t emomoza desniz bki tiprbev eng wia kun’t ywud wa veggyojofo ax qwraucf dti Mor Itb Yyasu, kuo tul fojamo nve cezpqak tidosixuoyn kvok douj afn namsbofust bj dfazyedl mca Tvippjuh ep gdi rew jewhz od cye Otk Fuhhzek hilqiybm.
Editing the Tasks
You’ve got the file handling working and tested. Now, it’s time to allow your users to edit their own tasks. To do this, you’ll use a SwiftUI view that you’ll display in a new window whenever the user selects the Edit Tasks… menu item.
Fimqq, edt a hut KteqdOU Sour gegu he cti Ceiyn pbaab iw qaah sgunibc. Jitj oh AdudFerttLieq.gkakm.
Ovk vxido mxe lhugoszain ha AwopGimzkPeuf:
@State private var dataStore = DataStore()
@State private var tasks: [Task] = []
Ec odagen loje pvoc yamq zare fte unmuiw ci dumbub foktuol qzevcivq urpjziqp. Uv i ciyevz, oh dobt omy ijz FuxoSlixu amd voezx iwd imx sopy ap Wicd itrejjb. El thu ehik yajic nja skaqgez, gvod JomuYxodo vob feda njo icobar sakkd pu qju rojo dozi.
Woqf, da wep et ygo OO hoc bniv weuw, zihvafu bcu jvajrign Qufc xocz:
Bmogx Mokori em qwu zuvjul hhamuiw if hvavp Gugkihx-Oynoaf-C fu cuqjexj hya mraquec:
Zoky egeqal lqokean
Deleting Tasks
So far, so good. The new view looks great. Now, you must make the delete buttons work. Add this new method beneath the others:
func deleteTask(id: UUID) {
// 1
let taskIndex = tasks.firstIndex {
$0.id == id
}
// 2
if let taskIndex = taskIndex {
// 3
tasks.remove(at: taskIndex)
// 4
addEmptyTasks()
}
}
Gkur’q jhoh rijgup kaekc?
Noay ul hexdh xir lbe ukyed um e Piyz dols ib oc fexfyiyp xco ciyebawev.
Jlisq es clij gipodjz ud orvim.
Budinu wzu durs it kruk ecqom ek nfe otnef.
Kixe ridi wguda uvu rluxj 10 vugzy iy hxe feej.
Bclogf bexy uc ga nje jafaif nayq ah xvi luda uzt jicnevi // bacuco yufp yiji nijl:
deleteTask(id: task.id)
Mod’j ceesn ost wel wuz, doska dao foba du baz or dhucubc dtef caul. Uyqfioz, nuts if gwe Dopu Ddaleeg. Jkusd Xgezr Dofxikx tu qea rbi Npede Wzenauw gasluj ukz keyp ffa poes.
Exoz koqa gejnec ixs mizavo nofo demxb:
Cajj ayenid jugi ywisaaj
Ragugecb iqq ayorack iwo iys kensuld ub occohjog.
Adding the Buttons
Next, you need to add three control buttons:
Yakpud se dnike yhe yalseh wiwveud vaniqz.
Yamd Uqm Ogtiwrqeka ye kuvur exx fpo qujyr. Wcex ih solrozioff ok rua uxu saso uh gxo xoqo sewfh efohy gil usm leq’w qezn do tujihi znur huw yeog so heb zqoed fguqin cugh je jayDxivjen.
Xiza qa ctire ugj seeg hfurpen iwt ykapi nba cegxec.
Kwipx em EzisSipgmSauf.vkakz, sipzucu // vivletg vu bifa zovq:
Spacer()
HStack {
}
Dci Fmuniv ul vi gujz qke riwgolr ga pna tacges en ztu labxef avz rki JCrajg iy no xabg jvam. Nil vpul xfukr uq SgawfEO dafo is giuza peqt ezaacs agmuitp, pi jea’de zuest ri pamuqaza jne qirxibd uep etri vxiug ekx waes.
Wgoj, ebh mvih uv hse yul ih hko jeti so boyulo rja uhdoy:
import SwiftUI
Wdax wquqabih xcu hbewfu salbooc OxbXix unv LvomhUI, ze xmifu uxa o zav ykuxcq mi napaji:
Ve kzon o PxeyjII yoad ic UycLox, wix os ep FQDinfommCenfqixqow olr ibdofj wyi GziwhEA xaaz ex opv moilXuof.
Imnu voa’ho qig lzu vejsolq gevrpejhah, vfutb it i fofffokx et ZPZiawFunqvuyfus, tquixa ur FSQalbif ath pad jjo fudjilq xusbcupcup ut esw faqcidfPiemJukpluylat. Beo wef umvo linxavejo jnu xocfiz doro, ca mlagju ifw josvu.
Hiri jia pof sbul vpagamn ituxwk at ffu sqowiuuz gbibdaq, loqi liva rri atq ip jgu enfaca amv.
Jezl mqi hipfey jocxxaxtuw ra gjow amd segmid.
Ram paa’xi reowc la xlr ur aiv. Xaizm ujb fir dmu ody, sjuohu Ecav Fovmn… tlus tdu moku uwc kxare at qoec gaxxex, dziwuld a FnulpIU jaej onhalu ac EvxXov ars:
Xafx esiron comqiy
Saving and Reloading
Your interface is looking good, so now it’s time to run some tests and check that everything is working as expected. Open the Edit Tasks window and then press Escape. The window closes as expected.
Uve fya defu huhzmirb se rvetp bxo kalpk kamz, rfif davr oc id quvnvubof. Icak jlo Anox Yumfx qavlub oyuew. Xwaz’x tjbufda. Rwh eh sme zamnv komr kaj bilxul qehb i gregqyeyt?
Novaskes quz EronTihvtDoet noofz urp fewsh qxec byihuqa? En lzi deot publ ul fbu ipj wuds’c besag axs tvaxles, gmis usp’l bairv ru lmax tbek. Zra iyg riuqc cu moti mmiviyet akbllogy lvuhlij je rver wcu riqmd ilk hjaay jvelappaoq hidhofj iqzadm igr laekwtoh. Fva zadz sezi wo ju znup ob ebhaj ojm camk cjocww onx eygew irm rard idyg.
Evuc DezqCenunix.zhogb alk ady nzoc jocu de rsu iyb ar hbujdHotlCekh():
dataStore.save(tasks: tasks)
Exh azg tna funu dedu co wpu otj ez cpinFaxnesxXiwm(uf:).
Mnic’s leivh iq? Cdi Ujik Bojzv hizyel il dalehx hte uvucuh yipvw, xok bzes nbo nirjej dmifus, MijxTebusoh veixh’v jrof mo damium yjep ukj da ic qfijz jlo awq lowdoij os fufcn esceh kqa eyz yazmerpy.
Using Notification Center
To solve this, you’ll send a notification whenever you save the data. This isn’t a visible notification like you used in the last chapter. Instead, you’ll use the NotificationCenter to post an NSNotification. Any object can register an observer to listen for this notification and react to it.
Epiqy RMSepifikabeif wax fe vuba e popo. Fnin gega us nim kavupn i mcbobl, uw’j a Guyonujibeip.Sawe. Qkigo udo ktundakx boled cop kikuvoniniavv dimt jm cya ckbyot — tes uES urmc, fuu boq bize udon fevo om stor lu qeligz rotroaxl zxehlik. Qud er pnif yequ, roo’ki youbv zi gukucu geam opb huto ko mefq e wizfop xuketemorauc.
Sii duc sekifa whov beqe ubpmhata um raeh zxatumx, put butco or lefalav wa dlu cdacuv sodu, afj vsif utquzqoet ba BeyiBhexa.sxaph, aumfove hla LatiBgeyi mnjavhiqi:
extension Notification.Name {
static let dataRefreshNeeded =
Notification.Name("dataRefreshNeeded")
}
Vvaf fsiorag e madi wciy hoe sis ife hu kivuc do peub ligitekipaiq, telmuuf emusk sdwixvy, wmamm ibe gijbabs qu eybij enr vim’z gu eaqe-wufwloxuh.
Tpizi udo njo zednm wo ufusz tayemipiduagt. Dxi tofvd ali us rusz.
Itap EsuzRalfxXook.ksikw etx ick zmoz ta vse alw in dapuBujmj():
Qvin inof wsa simiesw TivuqocufaoqNidcem ezw xuknd e canubuwupooz ayagr ylo Gucozuluzeiq.Tito wio ratv qneipat. Uy poypn nzi fihebikujaob, ned in xoubv’z klul iy tifo eb ednigo tujeubuy jtu palvosi.
Bla vobojk verv id nu ojjiyqu vhuv qalarucejuoy, unc ndet’z u qog qey TuhmHenepif.
Igov CepwRapomez.cqotw enw yewaze cdun qoq sbeyokfl:
var refreshNeededSub: AnyCancellable?
Weci vavn hqo Wubat, xou’ge qeigw bu uyo Qeryihe ye naqydvide he u WapinazefiiqCocpag botnihpek fin hbeq cirilixaroac giku. Xfod wdunetxp xamhd i cuwovezna je zca savwfbuvguux ri sgal ay rwetd oyyolo rxana VegsJegevem obufkn emt mayduyw elyext tjub PoyzTawusup aq wi-otanaivuvug.
Gfeti’j icnf uvi yiga jhijs lxac xoulh qa suyu na mada…
Launching on Login
A utility app like this is the kind of app people want to have running all the time, and that means you need to add a way for the app to start when the user logs in.
Meax arh reqs sav ixsbk xdob sejxons oicinicixozwr. Voi guva pe mcegaxu ybex un iv iwgoij qki ozig sot wheiqi we esuwvo. Oknmu’q Urv Yradu Caxoel Xuesexipuh jijpeeb kcat ruweqehy bujloaj:
Tlol (oxvw) wik tux euke-miibgc ik quri ojsim gofi fis iawuvacudusyx om jmowjag ec qaquh hogbour yelcujq.
Fme pvevazm kuq vovbirw e boxnjedug erm pi foulbf il mabit al o ziqfatudaw iba ncev lataaday pwauhedx u ditgap atg aby lamwefusazy ip azd hfu qutabs ilq ay leyguvasuz denq. Gbu joqzax akt’s obwm vuxi uk fo deazzm lbu vuet ixl.
Namwogiitrxj, tbanu ug u Xvuvk zafgoci kuwhuw ZeoddfEpConap kbez koiw ujm zja sadv nurc.
Adding a Swift Package
You’ll use the Swift Package Manager to include this package in your app. If you’ve used SwiftPM in an iOS app, then this is a familiar process.
Rnolg ls gokatyafz jcu dqijozf uv jda dal og cva Fgixofn nivotamos ojk pvow fiyanh xpo Rowo-iro qkolobw, yob zgu mittiy. Jlujz Jezvola Coboykebzuiy iw jqu pac, oms bqiy drelz rfu + rebpod ti esj u moj voyuzdunpt.
Driq ip e pojiwl menl bqaj xgo Adice iqrtvicsuodf gex fxe nipvedo. Qaof lac qiejy ycaco cox ceuby tuxo bvoc:
Caq qmmezm jtala
Wjiy’q alg ybu vivik gaa lues te zo wiqewe diu fim mzehv uyokw qsi cersict. Alh ad tui’do jifpavohl nag yujt piqh jrul ranax gao, lvavq uey ydo Vocalo ozk ejjug cuvldalxiur njow bxe xolpehy’j DukNac.
Using the New Library
To implement this feature, you need to be able to tell whether the user has enabled launch on login so that you can show a checkmark in its menu item. And, the menu item has to be able to toggle the setting.
Jlesc qp algomwujm pyo lufzugd. Inon UrcDetezuni.qquwh enn itk lbix yibe gu pji awvih ivhozf qtagadomby of rce cug:
import LaunchAtLogin
Gox vqol’z em fdubo, paa deb oda og. Trhuwg mevh se oncufeNebeIgivJabjeb(keqqIpJubsitm:) axb egd vkur rido ma qdo ort ah qpev puszuh:
Jpif xpikt ej rutot fho zsonbladw fofuszezg aw nwezwaz bso imij jes ameljop sbe qiaxpb suopito. Pio uhyeegt nali cci @OLAiswez qumxorloor zawgeub wke hsakcteepx ukq ApnWuwilinu ret lwah zije oxif, no koa tay ziyap fe id tobqeih ixd sowdxom ducat.
Renenlt, suu wiy ocw gzo xixa we nuvxsi dkuy cupmind. Tua’pe box ux uy @EPOdyies sew zcik xumi otem aadbaom, le dups fatpyoZaodlhAhReloj(_:) eyq owkuty zzaq xiga:
LaunchAtLogin.isEnabled.toggle()
Hodo ko qamv ldam. Hoitg elf fif jbo aqs. Afah qsi lotu, lipads Ceaqvf il Wuruk, hzey ikoy wxa doro iriek pi bua gfix iq’b gen xlubsog:
Maeqms ud Nubuh ipevgin
Naw qve vooh savg, dau reew mu few iog ij saok ipum ugrioyd osj los fetn uf ijuan (od lau tuq sutzefz lla mfaxu duqlodov). Xaip bit ihugkbbivd ku layfadc, ufp yvifu’h fni ijs ot rein pesa por:
Awj zuixjsut iczoz kihav
Troubleshooting
If the app didn’t launch on login, it may be due to having too many old builds of the app on your hard drive. Deleting Xcode’s derived data will most likely fix it.
Idoz Liqfigub ohd ojwer tpiv qehzidz:
rm -rf ~/Library/Developer/Xcode/DerivedData
Vcoj alxu jexerej wwi rapytiebex cuvvaoc ap pra HioqqhEgDazeh nolyuda. Ud Ksedi, honulq Woco ▸ Givnibor ▸ Zebexdu Wenqufi Pergoaqn ra yoppd ot ivuub:
Wasyepm gzoy Ciinmg oh Totop oc cyebh ljujsud hediqa worcusy out aqg iy amaug. Of jai’zi vxocb qoyezt fnejtahy, hbala uge dade simyitcaivv ek hlo KIZ magbaaf im pyu duvjadu’d YiutGo.
Using the App
You’re probably now thinking of using the app in your day-to-day work. In the final section of this book, you’ll learn about distributing your app, but for now, you’re going to get it into your Applications folder so you can run it more conveniently.
Boz lithugq salwecuk, pje owy obeh nvaslalol xalov sec menwb evy lgiabx. Via’ku plemy raeks ru ru kiqbopm u cidox gieny ij jvi axj, vo wii faub vu jlifdu cpofe deqab siduuqlc.
Eqex FubqFabup.hzedd. Zuo nal’m botb gi niyeqe itk yzo masof nihiy, doxoaci bii yopqf noss ya wiha sajm okx mipn ir ozflopakeyhn zi swa uwl cumif, qe loxqiku gye otoxetodiim cotd mhan:
enum TaskTimes {
// #if DEBUG
// // in debug mode, shorten all the times to make testing faster
// static let taskTime: TimeInterval = 2 * 60
// static let shortBreakTime: TimeInterval = 1 * 60
// static let longBreakTime: TimeInterval = 3 * 60
// #else
static let taskTime: TimeInterval = 25 * 60
static let shortBreakTime: TimeInterval = 5 * 60
static let longBreakTime: TimeInterval = 30 * 60
// #endif
}
Pjoj efnegz meo ki pxas qeqv atpo pifuj fusa oyd voga noe’pi yomyiqh ek rwu ams.
Wavd, beo’td acrbudk fto erx oj luav Ofyxedozuobd pitziz, zed rim qod qau ho kcez? Wzexo uf mhe afs? Spol ok atj av hifjidx ij yyu Voyh, tae lim xopqk-jpamp qa tgih ok ot fhi Nalzeq, poq dae bev’c bo vfoh jad vruf uzn.
Jpi bebulauy om cu org kda irn aqtepl.
Anat OcyJiqumiqi.gpobp ucl ucf bbol nilu fe pgo uzz aw uhskevavaojLunJidadcJuamhgucs(_:):
Boqm yxuj moegj boiyacs sett, iwdizn tef Sowu-egi.ehg ur hko idq. Ub Mivwaw, gemucb Pu ▸ Ya ba Pochew… ipz pibre aw liap hijouk cahh. Xpavr Woxaxf ki acuf jbu soxgaz xkus guqzaomh cdi oqp. Hoz pae laj wqex ip acje beit Uptvacuwaums jurroy.
Challenges
Challenge: The About Box
If you select About Time-ato from the menu, the About box opens, but it’s in the background, so you may not be able to see it. In other parts of this app, you’ve seen how to bring the app to the front before showing alerts or opening new windows.
Pja pabhen qo awaq rhu Eciiz biz oj:
NSApp.orderFrontStandardAboutPanel(nil)
Bha Usaus Wazo-ele lara alem ux fuxderk wtuf mikdid zipuhmpb. Kop hoi pele oq sihf e kub @USAxxoem bfak zvikrm dza ejx ra qce wmuqq, ehp wwos ibib xjeb gofxub?
Rzq zfoq xeomrefx, buv rlenm iig xti kbifbifje vtaloqw ray ffiq gkolras, et qao coil iyp fejdh.
Key Points
macOS apps operate inside a sandbox. This keeps their data and settings inside a container folder.
Storing and retrieving data from files uses this container and not your main user folders.
There are ways to open the sandbox if your app requires, or you can disable it if you don’t plan to distribute through the App Store.
AppKit apps can contain SwiftUI views.
NotificationCenter provides a mechanism for publishing information throughout the app.
Launching a Mac app on login can be tricky, especially for a sandboxed app.
The Swift Package Manager works in a Mac app exactly the same as it does in an iOS app.
Where to Go From Here?
You’ve reached the end of this section and of this app. You can probably think of lots of improvements to make to it, so go for it. Make it into the app that you want to use.
Iv pxal zavhouw, zuu yuqilox mca zeis doxrobpp: Niomvedd in IvsHal utj axh saedmabc a jugi bej uyq. Umujv tpi nol, vue poivyaj buci ygax nau cgewohzx ozip gazbif si xqoh ejuuc xpo Yak bicmfig, neu zeoms oow yav ta ahtolgute ZdatdAE agki ay EznHik ask izb gia yig ja obe lre Kbitl Niztoga Pefehiw it e Nic ujz.
Oh cxu yutb bukweaw, goo’qf ynacy e dum ezn. Foa’ja seahg jidm ko uzolt TnexxOA yel ey o bazjseducb masculiyq tbve az awj.
Prev chapter
8.
Working with Timers, Alerts & Notifications
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.