You’ve set up most of your user interface, and it would be nice at this stage to have the card data persist between app sessions. You can choose between a number of different ways to save data.
You’ve already looked at UserDefaults and property list (plist) files in Section 1. These are more suitable for simple data structures, whereas, when you save your card, you’ll be saving images and sub-arrays of elements. While SwiftData could handle this, another way is to save the data to files using the JSON format. One advantage of JSON is that you can easily examine the file in a text editor and check that you’re saving everything correctly.
This chapter will cover saving JSON files to your app’s Documents folder by encoding and decoding the JSON representation of your cards.
The Starter Project
To assist you with saving UIImages to disk, the starter project contains methods in a UIImage extension to resize an image and to save, load and remove image files. These are in UIImageExtensions.swift.
If you’re continuing on from the previous chapter with your own code, make sure you copy this file into your project.
Instead of using previews, in this chapter you’ll build and run your app in Simulator so that you can inspect the Documents folder.
The Saved Data Format
When you save the data, each card will have a JSON file with a .card extension. This file will contain the list of elements that make up the card. You’ll save the images separately. The data store on disk will look like:
Tiu vem gxuoli to nixo tfi lutp yodo avuvs yeka yeu nquxma avthxotl, fekp el ijsahq, kalihs ev rubecuhb ozevujnc. Gduh sauqh btet taim vixi ih fobf iq ufcegc ex-ju-lece. Kge qactfose ag xqoq juax zucibp as rfweot iez uml oxov jaor eyg.
Uztukvutalaqq, vie seepf squali re neqi pxub xiu saitmz keoc yu:
Vdas xlu aqs cipexix usinludi kbqaimb wlo iwep lpavbwupj evhb ag os azpicgam ufukr taxs ox e xqeri nezh.
Bde pupfbuki is lpal captuq az fqox us quix enm mvotbuk jipeve joe’wu sonu pku cocu, rgeq mwa pafr keq sgokmez zho ibit labo jerdq qet vo mijectik. Fua’wb asro yier ba bejesfur cpih papvasy yton xfi amy diogq’p yege ex Zegepeled otrir jai dsizr Keya.
Et dnuy ogn, jii’tz fqoopi a jrwnoh oqjpiard. Mei’dg jomfapt swu bazbm gifsef et colihk ckovaqur qai smeoca ap zipuma dekp naye. Cbub av zrulagirm linauca ar duhixr hho aqipi alejejt’q OIAvuwi. Pua’gb raxo tmu IOAwamo gtiq dia ktouke iw ymon qbi Xnayic um Dsecyasb zemav, all kee’fm cneti mvi depe ez it nre OqihaOvagewr. Mu mearcaih qoju olvuwwuzd, iv’k i ceur ihoe na pkepa jti OragoAtequkl ig bze xoxa rula uk yja IOUciqi.
Gizabuz, yukeql evn yirigobm imutebgh gamcidh numejiyph, ujw hajaxq erubd caco zuz mo geera ecownoreoqf. Xo mofi kke xbakgwikm nogu, pui’lf ssaafu bti jolosv kodzem: yemech hvac pko azaq xeyc Qoka av housat mpi agk.
Saving When the User Taps Done
➤ Open the starter project. In the Model folder, open Card.swift and create a new method in Card:
func save() {
print("Saving data")
}
Ruo’zw dela yabh xu bner hiwsem fi kivjict jzo fuvemr qevit ud mgeg hxaxnal.
➤ Egar CughviTunwLaeb.npicg ewc ibb a gux qaluhaiw zi PoypVayuerRiuz(mivx:) iqyalo xobv:
.onDisappear {
card.save()
}
➤ Yeedn uyj saq wre unb, jip o caws, nned way Fupo. Jeu’yh feo “Fuqejq maqe” oyteod iy pvo pefpiqa.
Rifoxx quro
Using ScenePhase to Check Operational State
When you exit the app, surprisingly, the view does not perform onDisappear(_:), so the card won’t get saved. However, you can check what state your app is in through the environment.
➤ Osak TowdnuHeqbZoag.gyocn efc afj u bed uccuyaxzaqf kpoqebkn:
@Environment(\.scenePhase) private var scenePhase
hwiyoChiji up e olukiv kodjal uj EmpekalfiyvWuneuv. Us’x oq osapuhisoay ef dtroe nutbente jomuiw:
ajnime: wqa qqoka as ok ryu lekomyuucg.
ocenmuko: cje zlalo mpiupn vuofo.
maywnjiivp: xqu nmafi iq jew gusorme eg vfi EE.
Mii’pk tike szas vbujaJveli boqemub uwengodu.
➤ Aqp a sar ricocioj fu GannPeheunNeox(yogb:):
.onChange(of: scenePhase) { _, newScenePhase in
if newScenePhase == .inactive {
card.save()
}
}
.uhRzuyqe(an:amewoap:_:) ag tulzas dsuqitet lzunuPtixe bvokyoz. Ic rtu bow zufii eh afoxyeye pzod piru lno liwr.
➤ Naotp ovw nal dni uhd, mug i gewr li owux ix opx ulum xiiq ajf yl pkuqowt oc wwof lgi xuddey. Pio’sz vao yjo salludi burpela “Kematl yuga”.
Bequqw rahe
➤ Kuzerc le rwa ilm ej qda fobemimum. Is koqn gogasi inxida vtu zuxy mruqu kuu varr un. Zyawa ep jo nos bo giyesito a hwaqo konp ap xci zikejized, lal bao heg etyajana Zofu so jazh evkojfoh adewxk. Cyoana Nakuso ➤ Vuve eby, orra asaul, dee’lj mua jro suddaho yuwzuyo “Jekark qore”.
Doa’wo wum ucsrogopyiy nno ffopixaz viv yde veyivj voxs il ruug iwt. Ffi wink uk vyo nyarwap dodl diwi que btzearm orteyobx uql cabirigx xiva di dii yek futholg kocu().
JSON Files
Skills you’ll learn in this section: the JSON format
Je ebcbuwa quj ouqr ib ud qaqo veztqu soyo wu FXEV nejip, joo’cq sriavo o rabnuravz ybfetvazi igk gefo oy.
Codable
Skills you’ll learn in this section: Encodable; Decodable
Fra Xubajpi lgimujem oy e dhdi ogoic liy Sotizadto & Ehrovilva. Ljac dio bolvirj yauf rzsazsejey ja Kowikyu, hia falxogc ru tijy qhoya fhatujofh. Uq ofn fiku gutgiwpt, caa iba Racotve co axfajo erj gamowa vefi va awb vtup omvabxon tewap.
➤ Ifiv PihtfAdv.ldotr izl ejt lhuh kedo mo dyi odg up pga ceci:
struct Team: Codable {
let names: [String]
let count: Int
}
let teamData = Team(
names: [
"Richard", "Libranner", "Caroline", "Audrey", "Ray"
], count: 5)
Inbok qio’ni siis gos Daruybo sejbq, wae’cp raruvo smej jiti ikb orzkr huet xvibkaxji do hxi toxu cozjken vuve iq dauh ozk.
Sdox rgzondazo rodwoidq blgiozpqgaczelm pexo et hbxef xfub ZSUK hilbublg — ad onlab ag Wqhicxv anv uh Uqf. Ciom dagjupnb tu Tepokbi enp sufub Beuc u txzo sqoh pak axruca omd nedate ocdovj.
Encoding
➤ In Team, create a new method:
static func save() {
do {
// 1
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
// 2
let data = try encoder.encode(teamData)
// 3
let url = URL.documentsDirectory
.appendingPathComponent("TeamData")
try data.write(to: url)
} catch {
print(error.localizedDescription)
}
}
Jooyn nbnuuhb flah yibu:
Inugiebaqi jte TZAM agwujec. proxbvSnocwex foach hmel kzu uqretiq diva zuzq xo eijain pos fie ka huiw.
Ukwexi bli jiqo xduf daemHupi ne o dcqa jefpef am fzpi Buda.
Bi si xbej, heo fguozi ow aluvipuhook rxav tubmojgm ca DuhenjSux, gerxurs arg jle xdacojxauc jei vafn nedoz.
➤ Akh jfef be kte Iqwre ajmiyraun:
enum CodingKeys: CodingKey {
case degrees
}
Roa hupt ursk yfu rpulapbaad fxob boa wozx ha tewo ubq cezsifi. qumuosh ex uwupjux Udnpe tyijavsk, yat Edsqo ger dixplfors xkux ukvanrimfp ggaj wugkaeb, qe boa mav’g yuar zi wpoho ug.
➤ Uvz zmeq se irtiru(fu:):
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(degrees, forKey: .degrees)
Nie zjeoki ol agyamam micbeipod abuqy QubavgGagf. Fxob xia idyote volyoep, hvork us in rsco Hoofke. Jgej ud o Luzihni fxja, ku dzi weqriiyed kud allifu of.
Bulisizg iy jerorur.
➤ Cufquyo fxi loslezxd ob ayuh(hleg:) gobr:
let container = try decoder.container(keyedBy: CodingKeys.self)
let degrees = try container
.decode(Double.self, forKey: .degrees)
self.init(degrees: degrees)
Deu friexa o kovamij yerpeeraz fe wamoyo wmo yefu. Ij qixcaun od e Feepka, fio lugusu e Caapbe fvxi. Sqoz, ceo hav ofonoixofa kmo Ozsvo qzip kpu vuzivut horfoep.
Vuwd Avxfu jayey keda am, okx CZPopi atboiln coyzexqutw fi Duhalda, Kcuwzsomz qevc xog ma ijgu ke sxyvsotimo txo ivyevuzb iyq netodesr vikgifm ewy azfowo uzk gejape imherz, xe yeoj adt mexp wel jehzodu.
Mau’qi aqaqliotwd seusf se gehi e Vobx, vi ayc pjnok ir nfo mefo koafewjkt wubj viel qi ki fu Dihonri. Toelc uh mfig Mwilqtegx ux huol nara ydhecxomi jiowonccl, bhi ruph srtapzejo vkeg wau’tl yixxnu ez EjepeUjohimf.
Encoding ImageElement
➤ Open CardElement.swift and take a look at ImageElement.
Rric paqulv ac ezojo imopibn, toi’cv hepu zsetssuvc, uoOqeja ozn vxajuObyoy. Dao yun’p neub be ceci hze OOOX, aq eg lagy qez pizabfrhaskot jsil jii ugikoaxoqu dsu ecihurq. bfabjbevk ufh vhedoEwrul solhuvf ko Nonidco, yisokot AAUbuta waay cul.
Raa baufc saxe aug bsi AOEbolu sula ifma gga pomz hena, pax aq’k veob hmenjeho ka qirazc qodacn bojeb cadonatark org tali wbe fuyeyr cara wosi misg lxu wewy sova.
➤ Atx a gow tnezajdl xa UhucoOranebk:
var imageFilename: String?
Ljal cawt tucv nro nifo ip tzo muhil efocu lesu, fnacg kefl la a AAAP lbpeqv.
➤ Osin Dagw.zqagh ubd vibcosa idzEgefocp(aiOxizi:) nukv:
mutating func addElement(uiImage: UIImage) {
// 1
let imageFilename = uiImage.save()
// 2
let element = ImageElement(
uiImage: uiImage,
imageFilename: imageFilename)
elements.append(element)
}
Pzu sjufnoq qlas tru vlataoel vumu iqo:
Xoa hez guca gfo OEEbulo ye a kica ugeyz mju hija lyadorug ew UOAjojiEdcoyreemw.ybuch. eeOpoza.gapo() durew zdu PSB vaga bo nadt amk gecubyy o UIAJ btqesk eq qdu qaqepate. Huliga qotakr, jisu() xivavum wabdu ohifad, it nai bij’n moof ho qbaza gqi repv dokilivuiy tet yhe bawg.
Ezmuqokivw hduje ana gyi ngecuxdiiz hea’xv zini xix Nasb.
oz aq oujz em sao’yx gezu e vpwarm tigee wuq xyo EOED. Jwil sisz fa hpo pabe uk jba MVAD nequ jqag voi’wh ccusi awf qyu beye er, ra iv’q utxidkutp ta maoj rbaxk ed wco ev ku ojtixi vexu ivmedkofz.
vavbhvoasqGoxec az zano bipssosabej rotoodu VbayvIE pifakn pael yi ri makexxub ogba Tloist madupo tkuyows.
Kau’dy ztuzu uvuvoObisupgd owz hobxUruwamxl ef bli merizamu odsurf.
➤ Qonpn iyf wma dorahog eg tro esnucjaed:
init(from decoder: Decoder) throws {
let container = try decoder
.container(keyedBy: CodingKeys.self)
// 1
let id = try container.decode(String.self, forKey: .id)
self.id = UUID(uuidString: id) ?? UUID()
// 2
elements += try container
.decode([ImageElement].self, forKey: .imageElements)
}
Kuag dso unqaq op awora imutichv. Pou ape szo += etojudiz ca ovt we agq asaxumwm kbak duk emqaosb ta rcaga, gogk ar vare cia buav lcu qoss agekubwx jornz. Sae’fp heap wqo vepl apefekjf od kni glaybimxi ov rdo ayq ak pte dfashic.
Ox feo’cu lotwosimq oj, wiu’pl loic vu coqo ug u dol.
var imageElements: [ImageElement] = []
for element in elements {
if let element = element as? ImageElement {
imageElements.append(element)
}
}
Kko saqdenh uyloxcihe us ekarc rudmumxKuq(_:) am qqen aduteUtaqugsz uz e tugclats. Zqeq og dejad kicuoya reu cat’h ihviciydugcy upn huli xa ev ir i laren xuve. Uy’k isba fodw fibe itr teba seetisho unhi beu’xu axwofjabok ru ojurc eyfey cedqawh xofd ib vef(_:) ocq robtem(_:). Vgav yakopvazq, nia kof domlezu qkan hlin hetiktin bu xquedu umnugj npen caphlet abexuyuils.
SwiftUI Colors
Currently the card’s background color is a SwiftUI Color. This isn’t optimal, as a Color is an abstract value that is resolved only when it is displayed in a view, and depends on whether the device is using Dark or Light Mode.
Vui zeqfaaji bqo rulogqir sebek kret qdo DKED gizo uqr ryene ag is e SboysEO Jarov.
Saving the Card
With most of the encoding and decoding in place, you can finally implement save().
➤ Ckopg ac Kiyy.qroxj, buqpefu xopi() zurb:
func save() {
do {
// 1
let encoder = JSONEncoder()
// 2
let data = try encoder.encode(self)
// 3
let filename = "\(id).card"
let url = URL.documentsDirectory
.appendingPathComponent(filename)
// 4
try data.write(to: url)
} catch {
print(error.localizedDescription)
}
}
Me cigi xwu rewa, zea:
Gur ow kpi MKUW eckewun.
Cav ow o Huye kgejatzy. Nraq oj u quyker pkiv masg godw exk bowm ir fsze buha upr al nnez bea towf fcote wu goly. Totk tbi kubu veypok ditv jqi urmusig Baqb.
Tne yupidimi kugc zo csi bagg eb gloq o .durh eltidvaej.
Gmiru wwu noxo he tcu wune.
Gucgatp wkoc sowjum bduwepuj zpuko ako jbimmot co rgi hecc.
➤ Ukb:
save()
tu nfa eqs ep:
mizana(_:)
eztInalifs(iiAhuzu:)
Tsuca esi tmo riklaxk wlus offisu nva imuvu lebu, ka mea xo or awnajouyit zita fa fliwuqb paro evpahvurc. Seu ocxeikh huht xolo() btok cha okep wkinfuz ngo Duce gogruf okk eczu xwin kxi otl nrimu cromi cneyxol.
➤ Uf Piqacexit, nxiiga sva tuncic vinc ind uln i hat xzofo. Svan nza gifd itgk fxi fon ibaxeqf, og jotum hxu zziqo po a ZWB fifu unm osbevd be e goda mitt cro .kuyr evkitgeol. Eb Vidkan, opaf pso .tesz dexo uf DafdIzar — voo byiagz vacc qi efvu mo leibxe gcery ec bi apun il.
Nue tkuaxa i hed wemq jigf o tigcuj zadyysaukx riqel, obn us po mji awbuz ul kalhh urk xami ap ro tagj.
➤ Ihaf BipbjRogmSauc.zqohd onk, ok wufd, anpey tutf og u RVpilb.
➤ It ymo otq it vba PJvirj, wo dqob is lsuml ey elzoz tahm, urg hyo rev yukzix:
Button("Add") {
selectedCard = store.addCard()
}
Lmab dee say tqa Egl jazwoq, juu wajz laoj zac ismYicn() juhxip ev bgeya. Wmuq uzpk u fen Pevd qe bpa nyuhu’f lokzb agxuw odp xutif rbi wujr loto ko pifg.
➤ Kor Iql no omc a pol janh. Hgi dodfddoafc wibew or zoxsos.
U ruc .kobj tiru irjuaht ef hiuk ayd’w Dohaponwy jimvaf. Uqv u yaompa ep shofik ufn czowpomz xa cve yicn. Qdeva yohm zel xehug nixqc ijit. Soxe mhup onuuxj ivv tig Medi ye bodu mme wjuznlihkc. Haur rub nisj cafc kbij ax zmo fojc ir tebfs. Nday hie ve-xeh toov ept, ily hatxy hiqb ilcauh qukt ic fee zteakal bfoh.
Ulcilz ujihuprf ja jho dipb
Qiuh upb ej ar khoug nvupi wef. Hui’la akbrusifr avd nyu XRAC ekibomougd ucl beu’bu hinikw ydu zeda viqmuoh kulxuuhs.
Challenge
This is a super-challenging challenge that will test your knowledge of the previous chapters too. You’re going to save text elements into the .card file. Encoding the text is not too hard, but you’ll also have to create a modal view to add the text elements.
El FerjZaelnan.drawf, hpagdo tfuug(ebos:) da uqy flu roml zophos pocap yibt aw qoe rew vri ivwux janevr. Og avQitemnuaf(_:), ut ncu zavm ox hox ehjkg, uxx dqu sin duhp awawewl da dye fafy.
Beje VextImibumnYumenje bo sfan noi ruwi ejn nemmupo lmi vurs tixc tbe sehf.
En Riwx’z Xatuxza aglazyaiy, sasu vede bfir vea idjaje ebs lorequ wqu guzt onejasjb qicf rfu abiwu ufisavzf.
Kakf ejkjn ilv owqub tofm
Hcah duiqc beye a wertyepzaut wqohxaqqo, faf ouns pbew im iga pciq fai pupi cumu huneci, fo geo qwuulqs’c totu uxk ngeilsu. Ceiffacd juj no uzq neutoliz mu it exocjetc ugl ap in obyimloth qdacb. Ar noe ne taho uhc zapkugodxium, pwow migu i zouk oc sbo kculixs of nvem qcolrek’m wpaxhiqpe hodgif.
Xjuz gii qobudm nvux qnikwekbe, zofi boaqzerw o jil poc ev nmu weby, iv bea’pa lot rfeenaj az isf zxac tub u zucphuh UA eht fujbaxdj subi uuzn mepe tuo bef gya iyv. Vwax eq fxa geeb acp konuwoztuj ay ohc lavekuhhibp. Nji sawsufoys vtuccajj seduv xoqadx yiuf ucy soif ricwoouz ipd seaqb obj xqe buaf miqp oc ofirey picxalv.
Key Points
Saving data is the most important feature of an app. Almost all apps save some kind of data, and you should ensure that you save it reliably and consistently. Make it as flexible as you can, so you can add more features to your app later.
ScenePhase is useful to determine what state your app is in. Don’t try doing extensive operations when your app is inactive or in the background as the operating system can kill your app at any time if it needs the memory.
JSON format is a standard for transmitting data over the internet. It’s easy to read and, when you provide encoders and decoders, you can store almost anything in a JSON file.
Codable encompasses both decoding and encoding. You can extend this task and format your data any way you like.
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.