You are on page 1of 9

7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects
PublishedApr28,2015LastupdatedJan18,2017

PersistingComplexObjects
Inmypreviousarticle,,wecoveredthebasicsofhowtopersistdatausingNSUserDefaults.

Nowwe'llseehowtopersistcustomclassesthatwecreate.Theseclasseswillbecontainedinanarray.We'llsavetheentirearraytoNSUserDefaults.

BookkeepingApp
Inthisarticle,we'llcreateasimpleregisterappthattracksmonetarytransactions.Theuserwillentereitherapositiveornegativevalue.Therewillbeanamountand
description.Theappwillopenscene1withatableviewoftransactions.Theusercanclicktodisplayasecondsceneforenteringintransactions.

Atransactionclasswillbecreatingtoholdthedescriptionandamount.

BuildingTheApp
Tocreatetheapp,startanewprojectwithaSingleViewApplication.SetthelanguageasSwiftandDeviceasiPhone.OpentheMain.storyboardandchangethe
viewcontrollertoa4.7iPhonefromtheIdentityInspector.
BrettRomero
Follow
Searchpost
Writeapost
SIGNUP
BrettRomero
Follow

Enjoythispost?GiveBrettRomeroalikeifit'shelpful.

SinceweareonlycreatingfortheiPhone,wewantaviewcontrollersizethatismorerealisticthanthedefaultsquare.

Clicktheviewanddeleteit.FromtheObjectLibrary,dragatableviewontotheviewcontroller.Yourdocumentoutlineshouldlooklikethefollowing:

Wecannowaddoursecondscene.FromtheObjectLibrary,draginaviewcontrollerandsizeittoiPhone4.7.Nowaddasecondviewcontroller.Simplydragone
fromtheObjectLibraryontothestoryboard.

Thisappwillbenavigationcontrollerbased.Therewillbeanavigationbaratthetopofeachscene.Tocreatethenavigationcontroller,clickonthefirstview
controllerandinthetopmenu,clickEditor>EmbedIn>NavigationController.Yourstoryboardwillthenlooklikethefollowing:

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 1/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

Itdoesn'tmatterwhatthefirstnavigationcontrollerlookslike.Itwon'tbeseen.Intheabovescreenshot,thereisanarrowgoingfromthenavigationcontrollertothe
firstscene.Wedon'tyethaveanarrowtooursecondscenesothereisnowaytoreachit.We'llfixthatnow.

SegueingAndNavigation
Tocreateatransitionfromscene1toscene2,addaBarButtonItemtoscene1'snavigationbar.ThensetitsstyletoAdd.

ControlclickontheAddbuttonanddragtotheviewonscene2.ThiswillcreateanActionSeguepopup.Selectshow.

Thiswillcreateaseguearrowbetweenscene1andscene2.

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 2/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

Let'sgiveeachviewcontrolleraname.Inscene1,clickthenavigationitemandprovideanameintheIdentityInspector.YoucanseebelowthatI'vechosen
"Bookkeeper":

Forscene2,weneedtoaddsomenavigationrelatedcomponents.IntheObjectLibrary,typein"navigation".SelectNavigationItemanddragitintothenavigation
sectionoftheviewcontroller.ProvideatitlefortheNavigationItem,justaswedidforscene1.I'vegonewith"AddTransaction".

Ifyouruntheapp,youshouldbeabletoclickthe"+"andtransitionfromscene1toscene2.

TransactionClassStructure
WecannowbuildoutourTransactionclass.RightclickonthebookkeeperfolderintheProjectNavigatorandselectNew.ChooseCocoaTouchclass.Provideaname
ofTransactionandleavesubclassasNSObject.

Weneedtoprovideaninitmethod.Thismeanswe'llalsohavetooverridetheoriginalinitmethod.Typeinthefollowingtodothis:

init(description:String,amount:String){
super.init()
self.transDescription=description
self.amount=amount
}

ThisallowscreationoftheTransactionclassusingsyntaxsuchas:
varmytrans=Transaction(description:"trans1",amount:"$4.50")

We'llalwaysneedtoprovidevaluestoourinitmethod.Thatshouldn'tbeaprobleminthissimpleapp,sinceitisaverycontrolledenvironment.Theonlyplacewe'll
becreatingtransactionsisintheaddscene.

Wecancreatetwopropertiesforourdescriptionandamount.Wecan'tactuallyusedescriptionasapropertysinceitisbeingusedbyNSObject.Instead,we'lluse
transDescription.Ouramountwillbeastringsincewearen'tdoinganycomputationsonit.

vartransDescription:String?
varamount:String?

TransactionClassEncoding/Decoding
Therearetwokeycomponentsforserializing(i.e.,saving)complexobjectswithNSUserDefaults.Oneistoaddingencoding/decodingtotheclassthatwe'resavingand
theotherisarchivingandunarchivingcallsusingNSUserDefaults.

We'regoingtoworkontheencoding/decodingpartfirst.TheTransactionclassneedstousetheNSCodingprotocol.AddtheNSCodingprotocoltoyourclass
declaration:

classTransaction:NSObject,NSCoding{

Nowweneedtoencode/decodeourclassproperties.Thefollowingcodewilldothis:
requiredinit(coderaDecoder:NSCoder){
iflettransDescriptionDecoded=aDecoder.decodeObjectForKey("transDescription")as?String{
self.transDescription=transDescriptionDecoded
}
ifletamountEncoded=aDecoder.decodeObjectForKey("amount")as?String{
self.amount=amountEncoded
}
}

funcencodeWithCoder(aCoder:NSCoder){
iflettransDescriptionEncoded=self.transDescription{
aCoder.encodeObject(transDescriptionEncoded,forKey:"transDescription")
}
ifletamountEncoded=self.amount{
aCoder.encodeObject(amountEncoded,forKey:"amount")
}
}

Let'sdiscussthiscode.Therearetwomethods:

requiredinit(coderaDecoder:NSCoder)

and

funcencodeWithCoder(aCoder:NSCoder)
https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 3/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor
funcencodeWithCoder(aCoder:NSCoder)

BothofthesemethodsignaturesarefollowingtheNSCodingprotocol.Fromtheirnames,youcantellwhattheyaredoing.initdecodestheproperties,whichisused
whenweretrievethemfromNSUserDefaults.encodeWithCoderwillencodethedata,whichwedowhensavingtoNSUserDefaults.

Let'sexaminetheinnerworkingsoftheinitmethod,whichwillalsodescribetheencodemethodsincetheyareverysimilar.

Thefollowinglooksupourpropertyasastringparameter.Thisstringisjustakeythatisassociatedtotheactualpropertyvalue.

iflettransDescriptionDecoded=aDecoder.decodeObjectForKey("transDescription")as?String{
self.transDescription=transDescriptionDecoded
}

ThepropertyvalueisthenassignedtotransDescriptionDecodedonlyifthereisavalue.That'swhytheoptional("?")isused.Itisunwrappingthevalue(i.e.checksif
theresavaluestoredandtakesthatvalue).Onlyifthereisavaluedowegointotheconditionalandassignthatvaluetoourclassproperty.Thesameisthendonefor
theamountproperty.

ReferencingTheArrayOfTransactions
Inordertoreferenceourarrayoftransactionsthroughouttheapp,we'regoingtocreateastaticclasstoholdthem.

Addanothernewfiletotheproject.ItwillagainbeaCocoaTouchClasswiththenameTransactionManagerandtypeisNSObject.Addthefollowingsinglelineto
declareastaticarrayoftypeTransaction:

classTransactionManager:NSObject{
staticvartransactions=[Transaction]()
}

NowwecaneasilyaccessthearrayofTransactioninstancesanywhereintheappwithonelineofcode:

TransactionManager.transactions

PopulatingTheTableview
BeforewecanbeginpopulatingtheTableview,weneedtomodifyourscene1viewcontroller.We'regoingtochangeitintoaUITableview.

ChangetheclasstypefromUIViewControllertoUITableViewController.WeneedtoadheretotheUITableViewControllerprotocols.Addthefollowingtwo
methods.IhavealsofilledtheseinwiththenecessarycodetoretrievevaluesfromtheTransactionarray.

overridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)>UITableViewCell{
letcell=tableView.dequeueReusableCellWithIdentifier("mycell")as!UITableViewCell
vartransaction=TransactionManager.transactions[indexPath.item]
cell.textLabel!.text="\(transaction.transDescription)[\(transaction.amount))"
returncell
}

overridefunctableView(tableView:UITableView,numberOfRowsInSectionsection:Int)>Int{
returnTransactionManager.transactions.count
}

Onceyoubegintypingtableview,hintsshouldappear.Clickenterwhenyouseeoneoftheaboveandthehintwillfilloutthecode.

In

cellForRowAtIndexPath

wedequeueatablecellandreuseit.ThenweaccessthetransactionarrayusingtheindexvaluefromindexPath.item.Thevaluecomingoutofthearrayisassignedtoa
transactionvariable.Thevaluesfromthistransactionareassignedtothetextfieldofthecell.Finally,wereturnthecellbacktothetableviewfordisplay.

numberOfRowsInSection

simplyreturnstheTransactionarraycount.

WecannowpopulatetheTransactionarraywithstubdata.Addthefollowingtoscene1'sviewDidLoad()method:

vararray=[
Transaction(description:"trans1",amount:"$4.50"),
Transaction(description:"trans2",amount:"$24.00"),
Transaction(description:"trans3",amount:"$11.00"),
]
TransactionManager.transactions=array

Thisstubdatawillgoawayoncescene2iswiredupandtheusercanenterindata.

WiringUpTheTableview
Ifweruntheappnow,westillwon'tseeanythinginthetableview.Therearefourthingswemustdofirstforthetableview:

Addadatasource
Addadelegatereference
CreateanIBOutletforthetableview
Supplyacellidentifierthatmatchestheonein

cellForRowAtIndexPath

ThefirstthreeitemsonthislistwillbedoneinInterfaceBuilder.Sincewearen'tusingaprototypecell,thelastmustbedoneincode.

Let'sgobacktoourMain.storyboard.Inscene1,clickthetableview.IntheConnectionsInspector,dragfromdatasourcetothetableview.Dothesamefordelegate.

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 4/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

Oncecompleted,youroutletsshouldlooklikethefollowing:

NowwecancreatethetableviewIBOutlet.OpentheAssistantEditor.ItshouldopentotheViewControllerclassassociatedwithscene1.Controldragfromthe
tableviewtothetopofviewDidLoad()intheViewControllerclass.ThiswillcreateanIBOutletpopup.Supplythenametableview.

Thefinalstepistotellthetableviewwhatourcellidentifierstringis.Thismustbedoneincodesincewearen'tusingaprototypetableviewcell.InviewDidLoad(),add
thefollowingabovethearraycode:

tableview.registerClass(UITableViewCell.self,forCellReuseIdentifier:"mycell")

Youcanseewe'reusingthetableviewIBOutletthatwasjustcreated.That'swhywesavedthispartforlast.

Ifyouruntheapp,itshouldlooklikethefollowing:

ToremovethewordOptional,useforcedunwrappingforeachvalue:
cell.textLabel!.text="\(transaction.transDescription!)[\(transaction.amount!))"

Noticethebang("!").WearetellingSwiftthatthesevaluesareknownandnotnil.Ifyouruntheappnow,theOptionalkeywordwillnolongerbethere.

AddTransactionsScene
Ouraddviewdoesn'thaveanassociatedclass,whichmeansthereisn'tanywhereforustoputourcodeforthisview.Wecanaddaclassnow.AddanewCocoaTouch
Classfile.GiveitthenameAddViewControllerandsubclassofUIViewController.

Gobackintothestoryboard.ClicktheAddViewViewControllericoninthesceneandopentheIdentityInspector.ChoosetheAddViewControllerclass.

WenowhaveaplacetotypeinourAddViewcode.WeneedtocreateafewIBOutletsforourfieldsandbutton.Controlclickdragfromeachinputfieldtothe
https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 5/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor
WenowhaveaplacetotypeinourAddViewcode.WeneedtocreateafewIBOutletsforourfieldsandbutton.Controlclickdragfromeachinputfieldtothe
AddViewControllerclass,creatingtwoIBOutletswiththenamestransactionDescriptionandamount.
@IBOutletvartransactionDescription:UITextField!
@IBOutletvaramount:UITextField!

Justlikebefore,wecan'tusethekeyworddescriptionsowemakeasubtlechangetothename.

CreateanIBActionforthebuttonandcalleditsaveButton_click:

SavingData
AlloftheUIisbuiltoutatthispoint.WecansaveourdataintotheTransactionManager'sarrayandthensavethisarrayintoNSUserDefaultsthroughthesavebutton.

AddthefollowingtothesaveButton_clickfunctionbody:

vartransaction=Transaction(description:transactionDescription.text,amount:amount.text)
TransactionManager.transactions.append(transaction)

letdefaults=NSUserDefaults.standardUserDefaults()
letmyData=NSKeyedArchiver.archivedDataWithRootObject(TransactionManager.transactions)
NSUserDefaults.standardUserDefaults().setObject(myData,forKey:"transactionsarray")
defaults.synchronize()

ThefirstthingthathappensiswecreateaTransactioninstanceusingthetwoinputfieldsontheaddview.Next,weinitializetheNSUserDefaultssingleton.Toavoid
typingthiscodeagain,weassignthesingletoninstancetoaconstant.

NSKeyedArchiver.archivedDataWithRootObject()takesourarrayandsetsitupforNSUserDefaultscompatibility.Thenwetakethisformatteddataandsaveitto
NSUserDefaults.NSUserDefaultsisbasicallyadictionarysoweprovideakeyvalueoftransactionsarray,whichisthesamekeywe'llusetoretrieveourdatalateron.

ReadingSavedData
ToreadbackdatafromNSUserDefaults,inViewController,removethetemporaryarraycodeweaddedtoviewDidLoad().ThenaddthefollowingtotheviewDidLoad():
iflettransactionsRaw=NSUserDefaults.standardUserDefaults().dataForKey("transactionsarray"){
iflettransactions=NSKeyedUnarchiver.unarchiveObjectWithData(transactionsRaw)as?[Transaction]{
TransactionManager.transactions=transactions
}
}

Let'swalkthroughthiscode.ThefirstlineretrievesourvaluefromNSUserDefaultsbysupplyingthekeytransactionsarray.Ifthevalueisnotnil,wetaketheraw
valueandtransformitintousabledata,castingittoaTransactionarray.ThisarrayisthenassignedtoourTransactionManagerarray,whereitisnowavailablethrough
outtheapplication.

Asafinaltouchtoensurewealwayshavethelatestdatainourtableviewaftertheuseraddsatransaction,we'regoingtoreloaddataintothetablevieweachtimescene
1isdisplays:

overridefuncviewDidAppear(animated:Bool){
super.viewDidAppear(animated)
tableView.reloadData()
}

Nowyoushouldbeabletoaddanentryandhaveitimmediatelydisplayinthetableview.Ifyoushutdowntheapp,nexttimeitloads,thearrayoftransactionswillbe
retrievedfromNSUserDefaultsanddisplayed,persistingtheuser'sdatafromoneappsessiontoanother.

Summary
Inthisarticle,wesawhowtomakeacustomclassserializable,allowingittobesavedinNSUserDefaults.ThiswasdoneusingtheNSCodingprotocols.Serializingthis
datameansitisavailablefromoneappsessiontotheother,creatingdataintegrityforourappandprovidingagreatuserexperience.

HNSubmission/Discussion
iOSnsuserdefaultsobjectsswift
Report

Enjoythispost?GiveBrettRomeroalikeifit'shelpful.

Share
BrettRomero
Softwareengineerandstartupfounder.Ihaveexperiencein.NET/C#,HTML/CSS,iOSandWordpress.I'mconstantlyexpandingmyskillset.IalsohaveanMBA
fromArizonaStateUniversity.
Follow
Bethefirsttoshareyouropinion
Leaveareply

**bold***italics*>quote`inlinecode````codeblock```
submit
DiscoverandreadmorepostsfromBrettRomero
getstarted
Enjoythispost?

LeavealikeandcommentforBrett

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 6/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

Subscribetoourweeklynewsletter

Youremail subscribe
CodyChilders

Replacingthis.functionName=this.functionName.bind(this)inReactjs
PublishedJul08,2017

Thispostwillexplainhowtoreplacethoserepetitivethis.functionName=this.functionName.bind(this)withsomegoodcodethatkeepscomplicatedcomponents
clean.

AfteraskingabouthowtofixthisinafewcommentsectionsonStackOverflow,Iwasnotreceivingusefulhelp,onlycommentslike"that'sjustpartofReact".Idonot
feelthisboilerplateineveryconstructorisuseful,butinsteadaflaw.

IeventuallydecidedtotrysubclassingReact'sComponentanditerateoveralistoffunctionnamesasstringstobindthis,becausethatiswhatIwoulddoinPython.

Luckily,nowthatES6hasarrived,thisiseasytodoinJavascripttoo.

First,IGoogled"es6subclass".YoushouldseetheofficialMozilladocsonclassescomeupinthetop5hitslikeClassesJavaScript|MDN.Abouthalfwaydown,it
says"Subclassingwithextends"andgivesanexample.

InanyReactprojectyoumayhave,makeafilecalledsomethinglikeMyComponent.js.WewillneedtoimportthestandardReactComponentclassaswell.Sofar,we
have:

MyComponent.js:

importReact,{Component}from'react';

classMyComponentextendsComponent{

Let'slookatatypicalReactcomponent:

SearchBar.js

importReact,{Component}from'react';

classSearchBarextendsComponent{
constructor(props){
super(props);
this.state={term:''};
this.handleFormSubmit=this.handleFormSubmit.bind(this);
this.onInputChange=this.onInputChange.bind(this);
this.processInput=this.processInput.bind(this);
this.sendSearchResults=this.sendSearchResults.bind(this);
}

render(){
...
}
}

Wecanseethatourgoalwiththissubclassistooverridetheconstructor,wherethecustomfunctionsareregisteredandboundwiththis.Weknowthatourgoalisto
simplifythecodebypassinginanarraylike

letcustom_methods=[
'handleFormSubmit',
'onInputChange',
'processInput',
'sendSearchResults'
];

Nowwehave:

MyComponent.js:

importReact,{Component}from'react';

classMyComponentextendsComponent{
constructor(props,custom_methods){

}
}

Languagesthathaveclassesnormallyhaveasuperbuiltinfunction,orsomethingsimilar,thatensurestheoriginalcodeintheclasswe'reextendingisrun.Inother
words,wewanttooverridetheComponentclass'sconstructorfunction,butwedon'twanttobreakourReactappbylosingallthebuiltinthingsComponentprovidesfor
us.So,justlikewedoinanormalReactComponent,addsuper:

MyComponent.js:

importReact,{Component}from'react';

classMyComponentextendsComponent{
constructor(props,custom_methods){
super(props);
}
}
https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 7/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor
}

Nowlet'sbindthistoourcustommethods,byiteratingwithmap:

MyComponent.js:

importReact,{Component}from'react';

classMyComponentextendsComponent{
constructor(props,custom_methods){
super(props);
custom_methods.map((method_name)=>{
this[method_name]=this[method_name].bind(this)
});
}
}

Wehaveaproblemherethough,thatthiscodewillbreakanytimeauserdoesn'thavecustommethodstopassin.Ifthisconstructoriscalledwithoutthesecond
argument,custom_methodswillbeundefined,andtheerrorwilloccurwhenmaptriestorun.Toseethis,runthefollowingcode:
varunmappable=undefined;
unmappable.map(function(item){console.log(item)})

Youget:
UncaughtTypeError:Cannotreadproperty'map'ofundefined
at<anonymous>:2:11

Topreventthis,wecanusethenewvariabledefaultsyntaxthatshouldlooksimilartootherlanguages.Defaultcustom_methodstoanemptyarray:

MyComponent.js:

importReact,{Component}from'react';

classMyComponentextendsComponent{
constructor(props,custom_methods=[]){
super(props);
custom_methods.map((method_name)=>{
this[method_name]=this[method_name].bind(this)
});
}
}

Nowwehaveourfinalizedcomponent.Let'srefactorthesearchbartoseehowtouseit:

SearchBar.js

importMyComponentfrom'./MyComponent';

classSearchBarextendsMyComponent{
constructor(props){
letcustom_methods=[
'handleFormSubmit',
'onInputChange',
'processInput',
'sendSearchResults'
];
super(props,custom_methods);
this.state={term:''};
}

render(){
...
}
}

Noticethesupercallpasses2arguments,whereasweusuallyonlypasspropstosuperinReactcomponents.ThisisbecauseSearchBarextendsMyComponent,andsuper
callstheparentclass'sfunctionofthesamename.Sohere,sincesuperisinsideamethodnamedconstructor,thesuper(props,custom_methods)calliscallingthe
constructorfunctionofMyComponent,whichtakes2arguments,props,andtheoptionalcustom_methods.

Comparereadingtheabovetotheoldstyle:

importReact,{Component}from'react';

classSearchBarextendsComponent{
constructor(props){
super(props);
this.state={term:''};
this.handleFormSubmit=this.handleFormSubmit.bind(this);
this.onInputChange=this.onInputChange.bind(this);
this.processInput=this.processInput.bind(this);
this.sendSearchResults=this.sendSearchResults.bind(this);
}

render(){
...
}
}

Thismakesmycodeslightlyeasiertomaintain,butmostimportantly,thispatternmakesmycustomcomponentsmucheasiertoreadandunderstandataglance.Ihope
you'llusethispatterninyourReactappsalso.

HNSubmission/Discussion
reactjsJavaScriptes6Classes
Report

Enjoythispost?GiveCodyChildersalikeifit'shelpful.

2
4
Share
CodyChilders
Follow

4Replies
Leaveareply

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 8/9
7/16/2017 MakingaBookkeepingAppwithNSUserDefaultsandComplexObjects|Codementor

**bold***italics*>quote`inlinecode````codeblock```
submit
RicoAlexander

4daysago

Nicearticle!Curious.IusetypescriptandusehandleOnClick=()=>{dosomething}whensettingfunctionhandlers.Isthereaproblemwithdoingitthisway?

Reply
CodyChilders
4daysago

ImnotsureInormallyuseReact,butthiswillhelphttps://rainsoft.io/whennottousearrowfunctionsinjavascript/.IfyouredefiningitonaclassfromwhatlittleI
knowoftypescriptIdontseeanissue

Reply
MatthewBrown
5daysago

Greatarticle!IhavejustbeengettingintoReactandwaswonderingiftherewasabettersolutiontoaddingallthose.bindsintheconstructor.Lookslikeyoursolution
willdothetrick.Thanksforsharing

Reply
CodyChilders
5daysago

GladyoulikeditMatthew

Reply
Showmorereplies

Shareinsightsfromyoursoftwaredevelopmentjourney

writeapost

PriyankaDwivedi

IsGoogleTensorflowObjectDetectionAPItheeasiestwaytoimplement
imagerecognition?

https://www.codementor.io/brettr/persistingdatawithnsuserdefaultsandcomplexobjectsdu107m6ja 9/9

You might also like