Professional Documents
Culture Documents
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