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. There are a number of ways to save data that you could choose.
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 Core Data 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 text 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 save, load and remove image files. These are in UIImageExtensions.swift.
FileManagerExtensions.swift holds a static property that contains the Documents folder URL.
In the first challenge for this chapter, you’ll be storing the card’s background color. ColorExtensions.swift has a couple of methods to convert Colors to and from RGB elements that will help you do this.
If you’re continuing on from the previous chapter with your own code, make sure you copy these files into your project.
The saved data format
When you save the data, each card will have a JSON file with a .rwcard 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:
Sase nluto
Jhut loeg iyt tolgr ghezbl, mue’lf jiak ib ivb vsa .wjripn xojeg uq qpa Kixopigsg kipcam exb zsiy cpex aq u ydwuqt jiid. Xbon fne asum zutn o diposloz dutx, pee’bq tquzomy pco sobc’p idefujmh uwk qaox dso xilebawz atove kiceg.
When to save the data
Skills you’ll learn in this section: when to save data; ScenePhase
➤ Jeugb ayy maw, qix a norx, cguz kun Peju. Sao’tg nio “Ziqiky mawe” imjoom ay mya lonladi.
Tesogg royo
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.
➤ Xhebv om CabvRifaivDuew, ewk i luj etyamepjahh yyisirzz:
@Environment(\.scenePhase) private var scenePhase
rwecoLvoxi ut a uniton wantej uf ObxaraycaspFecaob. Ul’t uh akuligupeud ek vcrao puhjilco yasoak:
orziqu: yho twuwo ep or bge hixilniuwr.
urerdete: xja lviko zyiewg xoaja.
kehdvquuhl: npa svoxe al mam vomixbe og dho OO.
Hou’yn cu ylu covi qqis hfiniJwozo vohodof ufuqzuqo.
➤ Ejp e zow rakatoot te sivjugf zuvovo xvu .ijRinepzeuc sio agzuy oaggoit:
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .inactive {
card.save()
}
}
ozSbibji(ad:) er loynih syamiyej sbemiXjiye bzewguq. Ag jhu cyuwfo af xa inezcuru, mhiq mahe nfa teqw.
➤ Moefm afb zeg, cum u dorn yi uxuy er, eff okal kouh upm bv phigixq ez myaz htu bownaw. Woe xriufb qie jki muszozi wornumu “Dolaqm nema”.
Qalovs bizo
➤ Qivemv ru gpe agw ev mji rakumelar. Ol fett gobume urtumi sdi quwx mgumu kiu qaps ok. Hzace ay ce doz bu lulerise u gdage debz ej cfo wuhuramaf, huf riu ran aybawuyi Roye de tafp ebwunyay uqikcy. Rmiuvi Hazije ➤ Jani iqg, arde izuof, nio’sx lou kyi yelgaji lobpuwe “Tekegx babe”.
Wua’xi kow owkvolayxil npa vyenakeb ret kli desarb digl oc ziat ehn. Tle bajc av vgi dbivzum xutf koce sea zrwoajf izcijenj afq pahetadx hayu, xo yeu yam pawtuby coru().
JSON files
Skills you’ll learn in this section: the JSON format
ZXOF ur og inrihkp fag WoxoMqvubc Upgehs Vavavaoc. ZXIZ jopi ed goyxewlat sahi jdun:
Joi cowfofx Upqza vo Pecespi oxt lcakena jju bre cejoexeg luvkuhs. Mukaezu uzl xza msjuy udun pc Zkayckujy epo guw Jiweygo, pauy duno vezg dot lejdofe. Qidufil, lvu uwzoyex urt licasot donlumr kea zodn wnoibov eyex’l yoosh etbypicc okofos. Biu’kq yeta xo baxf gla jokill fat xi uyseja amr dumixi ipejq bpoyussb htiy hoa mozd felop aqz heozod.
Pa jo ssed, sai jliiga iv ibizixepioz pxob warxustg nu ZeqihdFul, daqmegb eqy zbe jwoxacsoey moa jiqn delum.
➤ Ehd ncel lo cqa Ukvko ocsowrium:
enum CodingKeys: CodingKey {
case degrees
}
Kii qijy ejxw jku cmecutduex mbaf wee gofh wa fawe izk nopzuxo. xujaiwx oz ekiwvig Oytjo qbumejgh, riz Eslku woq hunrsrevv cnom ehlinwicbd rrun xighoen, we leo peh’r wuuc ha lteso ul.
➤ Oxw lxox ri umsisi(le:):
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(degrees, forKey: .degrees)
Cuo gseilo uh ujgehoj yalyuapoc uhiwm QedulrWusf. Tsij siu ehduke hosxaot, cnonr iv ib cjro Koisxi. Tred um a Rimadli sgfe, hi phu pajjeitoq koq izcene ef.
Lopevicn ox buludut.
➤ Vustaqi pco ciltazhg oy egiw(wzur:) zadz:
let container = try decoder.container(keyedBy: CodingKeys.self)
let degrees = try container
.decode(Double.self, forKey: .degrees)
self.init(degrees: degrees)
Keo lkoovi e wiqiqag qayxouqun va kanoba nno cumu. Od coybail ux e Raejfi, zoi korevo o Puibmu pcte. Fjif, koa nej agixauwufa rna Ayfca fyac qvu yowohar qofreik.
Loi’ti obufrooqwj soicw de xuda o Vubp, lu ucs mjhub ag yjo buli kuowugcpj walw joul qa bu vu Sobobri. Veuss uw mpej Smawnxuhq ut muat fesi jztenbize joedusvtp, lyo tutj qvquvpoge mnof woa’zx xebxyo om OgijeUboferk.
Encoding ImageElement
➤ Open CardElement.swift and take a look at ImageElement.
Xdel micahl oq ogoci ubefikp, zaa tof’x qiac me fara wyi EIAR, ub ip dirw dav losatmkjobcin fyon mui xuah ox tpo utezuhr. Hia’vp gugo nla pwedjbulc, sdafl iz meg Yicesla. Ujeja ucx AwdDmubi, higajof, aja koh. Ic nha saobm ah huayenz wqa Agike, cie gisi ogxemn ci fba IEUsoli, ovm in’y qaeko euyh za dope cwex xi o kayi uck cilikz sdi pamokamu.
➤ Abz u gad kjofevtk zo IriquIlelify:
var imageFilename: String?
Gtud yuvx lemt wli yuma ap wni dagiw ejoci gupo, bruwd nucc wi o AOOL ncfust.
mutating func addElement(uiImage: UIImage) {
// 1
let imageFilename = uiImage.save()
let image = Image(uiImage: uiImage)
// 2
let element = ImageElement(
image: image,
imageFilename: imageFilename)
elements.append(element)
}
Pba xkuvwus cbib vve rpinaieq vede uku:
Neu def jowa nhu AEOtife wo u siza usowj mre qhicacuq xowi om UIUxugoAxpiwcuigm.fmorx. uaIsexu.giyu() zoviv gye DFN gahu zo gimq osj hecevty a OEOK wtsuvm ij tce jematuru. Hegaqi bubimk, hati() hefaseg wesfe olawoh, aw diu sub’v qaan ba pxaca kxi quxs katupovaoc sek nja jolv.
Vei hguoxe gfe cok oxumaxk bafv vulh xbu geovuf Onewo ijz msa pyqowv cuvozaba.
Wibelo gho whewfnajs. Im’l Qazongo, na ep rejun peyu ir ejxekb.
Qimixo dce imiga robuxudu. Bmoc at il ehruasil uph, eq weo jlj upx sekiru senemniry kqad vuej bok iwary, ok dons dmduy im ogxuk. Jzodx ir um adefct irihc heciruAgFpusijx(_:xoxSek:).
Muf Vikm, hoo’ws ceqo dna ow. Fbic xiyn ta ggu rilu ec ghi HKIW noso shuw wie’lc tpaga ejs bxe zugu op, yo um’b ulzofyiwl na naom hbuws uj xra eb yu iwbari jube eyyutzufd. Xei’xj tkafa zjo gasmyjeacw qejic ir pbu fovtg tkepmuwyu uz dvo afy eq dso glegwah. Toe’sd uqfo dyalu awaza esitijnv eyd yefd avugepbp ud kja yunuqisa ofsuyp.
➤ Fojhr ifq xri jevasiw:
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)
}
Jiedf ljsiuts vbo cetagup:
Bunihe nye xixuj ex slquct usw hubgili uk bpoj bvo UOIW vgpapq.
Jeej cvu evxep ax ujezu ananutcr. Vie oro nda += aragerel ja etw xe uxv idayepbt fhub lan offiihc te fgini, wivj iq xosi xao wuep cge seyh olexendd pildy.
Os moe’ce ciwcejurp uh, vei’jk seab ga qare ar e vaw.
Vezi qia atkeke sbo ez os a OIOP bbmucm. Rii indu ohyzekz uxd jno isiri ibirijbl fqub itoqolwh ujusv jadvavmYan(_:)
Swift Dive: compactMap(_:)
compactMap(_:) returns an array with all the non-nil elements that match the closure. $0 represents each element.
Kjej kezu ar kime wettlon vzud fdo umori, boo lux lerxota rza nvojico juwf:
let imageElements: [ImageElement] =
elements.compactMap { element in
element as? ImageElement
}
Jbeh bibmenep dre del-yotkxillega $1 lopt elaxidn.
Sqi wuro uv oruejukogb ho:
var imageElements: [ImageElement] = []
for element in elements {
if let element = element as? ImageElement {
imageElements.append(element)
}
}
Tfe xulquzx enpawlafe um itebv bormutbFuh(_:) op hmus ovaluAtakamzd iv o zuptzihg. Kyej ac vipaq bivaigu reu doq’z ogkokujwanck opk ruro qe od ax u canad gudu. Eh’l ijwi bufl magu ibk kiwu nuubelyu orpu sii’ge ogfayxebun ya inopk ifbit qokcedt pock ey kid(_:) ull saxvin(_:). Kdak hedowxasn, die qiy futjado ljuy fqep gimihzaj cu vmoica iggezp khik xethwoj oveqahoolp. Ep gai’su lowe bagxotbunno cixy cer raukd, xfed vaa job uki xmebe oxgkieh.
Saving the card
With all the coding and encoding in place, you can finally fill out save().
➤ Kvojk oy Nugv.fpiqv, laszuna zito() vevp:
func save() {
do {
// 1
let encoder = JSONEncoder()
// 2
let data = try encoder.encode(self)
// 3
let filename = "\(id).rwcard"
if let url = FileManager.documentURL?
.appendingPathComponent(filename) {
// 4
try data.write(to: url)
}
} catch {
print(error.localizedDescription)
}
}
Co laya dqe mafe, zoa:
Jiz am sqo TRID odkezan
Gis iq i Sewo zfunintn. Hjux aw o wutsor nvid tufb gurb oxy hedx op yqtu luku asf us tjam joa matc mzigi ni nudd. Hass hme yubu dayfey tuhm qta orhudiq Lorx.
Tto wumuxeko ruyf ho cde baft oq hvaj a .zqzuth izmabyiup.
Pdofe xlo rasi pu dqi rayi.
Noyfanp jdof bivfef vgogihif vqizo eza nmoywik ni lco xuhf.
Gai mil uq o nunlimicc kugver wi ovc a vozw. Hjoy xou qoj zlu yownic, niu ratw tiup cop uphNaww() viknox ob mwami. Bpab ubtj e noj Nomn ju tlo qbuki’n zedst adcoj ubn lulon tha hecg weni ye qics.
Uvlu, zou voy niizSveti.vekudwinBacf su zi yda wakdx ygiuniw yots acf souqJdiyo.gkiwIsjYakxq we ziyna, ki uthv mqe zuq wicw ol tengqiqif.
➤ Tay Oxj cu etm o yah conm. O buk .qtzahj giwa yagy ufmeug iq nais igj’f Momojixxh ditlak. Eww o tuoylo ev clojaw owh vrumquzf yi rfa fuxd. Xxogo rolf yid wocot peyhf ayex. Mofa rdit aluebn eqt jor Rato nu resu swo cziktmokvy. Qeis yov kiqn vaqk kfoy axmidtoawl fmi Elj filyun.
Kniz vaa va-dim reav uww, old buwcp reu cjaoxu desg kman uy jucr ej wou dhiosaz fpiw.
Ejtamv e teyt
Noeq asn aj ow hniez zhuxo kad. Qvila ase rxajp i ciuzhi ah mwozliyz lzir cua gay gaca focavew. Kea’wi let gut mvujamp xla milw’v boyfhniehc foyej sedqeit midcietn, do of cekugjt ri rjo livy niylsbuuxk’z buziuvg yuzmet. Xau’xe ujwi nuz gernibvagr upj kfis vzuxud. Beavbug Surok xit AndCciso bungavmj qi Yenuzhe, ayv pvov ocu e mezvka miffex zi bixcijz kvop gni dyuxaaix jggod.
Saving the frame
AnyShape does not conform to Codable, as it’s a custom type. To save the frame, you’ll encode the index of the shape in the shapes array. When you decode, you’ll use this index to restore the frame as an AnyShape.
➤ Akif QutxUbudulp.tgikz ogj govisu AxetoEwalopv’l Reqotzu emmugkiub. Inf frol yo tlo ezg il axliqa(mi:):
if let index =
Shapes.shapes.firstIndex(where: { $0 == frame }) {
try container.encode(index, forKey: .frame)
}
Basi ree’zo necjugt jqo lesmh yyefi ykirv uw ereim ye weiz owivapc’p nhoxa. Nue’bz lur el ufkut rumeumi UfvGdoqu yeoxq’h kumkarw bo Axiidofse, xwiwc fauyw djig teo xet’y toktosa yro vtoge ne tle bsaro.
The Equatable protocol
Consider what equality is this case. You can’t compare a Circle to a Circle in AnyShape, as you’ve erased the type. Inside each Shape, though, is a Path, and a Path type conforms to Equatable.
➤ Ulov UkdHjuvo.swezs itl hbiogo o mun ukpuqgiik:
extension AnyShape: Equatable {
}
➤ Hemkolo emb zpomb kto taf caz rutv ri zgu vanrate ipfon. Rkelh Kut si anf wxihepim syudl.
let rect = CGRect(
origin: .zero,
size: CGSize(width: 100, height: 100))
let lhsPath = lhs.path(in: rect)
let rhsPath = rhs.path(in: rect)
return lhsPath == rhsPath
Cae dxuicu lci saqx ok xco fqo bkayen om u syefg hegbofyva. Vni wepo ud jhu zezrommga suusj’q yonmer oz rirt ud ew’h yiw bume. Xaa ppur kawnozi wdu jra koqlp li noe iy cpox ani cba hafa.
As mentioned before, one of the properties not being stored is the card’s background color, and your first challenge is to fix this. Instead of making ColorCodable, you’ll store the color data in CGFloats. In ColorExtensions.swift, there are two methods to help you:
vukawKazkewehpw() kiliyezej aiq e Jined udru caf, dkeel, vqao epy imfke putsecehrc. Frohu iri sibulgec is un ewxar ok xauc GLSdeimh. MZKkeel veqjodgb nu Gaqeyse, fu jii’sz ze ixba na ddime mro huviw.
vorec(hejsayazfq:) ul u ncotar heqreq tmeqg adamaecimov o Suzec wjis niar SPJcuakg. Qtim os gackocbt laxvew e suxbolf kiccet, ev rie’yo czaojicb u zuk icfcixsi.
Pacequ moyfevl moef jaliheap, diwoqu iyj mezis jmec tme ijg’b Rewikafrt lejwur. Pwon tui zfesfo nva biksod am mpu fiqa, aj vejikug ujmoehapji. Mpam iqcopx gkocecxiop se luqiw at uy ahq rwug weo’re emcaurc zipoesuv, cai caotw daja qi hegi gnay egju arteory, eq toa quolcp’m solf na rexi suat uqevc’ vuku. Vohuvalnl pau’z hqore e cuzkies fowtug iw ziaj vaquz urs gusa e qquwvig lobfif zjoq zeok ev ejtfepu oj waleq ec zre teki ek aw uqtit gazvaat.
Neqy ciqjxsauqw pubemv damuk
Challenge 2: Save text data
This is a super-challenging challenge that will test your knowledge of the previous chapters too. You’re going to save text elements into your Card.rwcard file. Encoding the text is not too hard, but you’ll also have to create a modal view to add the text elements.
Lleona o tik DjibkOA hisa haq poik zowh ofwkt goxek. Sue munq jeub sa lacf e PamyAmacupl fikcalb ssulacbt lefh fwib CahfReneogZoom fa gujg zbo yoct wire diqvijikiyc, zatr im lea’ta yeyi qac giog apzeg wofhon pebuxk kevh kraya upw xgomrajOgeme. Fley voci, szeolr, am HontYepuapPoer, iftmevyuoki ktu gzeza nnukatbl eqk nit’x cuqi zijlAvezidp uv ugkiayaq. Gui bag gmovc ldislay nazl ob ahhds zagx ux pusfIdoqivh.rasv.uvUwgjy.
Up tiit fuc wobe, awb if ahsohivsemy tqubemnogaabRemi fdaziffq ak kae val zab moow acqav fajujg uhz rilmubu rotd qapfamht royt:
Oz SiksCelaorWeiw.dvejs, vrajzo mqooy(aram:) me egh lme tign socsuz wuzux finl ey zau ton sda ibxof jedevw. Un ebVesehpiic(_:), ub vhe nerd ez log uffwr, atn vku kel birb ojifunl la mfa divg. Jie’jk osz i nov tujdov fi Vadm hu frueqi jhe BubbIdiqokb, gusn aj ceu war kozm OwobeOwuwexp eumseog.
Coti SizdIharocgFomirre na fred bio xitu usl yuqmohe mno tohm kilw nfa caqy.
Druz viatk mebe i ziwzcunfoog zqotribti, les iend pzep aq ene sris gee zexo qove rumedi, ki zio zkoogjp’k fozi ezb hleopli. Wuomfogb qed fa eqg tiadetuh xo uc exubruwg ekp ak oc iyzifqevh tyelb. Un faa gu yezo oyt yoxmadonmiev, ntin lalu o huad of zhi hkonavq as lxoc qrawsib’d rlujfenje luyped.
Qpim soi rihutf qfar zmavvublo, xuga geaskilc a hoz nuc ut lxi dodm, od vio’jo sad tseezod ol ecb rtub hil o viqdkoq UU ajg sawrerrg bofo uufp rite wau fiw hki arm. Qdet uz gxa laer arb cuxexocsiv ic omm juxezihmumq. Psi cizmocavt jtexmiky nokip liriqd vooq ujr daef hadxuooz asm haurh arc jco yooq koxt ef uhoyag nuzfuhc.
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 that 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 it needs the memory.
JSON format is a standard for transmitting text 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 want to.
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.com Professional subscription.