11
SubclassingUITableViewCell
AUITableViewdisplaysalistofUITableViewCellobjects.Formanyapplications,
thebasiccellwithitstextLabel,detailTextLabel,andimageViewis
sufficient.However,whenyouneedacellwithmoredetailoradifferentlayout,you
subclassUITableViewCell.
Inthischapter,youwillcreateasubclassofUITableViewCellnamedItemCell
thatwilldisplayIteminstancesmoreeffectively.Eachofthesecellswillshowan
Item’sname,itsvalueindollars,anditsserialnumber(Figure11.1).
Figure11.1Homepwnerwithsubclassedtableviewcells
YoucustomizetheappearanceofUITableViewCellsubclassesbyaddingsubviewsto
itscontentView.AddingsubviewstothecontentViewinsteadofdirectlytothecell
itselfisimportantbecausethecellwillresizeitscontentViewatcertaintimes.For
example,whenatableviewenterseditingmode,thecontentViewresizesitselfto
makeroomfortheeditingcontrols(Figure11.2).Ifyouaddedsubviewsdirectlytothe
UITableViewCell,theseeditingcontrolswouldobscurethesubviews.Thecellcannot
adjustitssizewhenenteringeditmode(itmustremainthewidthofthetableview),but
thecontentViewcanresize,anditdoes.
Figure11.2Tableviewcelllayoutinstandardandeditingmode
CreatingItemCell
CreateanewSwiftfilenamedItemCell.InItemCell.swift,defineItemCellas
aUITableViewCellsubclass.
importFoundation
importUIKit
classItemCell:UITableViewCell{
}
TheeasiestwaytoconfigureaUITableViewCellsubclassisthroughastoryboard.In
Chapter9,yousawthatstoryboardsfortableviewcontrollershaveaPrototypeCellssection.
ThisiswhereyouwilllayoutthecontentfortheItemCell.
OpenMain.storyboardandselecttheUITableViewCellinthedocumentoutline.Open
itsattributesinspector,changetheStyletoCustom,andchangetheIdentifiertoItemCell.
Nowopenitsidentityinspector(the tab).IntheClassfield,enterItemCell
(Figure11.3).
Figure11.3Changingthecellclass
Changetheheightoftheprototypecelltobeabout65pointstall.Youcanchangeiteither
onthecanvasorbyselectingthetableviewcellandchangingtheRowHeightfromitssize
inspector.
AnItemCellwilldisplaythreetextelements,sodragthreeUILabelobjectsontothe
cell.ConfigurethemasshowninFigure11.4.Makethetextofthebottomlabelaslightly
smallerfontinalightshadeofgray.
Figure11.4ItemCell’slayout
Addconstraintstothesethreelabelsasfollows.
1. Selectthetop-leftlabelandopentheAutoLayoutPinmenu.Selectthetopand
leftstrutandthenclickAdd2Constraints.
2. Youwantthebottom-leftlabeltoalwaysbealignedwiththetop-leftlabel.
Control-dragfromthebottom-leftlabeltothetop-leftlabelandselectLeading.
3. Withthebottom-leftlabelstillselected,openthePinmenu,selectthebottom
strut,andthenclickAdd1Constraint.
4. SelecttherightlabelandControl-dragfromthislabeltoitssuperviewonitsright
side.SelectbothTrailingSpacetoContainerMarginandCenterVerticallyinContainer.
5. Selectthebottom-leftlabelandopenitssizeinspector.FindtheVerticalContent
HuggingPriorityandloweritto250.LowertheVerticalContentCompressionResistance
Priorityto749.YouwilllearnwhattheseAutoLayoutpropertiesdoinChapter12.
6. Yourframesmightbemisplaced,soopentheResolveAutoLayoutIssuesmenuand
updatetheframesforthethreelabels.
ExposingthePropertiesofItemCell
ForItemsViewControllertoconfigurethecontentofanItemCellin
tableView(_:cellForRowAtIndexPath:),thecellmusthavepropertiesthat
exposethethreelabels.Thesepropertieswillbesetthroughoutletconnectionsin
Main.storyboard.
Thenextstep,then,istocreateandconnectoutletsonItemCellforeachofits
subviews.
OpenItemCell.swiftandaddthreepropertiesfortheoutlets.
importUIKit
classItemCell:UITableViewCell{
@IBOutletvarnameLabel:UILabel!
@IBOutletvarserialNumberLabel:UILabel!
@IBOutletvarvalueLabel:UILabel!
}
YouaregoingtoconnecttheoutletsforthethreeviewstotheItemCell.When
connectingoutletsearlierinthebook,youControl-draggedfromviewcontrollerinthe
storyboardtotheappropriateview.ButtheoutletsforItemCellarenotoutletsona
controller.Theyareoutletsonaview:thecustomUITableViewCellsubclass.
Therefore,toconnecttheoutletsforItemCell,youwillconnectthemtothe
ItemCell.
OpenMain.storyboard.Control-clickontheItemViewCellinthedocumentoutlineand
makethethreeoutletconnectionsshowninFigure11.5.
Figure11.5Connectingtheoutlets
UsingItemCell
Let’sgetyourcustomcellsonscreen.InItemsViewController’s
tableView(_:cellForRowAtIndexPath:)method,youwilldequeuean
instanceofItemCellforeveryrowinthetable.
NowthatyouareusingacustomUITableViewCellsubclass,thetableviewneedsto
knowhowtalleachrowis.Thereareafewwaystoaccomplishthis,butthesimplestway
istosettherowHeightpropertyofthetableviewtoaconstantvalue.Youwillsee
anotherwaylaterinthischapter.
OpenItemsViewController.swiftandupdateviewDidLoad()tosetthe
heightofthetableviewcells.
overridefuncviewDidLoad(){
super.viewDidLoad()
//Gettheheightofthestatusbar
letstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)
tableView.contentInset=insets
tableView.scrollIndicatorInsets=insets
tableView.rowHeight=65
}
NowthatyouhaveregisteredtheItemCellwiththetableview(usingtheprototype
cellsinthestoryboard),youcanaskthetableviewtodequeueacellwiththeidentifier
“ItemCell.”
InItemsViewController.swift,modify
tableView(_:cellForRowAtIndexPath:).
overridefunctableView(tableView:UITableView,
cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{
//Getaneworrecycledcell
letcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",
forIndexPath:indexPath)
letcell=tableView.dequeueReusableCellWithIdentifier("ItemCell",
forIndexPath:indexPath)as!ItemCell
//Setthetextonthecellwiththedescriptionoftheitem
//thatisatthenthindexofitems,wheren=rowthiscell
//willappearinonthetableview
letitem=itemStore.allItems[indexPath.row]
cell.textLabel?.text=item.name
cell.detailTextLabel?.text="$\(item.valueInDollars)"
//ConfigurethecellwiththeItem
cell.nameLabel.text=item.name
cell.serialNumberLabel.text=item.serialNumber
cell.valueLabel.text="$\(item.valueInDollars)"
returncell
}
First,thereuseidentifierisupdatedtoreflectyournewsubclass.Thecodeattheendof
thismethodisfairlyobvious–foreachlabelonthecell,setitstexttosomeproperty
fromtheappropriateItem.
Buildandruntheapplication.Thenewcellsnowloadwiththeirlabelspopulatedwiththe
valuesfromeachItem.
DynamicCellHeights
Currently,thecellshaveafixedheightof65points.Itismuchbettertoallowthecontent
ofthecelltodriveitsheight.Thatway,ifthecontenteverchanges,thetableviewcell’s
heightcanchangeautomatically.
Youcanachievethisgoal,asyouhaveprobablyguessed,withAutoLayout.The
UITableViewCellneedstohaveverticalconstraintsthatwillexactlydeterminethe
heightofthecell.Currently,ItemCelldoesnothavesufficientconstraintsforthis.You
needtoaddaconstraintbetweenthetwoleftlabelsthatfixestheverticalspacingbetween
them.
OpenMain.storyboard.Control-dragfromthenameLabeltothe
serialNumberLabelandselectVerticalSpacing.
NowopenItemsViewController.swiftandupdateviewDidLoad(_:)totell
thetableviewthatitshouldcomputethecellheightsbasedontheconstraints.
overridefuncviewDidLoad(){
super.viewDidLoad()
//Gettheheightofthestatusbar
letstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)
tableView.contentInset=insets
tableView.scrollIndicatorInsets=insets
tableView.rowHeight=65
tableView.rowHeight=UITableViewAutomaticDimension
tableView.estimatedRowHeight=65
}
UITableViewAutomaticDimensionisthedefaultvalueforrowHeight,sowhile
itisnotnecessarytoadd,itisusefulforunderstandingwhatisgoingon.Settingthe
estimatedRowHeightpropertyonthetableviewcanimproveperformance.Instead
ofaskingeachcellforitsheightwhenthetableviewloads,settingthispropertyallows
someofthatperformancecosttobedeferreduntiltheuserstartsscrolling.
Buildandruntheapplication.Theapplicationwilllookthesameasitdidbefore.Inthe
nextsection,youwilllearnaboutatechnologycalledDynamicTypethatwilltake
advantageoftheautomaticallyresizingtableviewcells.
DynamicType
Creatinganinterfacethatappealstoeveryonecanbedaunting.Somepeopleprefermore
compactinterfacessotheycanseemoreinformationatatime.Othersmightwanttobe
abletoeasilyseeinformationataglance,orperhapstheyhavepooreyesight.Inshort:
peoplehavedifferentneeds.Gooddevelopersstrivetomakeappsthatmeetthoseneeds.
DynamicTypeisatechnologythathelpsrealizethisgoalbyprovidingspecifically
designedtextstylesthatareoptimizedforlegibility.Userscanselectoneofseven
preferredtextsizesfromwithinApple’sSettingsapplication(plusafewadditionallarger
sizesfromwithintheAccessibilitysection),andappsthatsupportDynamicTypewillhave
theirfontsscaledappropriately.Inthissection,youwillupdateItemCelltosupport
DynamicType.Figure11.6showstheapplicationrenderedatthesmallestandlargest
user-selectableDynamicTypesizes.
Figure11.6ItemCellwithDynamicTypesupported
TheDynamicTypesystemiscenteredaroundtextstyles.Whenafontisrequestedfora
giventextstyle,thesystemwillconsidertheuser’spreferredtextsizeinassociationwith
thetextstyletoreturnanappropriatelyconfiguredfont.Figure11.7showsthesix
differenttextstyles.
Figure11.7Differenttextstyles
OpenMain.storyboard.Let’supdatethelabelstousethetextstylesinsteadoffixed
fonts.SelectthenameLabelandvalueLabelandopentheattributesinspector.Clickonthetext
icontotherightofFont.ForFont,chooseTextStyles-Body(Figure11.8).Repeatthesame
stepsfortheserialNumberLabel,choosingtheCaption1textstyle.
Figure11.8Changingthetextstyle
Nowlet’schangethepreferredfontsize.YoudothisthroughtheSettingsapplication.
Buildandruntheapplication.PresstheHomebutton(oruseHomefromtheHardware
menu)andopenApple’sSettingsapplication.UnderGeneral,selectAccessibilityandthen
LargerText.(Onanactualdevice,thismenuisaccessedinSettingsunderDisplay&Brightness
andthenTextSize.)Dragthesliderallthewaytothelefttosetthefontsizetothesmallest
value(Figure11.9).PresstheHomebuttonagaintosavethesechanges.
Figure11.9Textsizesettings
Buildandruntheapplication.(Ifyouswitchbacktotheapplication,eitherusingthetask
switcherorthroughtheHomescreen,youwillnotseethechanges.Youwillfixthatinthe
nextsection.)Addsomeitemstothetableviewandyouwillseethenewsmallerfont
sizesinaction.
Respondingtouserchanges
Whentheuserchangesthepreferredtextsizeandreturnstotheapplication,thetableview
willgetreloaded.Unfortunately,thelabelswillnotknowaboutthenewpreferredtext
size.Tofixthis,youneedtoupdatethelabelsmanually.
OpenItemCell.swiftandaddanewmethodthatupdatesallthreelabels.
funcupdateLabels(){
letbodyFont=UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
nameLabel.font=bodyFont
valueLabel.font=bodyFont
letcaption1Font=UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
serialNumberLabel.font=caption1Font
}
NowopenItemsViewController.swiftandcallthismethodin
tableView(_:cellForRowAtIndexPath:).
overridefunctableView(tableView:UITableView,
cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{
//Getaneworrecycledcell
letcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",
forIndexPath:indexPath)as!ItemCell
//Updatethelabelsforthenewpreferredtextsize
cell.updateLabels()
letitem=itemStore.allItems[indexPath.row]
cell.nameLabel.text=item.name
cell.serialNumberLabel.text=item.serialNumber
cell.valueLabel.text="$\(item.valueInDollars)"
returncell
}
Buildandruntheapplication.GointoSettingsandchangethepreferredreadingsizetothe
largestsize.Unlikebefore,youcannowswitchbacktoHomepwner,eitherbyopeningthe
taskswitcherorthroughtheHomescreen,andthetableviewwillupdatetoreflectthe
newpreferredtextsize.
BronzeChallenge:CellColors
UpdatetheItemCelltodisplaythevalueInDollarsingreenifthevalueisless
than50andredifthevalueisgreaterthanorequalto50.
12
StackViews
YouhavebeenusingAutoLayoutthroughoutthisbooktocreateflexibleinterfacesthat
scaleacrossdevicetypesandsizes.AutoLayoutisaverypowerfultechnology,butwith
thatpowercomescomplexity.Layingoutaninterfacewelloftenneedsalotofconstraints,
anditcanbedifficulttocreatedynamicinterfacesduetotheneedtoconstantlyaddand
removeconstraints.
Often,aninterface(orasubsectionoftheinterface)canbelaidoutinalinearfashion.
Let’sthinkaboutsomeoftheotherapplicationsyouhavewritteninthisbook.TheQuiz
applicationthatyouwroteinChapter1consistedoffoursubviewsthatwerelaidout
vertically.ThesameistruefortheWorldTrotterapplicationthatyouwrote:the
ConversionViewControllerhadaverticalinterfaceconsistingofatextfieldanda
fewlabels.
Interfacesthathavealinearlayoutaregreatcandidatesforusingastackview.Astack
viewisaninstanceofUIStackViewthatallowsyoutocreateaverticalorhorizontal
layoutthatiseasytolayoutandmanagesmostoftheconstraintsthatyouwouldtypically
havetomanageyourself.Perhapsbestofall,youareabletoneststackviewswithinother
stackviews,whichallowsyoutocreatetrulyamazinginterfacesinafractionofthetime.
Inthischapter,youaregoingtocontinueworkingonHomepwnertocreateaninterfacefor
displayingthedetailsofaspecificItem.Theinterfacethatyoucreatewillconsistof
multiplenestedstackviews,bothverticalandhorizontal(Figure12.1).
Figure12.1Homepwnerwithstackviews
UsingUIStackView
YouaregoingtocreateaninterfaceforeditingthedetailsforanItem.Youwillgetthe
basicinterfaceworkinginthischapter,andthenyouwillfinishimplementingthedetails
inChapter13.
Atthetoplevel,youwillhaveaverticalstackviewwithfourelementsdisplayingthe
item’sname,serialnumber,value,anddatecreated(Figure12.2).
Figure12.2Verticalstackviewlayout
OpenyourHomepwnerprojectandthenopenMain.storyboard.DraganewView
Controllerfromtheobjectlibraryontothecanvas.DragaVerticalStackViewfromtheobject
libraryontotheviewfortheViewController.Addconstraintstothestackviewtopinittothe
leadingandtrailingmargins,andpinthetopandbottomedgestobe8pointsfromthetop
andbottomlayoutguides.
NowdragfourinstancesofUILabelfromtheobjectlibraryontothestackview.From
toptobottom,givetheselabelsthetext“Name,”“Serial,”“Value,”and“DateCreated.”
(Figure12.3).
Figure12.3Labelsaddedtothestackview
Youcanseeaproblemrightaway:thelabelsallhavearedborder(indicatinganAuto
Layoutproblem)andthereisawarningthatsomeviewsareverticallyambiguous.There
aretwowaysyoucanfixthisissue:byusingAutoLayout,orbyusingapropertyonthe
stackview.Let’sworkthroughtheAutoLayoutsolutionfirstbecauseithighlightsan
importantaspectofAutoLayout.
Implicitconstraints
YoulearnedinChapter3thateveryviewhasanintrinsiccontentsize.Youalsolearned
thatifyoudonotspecifyconstraintsthatexplicitlydeterminethewidthorheight,the
viewwillderiveitswidthorheightfromitsintrinsiccontentsize.Howdoesthiswork?
Itdoesthisusingimplicitconstraintsderivedfromaview’scontenthuggingprioritiesand
itscontentcompressionresistancepriorities.Aviewhasoneoftheseprioritiesforeach
axis:
horizontalcontenthuggingpriority
verticalcontenthuggingpriority
horizontalcontentcompressionresistancepriority
verticalcontentcompressionresistancepriority
Contenthuggingpriorities
Thecontenthuggingpriorityislikearubberbandthatisplacedaroundaview.Therubber
bandmakestheviewnotwanttobebiggerthanitsintrinsiccontentsizeinthatdimension.
Eachpriorityisassociatedwithavaluefrom0to1000.Avalueof1000meansthata
viewcannotgetbiggerthanitsintrinsiccontentsizeonthatdimension.
Let’slookatanexamplewithjustthehorizontaldimension.Sayyouhavetwolabelsnext
tooneanotherwithconstraintsbothbetweenthetwoviewsandbetweeneachviewandits
superview,asshowninFigure12.4.
Figure12.4Twolabelssidebyside
Thisworksgreatuntilthesuperviewbecomeswider.Atthatpoint,whichlabelshould
becomewider?Thefirstlabel,thesecondlabel,orboth?AsFigure12.5shows,the
interfaceiscurrentlyambiguous.
Figure12.5Ambiguouslayout
Thisiswherethecontenthuggingprioritybecomesrelevant.Theviewwiththehigher
contenthuggingpriorityistheonethatdoesnotstretch.Youcanthinkaboutthepriority
valueasthe“strength”oftherubberband.Thehigherthepriorityvalue,thestrongerthe
rubberband,andthemoreitwantstohugtoitsintrinsiccontentsize.
Contentcompressionresistancepriorities
Thecontentcompressionresistanceprioritiesdeterminehowmuchaviewresistsgetting
smallerthanitsintrinsiccontentsize.ConsiderthesametwolabelsfromFigure12.4.
Whatwouldhappenifthesuperview’swidthdecreased?Oneofthelabelswouldneedto
truncateitstext(Figure12.6).Butwhichone?
Figure12.6Compressedambiguouslayout
Theviewwiththegreatercontentcompressionresistancepriorityistheonethatwillresist
compressionand,therefore,nottruncateitstext.
Withthisknowledge,youcannowfixtheproblemwiththestackview.
SelecttheDateCreatedlabelandopenitssizeinspector.FindtheVerticalContentHugging
Priorityandloweritto249.Nowtheotherthreelabelshaveahighercontenthugging
priority,sotheywillallhugtotheirintrinsiccontentheight.TheDateCreatedlabelwill
stretchtofillintheremainingspace.
Stackviewdistribution
Let’stakealookatanotherwayofsolvingtheproblem.Stackviewshaveanumberof
propertiesthatdeterminehowtheircontentislaidout.
Selectthestackview,eitheronthecanvasorusingthedocumentoutline.Openits
attributesinspectorandfindthesectionatthetoplabeledStackView.Oneoftheproperties
thatdetermineshowthecontentislaidoutistheDistributionproperty.Currentlyitissetto
Fill,whichletstheviewslayouttheircontentbasedontheirintrinsiccontentsize.Change
thevaluetoFillEqually.Thiswillresizethelabelssothattheyallhavethesameheight,
ignoringtheintrinsiccontentsize(Figure12.7).Besuretoreadthedocumentationforthe
otherdistributionvaluesthatastackviewcanhave.