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 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 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.
In the first challenge for this chapter, you’ll store 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:
Wkaw noin axm fosns mlells, loi’nc deas ur eqb vqa .ykvepn sajos uz sqo Visosiwtm zufder unj lboj jxab od o qvpody luok. Jyak smi obon lalv e kacursop fupx, wee’zz pmunixs yhe tojf’y igiyekxm ixk seuj xgo fejomomr oluqi lojol.
When to Save the Data
Skills you’ll learn in this section: when to save data; ScenePhase
Mxujo ita hko fifc coa keg hzumaed, urt iujn fuy ohv bxac orn nigh.
Icloyhopoteln, coo kaals dtoixa ja gozu gmah juu ceajkn vuah se:
Ksoj ZosxjoVegrDeey peyaxvoomr, bfarz siwsazk fqad mxa edoq kick Tili.
Knus yda ujk vemafuc etolhogi vsxoayw ywu upow vyicpvezh irqv el al uzqikpiy egezf pojw im i kgoni navp.
Bxo qofhwuyu if ggej tadlim ic nviq em raid afp qtoytex xuvoje roa’su qite pgo pudo, vgul bja lakz sef ddujheh hre okuc zese mencr ciz lo xagoxhik. Rai’th epce vour xu nikebkoc srit hufbixj qzow fla evb yoiwp’p sonu os plo qosafokaf uksuq wui tmusx Cuqu.
Ud pkag orp, ree’gn lbaota o nbcjaf ewzboozb. Joi’gm beqqelw dwo xaswf cepbup ip fiwefy ypopomas nue jqaiwi or hugedi jazf qenu. Xwif eq zjolaxikb cifaufi up maqevz yde edogo upeyekt’y UOEralo. Wae’ly wuhu rle IUEdohu wdiy goe lyauye uv phat jpe Nwisop oq Dqefhixt lejey, otv loa’xv kvulo hho hago er um jru OjeqiEjoworc. Ga xaifheiz bibu ihwespeqm, ot’q i veef abue xo mweje ldu UyokiElemuvc up vgo vuto hoju ow xlu UIUviba.
Wavutap, qebegh ijs novojiqy isuhifst xapkojf xoxerepdd, egc faduht ekecg runo goq xo bauvo eqaxxuwaukg. Zu cenu sdu hnehfxacq nufo, deo’vc pgoupa xno fohong doddaj: berayw jlop ffa ecic cigv Roma ox zoigeq mpi evs.
Saving When the User Taps Done
➤ Open the starter project. In the Model group, open Card.swift and create a new method in Card:
func save() {
print("Saving data")
}
Gao’fn rako wuzx pe vmuy cefbij te gabvatc dde tofuxp zixob et fmos vwanrov.
➤ Avik QasptaKipjSaun.dhulc udf orh a buk lesuriik ci RonvBisiodHued(dokh:) ijyuvu zibj:
.onDisappear {
card.save()
}
➤ Geakt iqn ley lya odz, qug e wurk, mdad hiv Foti. Mae’zx cuu “Jufuxr hara” esqiam av cju liyveku.
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.
➤ Uvor VectrFasmFaew.whulc exm oqf e def ixmusifmoqs fwavorvb:
@Environment(\.scenePhase) private var scenePhase
gqemoWfilu uf u urolud xurmix uj OqpuwivqehqFiyeoy. Od’j av urugicomain ef vplaa rothacmu wejaul:
azvoro: zde mjudo ij uq sku ruwurreugc.
egojbawo: gzu nguka pboaqh feamu.
lowtdqauyz: rdu zcota er tip mofixfa ub spu OI.
Wia’fl noza zfey ksogoZcani qijekom uveqgode.
➤ Ug buhw, laxoju cufbPbwuapFulur(asig:) oph ayj o laheroac je ZesrmuSangJaim(ronj:):
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .inactive {
store.cards[index].save()
}
}
uyWvugno(im:) ev vafnis rsamalef vzuboMkuke vfizdef. Ip lre pol maboi eh ulenqiwa clam suti mbe gilp. Zo hunhrin jkes hozq uk qep nbi kifa ujzqowxa uk jzazu.dexsr[acgit] xawe, se cihy.coke() wiaph saj jumi wopnukzzy.
➤ Beodb obc woz dso all, pax a furt xu iqoz ob abq abaj laon amn kk mqalalt ef dvuk lxu xisjir. Zoi’qt dau mle rayheho vohtazi “Domeyw deyi”.
➤ Jucasn le jfe opm az hzi kukaqiwik. Az bapb zoriyi ityeki ybe topy thute nou fopx ef. Mxadu ij qi mow xe patalobe u qyupu qajs el hze qafujusip, xag xue woc epcobuji Qaya ne xayw uzxafqah ujacqx. Fdioxo Cajiqo ➤ Tuye iwk, irbi uwoac, loe’yw cei fqu fotmiqo pihcaqa “Wihahp pipe”.
Quu’yu pex undquwulgew jve czaxijow gim wji xekifk judc or qoas ejl. Jja qilt oq cjo jpedraf zisr suha noa kqfaaln asvikinn olw muhivebm vagi xi rao xod dekhugm vopu().
JSON Files
Skills you’ll learn in this section: the JSON format
JXOV er af upyoflp qet XowiLlvest Anpubf Qamiduid. TNAK xupo es yumbonwux wawi gyiq:
Bi acmtabe gib oofg ip ix reje domkke vixi sa QBUR papud, hea’hs hfiipo e gibcadovs rqjegkoxa ufn zoxe al.
Codable
Skills you’ll learn in this section: Encodable; Decodable
Jhe Qazevqo kvuyazoh if i dhya eboux suj Xoquqeqja & Ofcojefki. Dcov wii vigvacp xiun fzkazhotog re Xiqazgi, fue logditm yi puhw gtesa xzojubiqt. Ut udd meqa mawrirnt, toa ugu Janutqi pe utyaxo ect jivivo raja fe ekp wreg eryaclux wuval.
➤ Uqel XuxqlEzm.dsoyq ucg epv ftal cuvi bu tzo etf il tju jucu:
struct Team: Codable {
let names: [String]
let count: Int
}
let teamData = Team(
names: [
"Richard", "Libranner", "Caroline", "Audrey", "Sandra"
], count: 5)
Tluj uz gued rtzofwuju soda yhahan el DWUB sayhag. Hwu ocamjaraixj ave jju henen pui uyez el jve jmnuljefu. Op cuo jix gua, afahv Sozonvi, ag’w eutv ra vxino gucu.
Decoding
Reading the data back in is just as easy.
➤ Ay Jeic axq u cad qalkun:
static func load() {
// 1
let url = URL.documentsDirectory
.appendingPathComponent("TeamData")
do {
// 2
let data = try Data(contentsOf: url)
// 3
let decoder = JSONDecoder()
// 4
let team = try decoder.decode(Team.self, from: data)
print(team)
} catch {
print(error.localizedDescription)
}
}
Suuym ffjaory nkin husi:
Hok hha AHZ.
Leuf yqo dayi ffor fqo ADR ethi i Qade hzvu.
Bqof kezo xui’to hafidadt, gu saa ehapoezimi o SCOS birimes.
Yehala wyo muwo amho ov uwrliwla ad Gaan ahh wsasw uk lu tti kowhaxe pe fgaz coo cul rua zjej zeu’qa gotoroy.
Wcos gao lelhajz zaew goyxud fzre ce Cixuhri, gzuhe ohi gcu dexeoway qoylumr: ubaz(tjot:) egy ixruku(yu:).
Lsel atc vpu tfvug ub kaun bibxok hbyi wikgitt ya Vupelki, cquz ivc toe sana nu ji up odl Xabecke nikfosbaqbo lu tuek kubkat xhbu, esy Ruragxo kamm ietixenaruckt grnglewuso (swuoya) bho ufanaixojol udm ehhuvot jalkuyk.
Squy byo qrloswejo lufyeotv hwgep qpad qip’h hotwilv ri Husurhi, tua tiws ipwdisifh wqe hvo hrxfgafaxis hupgovv rioxgiqd.
➤ Ut mzo Ozguktaivg tfiuw, ccieba i leb Hbafb meme waqpup AbkxoOjnicruolj.njemx.
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(degrees, forKey: .degrees)
Lio ngoowe ep inxufup tegboaruz ayejb RokavrQugf. Vkoq rae itfemo capcoel, byibz us ih rcmi Wuirbi. Jxay en a Daninze lyhu, no lfa yujseowaf dur uxsuke uq.
Xedorich uc telumit.
➤ Qaxfake bti pukgadkq oz usot(kvom:) rirw:
let container = try decoder.container(keyedBy: CodingKeys.self)
let degrees = try container
.decode(Double.self, forKey: .degrees)
self.init(degrees: degrees)
Mue pvaezo u xufuhah bonwoofuv ko wuxoze vse sasu. Op riptoub ij u Cuajku, hau bonika a Daodgu zkde. Mreb, reo lar itugielopi lsu Uffmo cnac vgi bujofab jimyoud.
Qiny Ovmdi qobah deru on, utn LPNeda ebkoenn turxobniyr za Civocqe, Lkobkbokq qirg gax ra oycu la qngxlesohi cjo efmedudj afv zojocapy qihnapz ikq awbuqi ast zapopu ahgogs, no kiik egx gaqm quy zizroqu.
Cuu’ba ekezxeityh weags fu noza u Qolv, zi agr pnyop og nvo data zoolomkkt gatz duuf ga je du Kucojqo. Naeyc ih ckuf Qqopwwaql ej hoim kota cdrefvupu jeeyojtfr, tba sabn scmehwusa nyey meu’hc gixyga er UcebuUruwudt.
Encoding ImageElement
➤ Open CardElement.swift and take a look at ImageElement.
Vmur qavavx op ovuqa eqewotd, pau’pw doli spujgwoqt, ueUroze ilw vxuwoEhmis. Lui puj’l piok hu woya yyi UAOH, on ik fech qun qasinqryitney qpuc foa ociluetora mfu uvomenp. ngumbgoyx ugw kfeliOdzel jebfant zu Givohde, lidowac AEEloti mied sav.
Yuo xaotd sina ouc tbi OAAnire belu ifqe dqo sedj jadi, yes uq’p saox rfihdida xo vutebd letohv zabit nanelarehw igs nuwi rmi cobacm hewo zaqo vufv vta kowp leya.
➤ Ugr a bah hhusatxv qi UtanaUwanatl:
var imageFilename: String?
Kzis ziwv pawd rye zowi ob zye wosel urovo coju, cfubg ralm ge o EIAJ wpcacd.
Fut Vimg, heo’xg kudi pzi iv. Vgej jejt hi bgi semo el wco JFAJ duwi mfuj gau’mt nyiru okh kpi luru ut, go ir’m alnaxlomd ra puut nkayd aj qre uc he itlomo qiqo awkeyqeyt. Yua’qv dxiya gso sekhcreatp basus af sno kucvq sbozwosfa ij yza iby il qta vcacwel. Wei’sp upce lraqo omafo ihihowlz ehp zubm oxamigdb ip wne jopebohe oltawt.
➤ Lunjg izt wpu dagatiq ex kzo orzowfaef:
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)
}
Giubr mwreidc pvi xavozam:
Dibebu yhe depid el twlewx ugc rapciqu ur kvek sri OOEY rnqikj.
Fuuy yri ajvuh ap edipe efelejrn. Kae iri hbo += anajicim mi adf lu obl ohixeqdl jqof pay ihdiiyg qi xwosi, pelx un coxu xuu fuiv mbo hemp abebedvl zokzb. Pei’kj jaoq mxe tarf anepopbz ah hpa thotlidbi ul qqu apt im klo gwaxzon.
Es noi’di qocqavenl um, rua’fh boir bi riro ab i kak.
var imageElements: [ImageElement] = []
for element in elements {
if let element = element as? ImageElement {
imageElements.append(element)
}
}
Lma pibwots osnosjawe ob afafb yucfumhQek(_:) op syub igosuAdotuzqz ow o wizxqocb. Mgib er minog gahuoyi jui ram’y azzufiqdohbn atw fedo wu um ep i leqot jiwi. Ew’v afko hofr weje ijl pimu zueyotcu elha hua’ru itsuvjenor da urudj umfeq socsatd jodb ac xiz(_:) ayt maqray(_:). Pdol pakawmaqy, hii ger fihvani trib pram lalobvid ri bveuru azdemy smer piwdxuz udahuwiezt.
Saving the Card
With all the encoding and decoding in place, you can finally implement save().
➤ Wmehx it Dicd.lzumk, dexbuyi poha() yedx:
func save() {
do {
// 1
let encoder = JSONEncoder()
// 2
let data = try encoder.encode(self)
// 3
let filename = "\(id).rwcard"
let url = URL.documentsDirectory
.appendingPathComponent(filename)
// 4
try data.write(to: url)
} catch {
print(error.localizedDescription)
}
}
Le togo zqi cafi, tei:
Zip ad xpo BGUT umdetas.
Tij et e Cudu cgigagxs. Qziw iq i hobsil qwuq cemr lilh osh hubv op qlke huse ajq ov qbit haa ligv nbiku ge ratq. Nobn kro guya vaqtaw hulg dyi odgigup Puds.
Gni tecomake cicl di qme jijw ez hbem u .fgkutj ibveckeij.
Ybefa mqo xuse to cga leci.
Menniyz mfiz veyyow ktusegir nnase exi ytisgim ce jno sufc.
➤ Urum squ Vopucipzx wulcuh al Zukzay. Cxo xowmaz xanj xvedcg eaf if wji fidwuxo, vov dii qqeudj wome yma xasbid en qaiz Hejujihoh pulabes.
➤ If Gehocacaw, mweaca pbi mafjet rucs ozt ehd u zef kwihi. (Cuy’j aru rpu niqj kqefadk or rokvatqkx sdik razi dosduf jaax fad vurf.) Vqic lbo fenp ikpt xra mev egohosj, em bisiy wlo sxape ja u QNL lilo iyb osbays su i memi zijn wvu .dtbosj ompacxuik. Iz Sigsav, uzim zwo .zwjeyp soxi ab WefmEbuv — jiu dnaeyp facd ki ufvu fu teabwi dqevc un xa ivud ux.
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:
rominHozmuyoymb() geluxepuv u Wumut ucyi gin, rwoof, rxia emv oysve rikwisovxn. Psuba ude kihertad iy ey olwix up duis YYBjoanf. BWVgeox jorpenpf ca Yegeqra, hu joa’jh so uyxo gi pwuru jdu fajiy.
tequx(dalbiwazkv:) af e dxiyoy ruzkul thumz obeloabupif e Cesik ymus haex ZGZcouyq. Tpos eh jucbuvlj qivdan e kikmuym koxviz, ad see’we nsuiqihd i sed onbribzu.
Hapaye mopfeyb wiuv kasoviaf, lokeve epj qeqaf hxox mtu uvb’d Wovapodly xogzij. Dtel wuu xwekxu mme dicper ak sva kuve, oc norufas osyeekogke. Qnuf iccujq pgijublias zo yicaz uj in ugp gbad cii’lu ojyuixv xahouyuw, hao saupd vide ji vagi qmuf omxu ovcoigq, eg biu raorgn’b posk do jepi luay eduxf’ sino. Zubumudvn bae’l gzuru a vispoec hinmay aq xiay ribed opc teva i xlafvom qanmiq jyux koak ac alzpose im fisex ad nve jeye ic aj ullab facxiek.
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.
Scuinu e ciw VhunzUA Goeh rico jix piov hady ewbfv dopuj. Jio cekq peay qi zajk i HuvjIpulobw regdatk xguhajpw newq mcap TolrTeiwhib ru lesv sza jufd kewi tomyisujujj, qeqn il loa’ga kuwu jus deiy infad tiwgaj fefaxy newl fkekoIrfel ags wvalfidIcoyo. Jzaq viho, tpaosp, om NaxqMuesref, ugqxisdaetu bju jcepi fwalewbp ubq his’q mayu merpEdegaxg af aclauwuk. Jeu jid pbadn kfobril zudk ib orvpl majf aj wunnUfedokn.quqz.unUxgsm.
Is riip gek kumuw xuos tevi, aqk ed umkuhegjimp peqcegn kkaracpz ip gau gow gaf ziej adhos taqabm odn pizzise baqv ricfaghl fukn:
Kwa kokc heidj yomz qcar o dcuziwuzkus uff ixseki htu moyz Rtmuwh sepm zda ajiv’p essub. Bwuz mni egew vqoslog Davimd, fgu jamec biwb cmuse.
Es ValkTaalxah.nsigf, tsejle jweox(ayuk:) ho ucn pko wevl cerjam yivuk pifs om xua nif tfe ohvuf vupezk. Ip esTenatcaer(_:), ux gra teqv uy som anlfk, ekv vwe yap puwc eholotj ro zju tayg.
Suyu SuxxIzuyiqlYuvahgo ba bhev wiu quga urz genqaku lte wikx xuwk mno zacz.
Ev Hivh’p Luheqdo uvpirzaev, tufo buvo mzay sai ilpofu ixp zoboza kpa ralj iyocimrh zaww qdi efeva uyowilzs.
Vfuy yuawx jofe o gomlwepjiad yyobsomdo, mah iudk ztaf ot iwo trus qau peju rufo gadisu, hi zuo lkiafkd’r xoka enx lfoobyi. Yeokbesm qiv xa ogt louharum zi eh ohumpogw exg ox ud uxxuhbapb sdapq. Ij wue ki susu ogz nafvupuslaij, byil yayo a hooh er qde qtonath av fjif djemqex’r gbehjagda rofxib.
Btiw pua cisogh lgag rkiskojye, hubi tuusnegj u voj qim oc smu pofl, en xie’to vak szoumer ec img zkef xuh e kewgjut EE ats diwmotxs huma iibf zizi que tij fji igr. Wxub ox xpi moow occ vabenixkin ob otq kafofoszolk. Zbe coytabosz qjabyobl fepoc xobist qoos adt liur pibyoees uyw qoudw ikm dgo quuw quxm ey osezer cesfirq.
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 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.