iOS Programming The Big Nerd Ranch Guide, 5th
Transcript of iOS Programming The Big Nerd Ranch Guide, 5th
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
iOSProgramming:TheBigNerdRanchGuidebyChristianKeurandAaronHillegass
WOW! eBook www.wowebook.org
Copyright©2015BigNerdRanch,LLC
Allrightsreserved.PrintedintheUnitedStatesofAmerica.Thispublicationisprotectedbycopyright,andpermissionmustbeobtainedfromthepublisherpriortoanyprohibitedreproduction,storageinaretrievalsystem,ortransmissioninanyformorbyanymeans,electronic,mechanical,photocopying,recording,orlikewise.Forinformationregardingpermissions,contact
BigNerdRanch,LLC200ArizonaAveNEAtlanta,GA30307(770)817-6373http://www.bignerdranch.com/[email protected]
The10-gallonhatwithpropellerlogoisatrademarkofBigNerdRanch,LLC.
ExclusiveworldwidedistributionoftheEnglisheditionofthisbookby
PearsonTechnologyGroup800East96thStreetIndianapolis,IN46240USAhttp://www.informit.com
Theauthorsandpublisherhavetakencareinwritingandprintingthisbookbutmakenoexpressedorimpliedwarrantyofanykindandassumenoresponsibilityforerrorsoromissions.Noliabilityisassumedforincidentalorconsequentialdamagesinconnectionwithorarisingoutoftheuseoftheinformationorprogramscontainedherein.
AppStore,Apple,Cocoa,CocoaTouch,Finder,Instruments,iCloud,iPad,iPhone,iPod,iPodtouch,iTunes,Keychain,Mac,MacOS,Multi-Touch,Objective-C,OSX,Quartz,Retina,Safari,andXcodearetrademarksofApple,Inc.,registeredintheU.S.andothercountries.
Manyofthedesignationsusedbymanufacturersandsellerstodistinguishtheirproductsareclaimedastrademarks.Wherethosedesignationsappearinthisbook,andthepublisherwasawareofatrademarkclaim,thedesignationshavebeenprintedwithinitialcapitallettersorinallcapitals.
ISBN-100134389395
ISBN-13978-0134389394
Fifthedition,firstprinting,December2015ReleaseE.5.1.1
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AcknowledgmentsWhileournamesappearonthecover,manypeoplehelpedmakethisbookareality.Wewouldliketotakethischancetothankthem.
FirstandforemostwewouldliketothankJoeConwayforhisworkontheearliereditionsofthisbook.Heauthoredthefirstthreeeditionsandcontributedgreatlytothefourtheditionaswell.Manyofthewordsinthisbookarestillhis,andforthat,weareverygrateful.
Afewpeopleinparticularwentaboveandbeyondwiththeirhelponthisbook.TheyareMikeyWard,JuanPabloClaude,andChrisMorris.
TheotherinstructorswhoteachtheiOSBootcampfeduswithanever-endingstreamofsuggestionsandcorrections.TheyareBenScheirman,BolotKerimbaev,BrianHardy,ChrisMorris,JJManton,JohnGallagher,JonathanBlocksom,JosephDixon,JuanPabloClaude,MarkDalrymple,MattBezark,MattMathias,MikeZornek,MikeyWard,PouriaAlmassi,RodStrougo,ScottRitchie,StepChristopher,ThomasWard,TJUsiyan,andTomHarrington.Theseinstructorswereoftenaidedbytheirstudentsinfindingbookerrata,somanythanksareduetoallthestudentswhoattendtheiOSBootcamp.
ThankstoalloftheemployeesatBigNerdRanchwhohelpedreviewthebook,providedsuggestions,andfounderrata.
Ourtirelesseditor,ElizabethHoladay,tookourdistractedmumblingsandmadethemintoreadableprose.
AnnaBentleyjumpedintoprovideproofing.
EllieVolckhausendesignedthecover.(Thephotoisofthebottombracketofabicycleframe.)
ChrisLoperatIntelligentEnglish.comdesignedandproducedtheprintbookandtheEPUBandKindleversions.
TheamazingteamatPearsonTechnologyGrouppatientlyguidedusthroughthebusinessendofbookpublishing.
Thefinalandmostimportantthanksgoestoourstudentswhosequestionsinspiredustowritethisbookandwhosefrustrationsinspiredustomakeitclearandcomprehensible.
WOW! eBook www.wowebook.org
TableofContentsIntroduction
Prerequisites
WhatHasChangedintheFifthEdition?
OurTeachingPhilosophy
HowtoUseThisBook
UsinganeBook
HowThisBookIsOrganized
StyleChoices
TypographicalConventions
NecessaryHardwareandSoftware
1.ASimpleiOSApplication
CreatinganXcodeProject
Model-View-Controller
DesigningQuiz
InterfaceBuilder
BuildingtheInterface
Creatingviewobjects
Configuringviewobjects
Runningonthesimulator
AbriefintroductiontoAutoLayout
Makingconnections
CreatingtheModelLayer
Implementingactionmethods
Loadingthefirstquestion
BuildingtheFinishedApplication
ApplicationIcons
LaunchScreen
2.TheSwiftLanguage
WOW! eBook www.wowebook.org
TypesinSwift
UsingStandardTypes
Inferringtypes
Specifyingtypes
Literalsandsubscripting
Initializers
Properties
Instancemethods
Optionals
Subscriptingdictionaries
LoopsandStringInterpolation
EnumerationsandtheSwitchStatement
Enumerationsandrawvalues
ExploringApple’sSwiftDocumentation
3.ViewsandtheViewHierarchy
ViewBasics
TheViewHierarchy
CreatingaNewProject
ViewsandFrames
Customizingthelabels
TheAutoLayoutSystem
Alignmentrectangleandlayoutattributes
Constraints
AddingconstraintsinInterfaceBuilder
Intrinsiccontentsize
Misplacedviews
Addingmoreconstraints
BronzeChallenge:MoreAutoLayoutPractice
4.TextInputandDelegation
TextEditing
Keyboardattributes
WOW! eBook www.wowebook.org
Respondingtotextfieldchanges
Dismissingthekeyboard
ImplementingtheTemperatureConversion
Numberformatters
Delegation
Conformingtoaprotocol
Usingadelegate
Moreonprotocols
BronzeChallenge:DisallowAlphabeticCharacters
5.ViewControllers
TheViewofaViewController
SettingtheInitialViewController
UITabBarController
Tabbaritems
LoadedandAppearingViews
Accessingsubviews
InteractingwithViewControllersandTheirViews
SilverChallenge:DarkMode
FortheMoreCurious:RetinaDisplay
6.ProgrammaticViews
CreatingaViewProgrammatically
ProgrammaticConstraints
Anchors
Activatingconstraints
Layoutguides
Margins
Explicitconstraints
ProgrammaticControls
BronzeChallenge:AnotherTab
SilverChallenge:User’sLocation
GoldChallenge:DroppingPins
WOW! eBook www.wowebook.org
FortheMoreCurious:NSAutoresizingMaskLayoutConstraint
7.Localization
Internationalization
Formatters
Baseinternationalization
Preparingforlocalization
Localization
NSLocalizedStringandstringstables
BronzeChallenge:AnotherLocalization
FortheMoreCurious:NSBundle’sRoleinInternationalization
FortheMoreCurious:ImportingandExportingasXLIFF
8.ControllingAnimations
BasicAnimations
Closures
AnotherLabel
AnimationCompletion
AnimatingConstraints
TimingFunctions
BronzeChallenge:SpringAnimations
SilverChallenge:LayoutGuides
9.UITableViewandUITableViewController
BeginningtheHomepwnerApplication
UITableViewController
SubclassingUITableViewController
CreatingtheItemClass
Custominitializers
UITableView’sDataSource
Givingthecontrolleraccesstothestore
Implementingdatasourcemethods
UITableViewCells
CreatingandretrievingUITableViewCells
WOW! eBook www.wowebook.org
ReusingUITableViewCells
ContentInsets
BronzeChallenge:Sections
SilverChallenge:ConstantRows
GoldChallenge:CustomizingtheTable
10.EditingUITableView
EditingMode
AddingRows
DeletingRows
MovingRows
DisplayingUserAlerts
DesignPatterns
BronzeChallenge:RenamingtheDeleteButton
SilverChallenge:PreventingReordering
GoldChallenge:ReallyPreventingReordering
11.SubclassingUITableViewCell
CreatingItemCell
ExposingthePropertiesofItemCell
UsingItemCell
DynamicCellHeights
DynamicType
Respondingtouserchanges
BronzeChallenge:CellColors
12.StackViews
UsingUIStackView
Implicitconstraints
Stackviewdistribution
Nestedstackviews
Stackviewspacing
Segues
HookingUptheContent
WOW! eBook www.wowebook.org
PassingDataAround
BronzeChallenge:MoreStackViews
13.UINavigationController
UINavigationController
NavigatingwithUINavigationController
AppearingandDisappearingViews
DismissingtheKeyboard
Eventhandlingbasics
DismissingbypressingtheReturnkey
Dismissingbytappingelsewhere
UINavigationBar
Addingbuttonstothenavigationbar
BronzeChallenge:DisplayingaNumberPad
SilverChallenge:ACustomUITextField
GoldChallenge:PushingMoreViewControllers
14.Camera
DisplayingImagesandUIImageView
Addingacamerabutton
TakingPicturesandUIImagePickerController
Settingtheimagepicker’ssourceType
Settingtheimagepicker’sdelegate
Presentingtheimagepickermodally
Savingtheimage
CreatingImageStore
GivingViewControllersAccesstotheImageStore
CreatingandUsingKeys
WrappingUpImageStore
BronzeChallenge:EditinganImage
SilverChallenge:RemovinganImage
GoldChallenge:CameraOverlay
FortheMoreCurious:NavigatingImplementationFiles
WOW! eBook www.wowebook.org
//MARK:
15.Saving,Loading,andApplicationStates
Archiving
ApplicationSandbox
ConstructingafileURL
NSKeyedArchiverandNSKeyedUnarchiver
Loadingfiles
ApplicationStatesandTransitions
WritingtotheFilesystemwithNSData
ErrorHandling
BronzeChallenge:PNG
FortheMoreCurious:ApplicationStateTransitions
FortheMoreCurious:ReadingandWritingtotheFilesystem
FortheMoreCurious:TheApplicationBundle
16.SizeClasses
AnotherSizeClass
BronzeChallenge:StackedTextFieldandLabels
17.TouchEventsandUIResponder
TouchEvents
CreatingtheTouchTrackerApplication
CreatingtheLineStruct
Structs
Valuetypesvs.referencetypes
CreatingDrawView
DrawingwithDrawView
TurningTouchesintoLines
Handlingmultipletouches
@IBInspectable
SilverChallenge:Colors
GoldChallenge:Circles
FortheMoreCurious:TheResponderChain
WOW! eBook www.wowebook.org
FortheMoreCurious:UIControl
18.UIGestureRecognizerandUIMenuController
UIGestureRecognizerSubclasses
DetectingTapswithUITapGestureRecognizer
MultipleGestureRecognizers
UIMenuController
MoreGestureRecognizers
UILongPressGestureRecognizer
UIPanGestureRecognizerandsimultaneousrecognizers
MoreonUIGestureRecognizer
SilverChallenge:MysteriousLines
GoldChallenge:SpeedandSize
PlatinumChallenge:Colors
FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActions
19.WebServices
StartingthePhotoramaApplication
BuildingtheURL
FormattingURLsandrequests
NSURLComponents
SendingtheRequest
NSURLSession
ModelingthePhoto
JSONData
NSJSONSerialization
Enumerationsandassociatedvalues
ParsingJSONdata
DownloadingandDisplayingtheImageData
TheMainThread
BronzeChallenge:PrintingtheResponseInformation
FortheMoreCurious:HTTP
20.CollectionViewsWOW! eBook
www.wowebook.org
DisplayingtheGrid
CollectionViewDataSource
CustomizingtheLayout
CreatingaCustomUICollectionViewCell
DownloadingtheImageData
Extensions
NavigatingtoaPhoto
SilverChallenge:UpdatedItemSizes
GoldChallenge:CreatingaCustomLayout
21.CoreData
ObjectGraphs
Entities
Modelingentities
Transformableattributes
NSManagedObjectandsubclasses
BuildingtheCoreDataStack
NSManagedObjectModel
NSPersistentStoreCoordinator
NSManagedObjectContext
UpdatingItems
Insertingintothecontext
Savingchanges
UpdatingtheDataSource
Fetchrequestsandpredicates
SavingImagestoDisk
BronzeChallenge:PhotoViewCount
22.CoreDataRelationships
Relationships
AddingTagstotheInterface
Parent-ChildContexts
SilverChallenge:Favorites
WOW! eBook www.wowebook.org
23.Afterword
WhattoDoNext
ShamelessPlugs
Index
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
IntroductionAsanaspiringiOSdeveloper,youfacethreemajortasks:
YoumustlearntheSwiftlanguage.SwiftistherecommendeddevelopmentlanguageforiOS.ThefirsttwochaptersofthisbookaredesignedtogiveyouaworkingknowledgeofSwift.
Youmustmasterthebigideas.Theseincludethingslikedelegation,archiving,andtheproperuseofviewcontrollers.Thebigideastakeafewdaystounderstand.Whenyoureachthehalfwaypointofthisbook,youwillunderstandthesebigideas.
Youmustmastertheframeworks.TheeventualgoalistoknowhowtouseeverymethodofeveryclassineveryframeworkiniOS.Thisisaprojectforalifetime:therearehundredsofclassesandthousandsofmethodsavailableiniOS,andAppleaddsmoreclassesandmethodswitheveryreleaseofiOS.Inthisbook,youwillbeintroducedtoeachofthesubsystemsthatmakeuptheiOSSDK,butyouwillnotstudyeachonedeeply.Instead,ourgoalistogetyoutothepointwhereyoucansearchandunderstandApple’sreferencedocumentation.
WehaveusedthismaterialmanytimesatouriOSbootcampsatBigNerdRanch.ItiswelltestedandhashelpedthousandsofpeoplebecomeiOSdevelopers.Wesincerelyhopethatitprovesusefultoyou.
WOW! eBook www.wowebook.org
PrerequisitesThisbookassumesthatyouarealreadymotivatedtolearntowriteiOSapps.WewillnotspendanytimeconvincingyouthattheiPhone,iPad,andiPodtoucharecompellingpiecesoftechnology.
Wealsoassumethatyouhavesomeexperienceprogrammingandknowsomethingaboutobject-orientedprogramming.Ifthisisnottrue,youshouldprobablystartwithSwiftProgramming:TheBigNerdRanchGuide.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
WhatHasChangedintheFifthEdition?AllofthecodeinthisbookisSwift,andanearlychapterisdevotedtogettingyouuptospeedwiththisnewlanguage.Throughoutthebook,youwillseehowtouseSwift’scapabilitiesandfeaturestowritebetteriOSapplications.WehavecometoloveSwiftatBigNerdRanchandbelieveyouwill,too.
OtheradditionsincludecollectionviewsandsizeclassesandimprovedcoverageofAutoLayout,webservices,andCoreData.
ThiseditionassumesthatthereaderisusingXcode7.1orlaterandrunningapplicationsonaniOS9orlaterdevice.
Besidestheseobviouschanges,wemadethousandsoftinyimprovementsthatwereinspiredbyquestionsfromourreadersandourstudents.Everychapterofthisbookisjustalittlebetterthanthecorrespondingchapterfromthefourthedition.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
OurTeachingPhilosophyThisbookwillteachyoutheessentialconceptsofiOSprogramming.Atthesametime,youwilltypeinalotofcodeandbuildabunchofapplications.Bytheendofthebook,youwillhaveknowledgeandexperience.However,alltheknowledgeshouldnot(and,inthisbook,willnot)comefirst.Thatisthetraditionalwaywehaveallcometoknowandhate.Instead,wetakealearn-while-doingapproach.Developmentconceptsandactualcodinggotogether.
HereiswhatwehavelearnedovertheyearsofteachingiOSprogramming:
Wehavelearnedwhatideaspeoplemustgrasptogetstartedprogramming,andwefocusonthatsubset.
Wehavelearnedthatpeoplelearnbestwhentheseconceptsareintroducedastheyareneeded.
Wehavelearnedthatprogrammingknowledgeandexperiencegrowbestwhentheygrowtogether.
Wehavelearnedthat“goingthroughthemotions”ismuchmoreimportantthanitsounds.Manytimeswewillaskyoutostarttypingincodebeforeyouunderstandit.Werealizethatyoumayfeellikeatrainedmonkeytypinginabunchofcodethatyoudonotfullygrasp.Butthebestwaytolearncodingistofindandfixyourtypos.Farfrombeingadrag,thisbasicdebuggingiswhereyoureallylearntheinsandoutsofthecode.Thatiswhyweencourageyoutotypeinthecodeyourself.Youcouldjustdownloadit,butcopyingandpastingisnotprogramming.Wewantbetterforyouandyourskills.
Whatdoesthismeanforyou,thereader?Tolearnthiswaytakessometrust–andweappreciateyours.Italsotakespatience.Asweleadyouthroughthesechapters,wewilltrytokeepyoucomfortableandtellyouwhatishappening.However,therewillbetimeswhenyouwillhavetotakeourwordforit.(Ifyouthinkthiswillbugyou,keepreading–wehavesomeideasthatmighthelp.)Donotgetdiscouragedifyourunacrossaconceptthatyoudonotunderstandrightaway.Rememberthatweareintentionallynotprovidingalltheknowledgeyouwilleverneedallatonce.Ifaconceptseemsunclear,wewilllikelydiscussitinmoredetaillaterwhenitbecomesnecessary.Andsomethingsthatarenotclearatthebeginningwillsuddenlymakesensewhenyouimplementthemthefirst(orthetwelfth)time.
Peoplelearndifferently.Itispossiblethatyouwilllovehowwehandoutconceptsonanas-neededbasis.Itisalsopossiblethatyouwillfinditfrustrating.Incaseofthelatter,herearesomeoptions:
Takeadeepbreathandwaititout.Wewillgetthere,andsowillyou.
Checktheindex.Wewillletitslideifyoulookaheadandreadthroughamoreadvanceddiscussionthatoccurslaterinthebook.
ChecktheonlineAppledocumentation.Thisisanessentialdevelopertool,and
WOW! eBook www.wowebook.org
youwillwantplentyofpracticeusingit.Consultitearlyandoften.
IfSwiftorobject-orientedprogrammingconceptsaregivingyouahardtime(orifyouthinktheywill),youmightconsiderbackingupandreadingourSwiftProgramming:TheBigNerdRanchGuide.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
HowtoUseThisBookThisbookisbasedontheclassweteachatBigNerdRanch.Assuch,itwasdesignedtobeconsumedinacertainmanner.
Setyourselfareasonablegoal,like“Iwilldoonechaptereveryday.”Whenyousitdowntoattackachapter,findaquietplacewhereyouwillnotbeinterruptedforatleastanhour.Shutdownyouremail,yourTwitterclient,andyourchatprogram.Thisisnotatimeformultitasking;youwillneedtoconcentrate.
Dotheactualprogramming.Youcanreadthroughachapterfirst,ifyoulike.Butthereallearningcomeswhenyousitdownandcodeasyougo.Youwillnotreallyunderstandtheideauntilyouhavewrittenaprogramthatusesitand,perhapsmoreimportantly,debuggedthatprogram.
Acoupleoftheexercisesrequiresupportingfiles.Forexample,inthefirstchapteryouwillneedaniconforyourQuizapplication,andwehaveoneforyou.Youcandownloadtheresourcesandsolutionstotheexercisesfromhttp://www.bignerdranch.com/solutions/iOSProgramming5ed.zip.
Therearetwotypesoflearning.WhenyoulearnaboutthePeloponnesianWar,youaresimplyaddingdetailstoascaffoldingofideasthatyoualreadyunderstand.Thisiswhatwewillcall“EasyLearning.”Yes,learningaboutthePeloponnesianWarcantakealongtime,butyouareseldomflummoxedbyit.LearningiOSprogramming,ontheotherhand,is“HardLearning,”andyoumayfindyourselfquitebaffledattimes,especiallyinthefirstfewdays.Inwritingthisbook,wehavetriedtocreateanexperiencethatwilleaseyouoverthebumpsinthelearningcurve.Herearetwothingsyoucandotomakethejourneyeasier:
FindsomeonewhoalreadyknowshowtowriteiOSapplicationsandwillansweryourquestions.Inparticular,gettingyourapplicationontoadevicethefirsttimeisusuallyveryfrustratingifyouaredoingitwithoutthehelpofanexperienceddeveloper.
Getenoughsleep.Sleepypeopledonotrememberwhattheyhavelearned.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UsinganeBookIfyouarereadingthisbookonaneReader,wewanttopointoutthatreadingthecodemaybetrickyattimes.Longerlinesofcodewillwraptoasecondlinedependingonyourselectedfontsize.ThisbothersusbecausewearereallyconscientiousatBigNerdRanchaboutthewayourcodeappearsonthepage.Clearvisualpatternsincodemakethatcodeeasiertounderstand.
Thelongestlinesofcodeinthisbookare86monospacecharacters,likethisone.funcprocessRecentPhotosRequest(datadata:NSData?,error:NSError?)->PhotosResult{
YoucanplaywithyoureReaders’ssettingstofindthebestforviewinglongcodelines.
Whenyougettothepointwhereyouareactuallytypingincode,wesuggestopeningthebookonyourMaciniBooksorAdobeDigitalEditions.(AdobeDigitalEditionsisafreeeReaderapplicationyoucandownloadfromhttp://www.adobe.com/products/digitaleditions/.)Maketheapplicationwindowlargeenoughsothatyoucanseethecodewithnowrappinglines.Youwillalsobeabletoseethefiguresinfulldetail.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
HowThisBookIsOrganizedInthisbook,eachchapteraddressesoneormoreideasofiOSdevelopmentthroughdiscussionandhands-onpractice.Formorecodingpractice,mostchaptersincludechallengeexercises.Weencourageyoutotakeonatleastsomeofthese.TheyareexcellentforfirmingupyourgraspoftheconceptsintroducedinthechapterandformakingyouamoreconfidentiOSprogrammer.Finally,mostchaptersconcludewithoneortwo“FortheMoreCurious”sectionsthatexplaincertainconsequencesoftheconceptsthatwereintroducedearlier.
Chapter1introducesyoutoiOSprogrammingasyoubuildanddeployatinyapplication.YouwillgetyourfeetwetwithXcodeandtheiOSsimulatoralongwithallthestepsforcreatingprojectsandfiles.ThechapterincludesadiscussionofModel-View-ControllerandhowitrelatestoiOSdevelopment.
Chapter2providesanoverviewofSwift,includingbasicsyntax,types,optionals,initialization,andhowSwiftisabletointeractwiththeexistingiOSframeworks.Youwillalsogetexperienceworkinginaplayground,Xcode’snewcodeprototypingtool.
InChapter3,youwillfocusontheiOSuserinterfaceasyoulearnaboutviewsandtheviewhierarchyandcreateanapplicationcalledWorldTrotter.
Chapter4introducesdelegation,animportantiOSdesignpattern.YouwillalsoatextfieldtoWorldTrotter.
InChapter5,youwillexpandWorldTrotterandlearnaboutusingviewcontrollersformanaginguserinterfaces.Youwillgetpracticeworkingwithviewsandviewcontrollersaswellasnavigatingbetweenscreensusingatabbar.
InChapter6,youwilllearnhowtomanageviewsandviewcontrollersincode.YouwilladdasegmentedcontroltoWorldTrotterthatwillletyouswitchbetweenvariousmaptypes.
Chapter7introducestheconceptsandtechniquesofinternationalizationandlocalization.YouwilllearnaboutNSLocale,stringstables,andNSBundleasyoulocalizepartsofWorldTrotter.
InChapter8,youwilllearnaboutandadddifferenttypesofanimationstotheQuizprojectthatyoucreatedinChapter1.
Chapter9introducesthelargestapplicationinthebook–Homepwner.(Bytheway,“Homepwner”isnotatypo;youcanfindthedefinitionof“pwn”atwww.urbandictionary.com.)Thisapplicationkeepsarecordofyouritemsincaseoffireorothercatastrophe.Homepwnerwilltakeeightchapterstocomplete.
InChapter9-Chapter11,youwillworkwithtables.Youwilllearnabouttableviews,theirviewcontrollers,andtheirdatasources.Youwilllearnhowtodisplaydatainatable,howtoallowtheusertoeditthetable,andhowtoimprovetheinterface.
Chapter12introducesstackviewsthatwillhelpyoucreatecomplexinterfacesveryeasily.YouwilluseastackviewtoaddanewscreentoHomepwnerthatdisplaysthedetailsforasingleitem.
WOW! eBook www.wowebook.org
Chapter13buildsonthenavigationexperiencegainedinChapter5.YouwilluseUINavigationControllertogiveHomepwneradrill-downinterfaceandanavigationbar.
Chapter14introducesthecamera.YouwilltakepicturesanddisplayandstoreimagesinHomepwner.
InChapter15,youwilladdpersistencetoHomepwnerusingarchivingtosaveandloadtheapplicationdata.
InChapter16,youwilllearnaboutsizeclasses,andyouwillusethesetoupdateHomepwner’sinterfacetoscalewellacrossvariousscreensizes.
InChapter17andChapter18,youwillcreateadrawingapplicationnamedTouchTrackertolearnabouttouchevents.YouwillseehowtoaddmultitouchcapabilityandhowtouseUIGestureRecognizertorespondtoparticulargestures.Youwillalsogetexperiencewiththefirstresponderandresponderchainconceptsandmorepracticewithusingstructuresanddictionaries.
Chapter19introduceswebservicesasyoucreatethePhotoramaapplication.ThisapplicationfetchesandparsesJSONfromaserverusingNSURLSessionsandNSJSONSerialization.
InChapter20,youwilllearnaboutcollectionviewsasyoubuildaninterfaceforPhotoramausingUICollectionViewandUICollectionViewCell.
InChapter21andChapter22,youwilladdpersistencetoPhotoramausingCoreData.YouwillstoreandloadimagesandassociateddatausinganNSManagedObjectContext.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
StyleChoicesThisbookcontainsalotofcode.Wehaveattemptedtomakethatcodeandthedesignsbehinditexemplary.Wehavedoneourbesttofollowtheidiomsofthecommunity,butattimeswehavewanderedfromwhatyoumightseeinApple’ssamplecodeorcodeyoumightfindinotherbooks.Inparticular,youshouldknowup-frontthatwenearlyalwaysstartaprojectwiththesimplesttemplateproject:thesingleviewapplication.Whenyourappworks,youwillknowitisbecauseofyourefforts–notbecausethatbehaviorwasbuiltintothetemplate.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TypographicalConventionsTomakethisbookeasiertoread,certainitemsappearincertainfonts.Classes,types,methods,andfunctionsappearinabold,fixed-widthfont.Classesandtypesstartwithcapitalletters,andmethodsandfunctionsstartwithlowercaseletters.Forexample,“IntheloadView()methodoftheRexViewControllerclass,createaconstantoftypeString.”
Variables,constants,andfilenamesappearinafixed-widthfontbutarenotbold.Soyouwillsee,“InViewController.swift,addavariablenamedfidoandinitializeitto“Rufus”.”
Applicationnames,menuchoices,andbuttonnamesappearinasansseriffont.Forexample,“OpenXcodeandselectNewProject…fromtheFilemenu.SelectSingleViewApplicationandthenclickChoose….”
Allcodeblocksareinafixed-widthfont.Codethatyouneedtotypeinisalwaysbold.Forexample,inthefollowingcode,youwouldtypeinthetwolinesbeginning@IBOutlet.Theotherlinesarealreadyinthecodeandareincludedtoletyouknowwheretoaddthenewlines.importUIKit
classViewController:UIViewController{
@IBOutletvarquestionLabel:UILabel!@IBOutletvaranswerLabel:UILabel!
}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
NecessaryHardwareandSoftwareTobuildtheapplicationsinthisbook,youmusthaveaMacrunningOSXYosemite(10.10.5)orlater.YouwillalsoneedXcode,Apple’sIntegratedDevelopmentEnvironment,whichisavailableontheAppStore.XcodeincludestheiOSSDK,theiOSsimulator,andotherdevelopmenttools.
YoushouldjointheAppleDeveloperProgram,whichcosts$99/year,because:
Downloadingthelatestdevelopertoolsisfreeformembers.
Youcannotputanappinthestoreuntilyouareamember.
Ifyouaregoingtotakethetimetoworkthroughthisentirebook,membershipintheAppleDeveloperProgramisworththecost.Gotohttp://developer.apple.com/programs/ios/tojoin.
WhataboutiOSdevices?MostoftheapplicationsyouwilldevelopinthefirsthalfofthebookareforiPhone,butyouwillbeabletorunthemonaniPad.OntheiPadscreen,iPhoneapplicationsappearinaniPhone-sizedwindow.NotacompellinguseofiPad,butthatisOKwhenyouarestartingwithiOS.Intheearlychapters,youwillbefocusedonlearningthefundamentalsoftheiOSSDK,andthesearethesameacrossiOSdevices.Laterinthebook,youwillseehowtomakeapplicationsrunnativelyonbothiOSdevicefamilies.
Excitedyet?Good.Let’sgetstarted.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
1ASimpleiOSApplication
Inthischapter,youaregoingtowriteaniOSapplicationnamedQuiz.Thisapplicationwillshowaquestionandthenrevealtheanswerwhentheusertapsabutton.Tappinganotherbuttonwillshowtheuseranewquestion(Figure1.1).
Figure1.1Yourfirstapplication:Quiz
WhenyouarewritinganiOSapplication,youmustanswertwobasicquestions:
HowdoIgetmyobjectscreatedandconfiguredproperly?(Example:“IwantabuttonherethatsaysNextQuestion.”)
HowdoImakemyapprespondtouserinteraction?(Example:“Whentheusertapsthebutton,Iwantthispieceofcodetobeexecuted.”)
Mostofthisbookisdedicatedtoansweringthesequestions.
Asyougothroughthisfirstchapter,youwillprobablynotunderstandeverythingthatyouaredoing,andyoumayfeelridiculousjustgoingthroughthemotions.Butgoingthrough
WOW! eBook www.wowebook.org
themotionsisenoughfornow.Mimicryisapowerfulformoflearning;itishowyoulearnedtospeak,anditishowyouwillstartiOSprogramming.Asyoubecomemorecapable,youwillexperimentandchallengeyourselftodocreativethingsontheplatform.Fornow,goaheadanddowhatweshowyou.Thedetailswillbeexplainedinlaterchapters.
WOW! eBook www.wowebook.org
CreatinganXcodeProjectOpenXcodeand,fromtheFilemenu,selectNew→Project…(IfXcodeopenstoawelcomescreen,selectCreateanewXcodeproject.)
Anewworkspacewindowwillappearandasheetwillslidedownfromitstoolbar.Onthelefthandside,findtheiOSsectionandselectApplication(Figure1.2).Youwillbeofferedseveralapplicationtemplatestochoosefrom.SelectSingleViewApplication.
Figure1.2Creatingaproject
ThisbookwascreatedforXcode7.1.ThenamesofthesetemplatesmaychangewithnewXcodereleases.IfyoudonotseeaSingleViewApplicationtemplate,usethesimplest-soundingtemplate.OrvisittheBigNerdRanchforumforthisbookathttp://forums.bignerdranch.comforhelpworkingwithnewerversionsofXcode.
ClickNextand,inthenextsheet,enterQuizfortheProductName(Figure1.3).Theorganizationnameandcompanyidentifierarerequiredtocontinue.YoucanuseBigNerdRanchandcom.bignerdranch.Oruseyourcompanynameandcom.yourcompanynamehere.
FromtheLanguagepop-upmenu,chooseSwift,andfromtheDevicespop-upmenu,chooseiPhone.MakesurethattheUseCoreData,IncludeUnitTests,andIncludeUITestscheckboxesareunchecked.
WOW! eBook www.wowebook.org
Figure1.3Configuringanewproject
YouarecreatingQuizasaniPhoneapplication,butyouwillbeabletorunitonaniPadaswell.Forthefirstpartofthisbook,youwillsticktocreatingiPhoneapplicationsandfocusonlearningthefundamentalsoftheiOSsoftwaredevelopmentkit,orSDK.Later,youwilllearnhowtocreateuserinterfacesthatadaptelegantlytothescreensizesofdifferentiOSdevices.
ClickNextand,inthefinalsheet,savetheprojectinthedirectorywhereyouplantostoretheexercisesinthisbook.ClickCreatetocreatetheQuizproject.
Oncetheprojectiscreated,itwillopenintheXcodeworkspacewindow(Figure1.4).
WOW! eBook www.wowebook.org
Figure1.4Xcodeworkspacewindow
Thelefthandsideoftheworkspacewindowisthenavigatorarea.Thisareadisplaysdifferentnavigators–toolsthatshowyoudifferentpartsofyourproject.Youcanopenanavigatorbyselectingoneoftheiconsinthenavigatorselector,whichisthebarjustabovethenavigatorarea.
Thenavigatorcurrentlyopenistheprojectnavigator.Theprojectnavigatorshowsyouthefilesthatmakeupaproject(Figure1.5).Youcanselectoneofthesefilestoopenitintheeditorareatotherightofthenavigatorarea.
Thefilesintheprojectnavigatorcanbegroupedintofolderstohelpyouorganizeyourproject.Afewgroupshavebeencreatedbythetemplateforyou.Youcanrenamethem,ifyouwant,oraddnewones.Thegroupsarepurelyfortheorganizationoffilesanddonotcorrelatetothefilesysteminanyway.
Figure1.5Quizapplication’sfilesintheprojectnavigator
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
Model-View-ControllerBeforeyoubeginyourapplication,let’sdiscussakeyconceptinapplicationarchitecture:Model-View-Controller,orMVC.MVCisadesignpatternusediniOSdevelopment.InMVC,everyinstancebelongstoeitherthemodellayer,theviewlayer,orthecontrollerlayer.(Layerheresimplyreferstooneormoreobjectsthattogetherfulfillarole.)
Themodellayerholdsdataandknowsnothingabouttheuserinterface.InQuiz,themodelwillconsistoftwoorderedlistsofstrings:oneforquestionsandanotherforanswers.
Usually,instancesinthemodellayerrepresentrealthingsintheworldoftheuser.Forexample,whenyouwriteanappforaninsurancecompany,yourmodelwillalmostcertainlycontainacustomtypecalledInsurancePolicy.
Theviewlayercontainsobjectsthatarevisibletotheuser.Examplesofviewobjects,orviews,arebuttons,textfields,andsliders.Viewobjectsmakeupanapplication’suserinterface.InQuiz,thelabelsshowingthequestionandanswerandthebuttonsbeneaththemareviewobjects.
Thecontrollerlayeriswheretheapplicationismanaged.Controllerobjects,orcontrollers,arethemanagersofanapplication.Controllersconfiguretheviewsthattheuserseesandmakesurethattheviewandmodelobjectsstaysynchronized.
Ingeneral,controllerstypicallyhandle“Andthen?”questions.Forexample,whentheuserselectsanitemfromalist,thecontrollerdetermineswhattheuserseesnext.
Figure1.6showstheflowofcontrolinanapplicationinresponsetouserinput,suchastheusertappingabutton.
WOW! eBook www.wowebook.org
Figure1.6MVCpattern
Noticethatmodelsandviewsdonottalktoeachotherdirectly;controllerssitsquarelyinthemiddleofeverything,receivingmessagesanddispatchinginstructions.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DesigningQuizYouaregoingtowritetheQuizapplicationusingtheMVCpattern.Hereisabreakdownoftheinstancesyouwillbecreatingandworkingwith:
Themodellayerwillconsistoftwoinstancesof[String].
TheviewlayerwillconsistoftwoinstancesofUILabelandtwoinstancesofUIButton.
ThecontrollerlayerwillconsistofaninstanceofAppDelegateandaninstanceofViewController.
TheseinstancesandtheirrelationshipsarelaidoutinthediagramforQuizshowninFigure1.7.
Figure1.7ObjectdiagramforQuiz
Figure1.7isthebigpictureofhowthefinishedQuizapplicationwillwork.Forexample,whentheNextQuestionbuttonistapped,itwilltriggeramethodinViewController.Amethodisalotlikeafunction–alistofinstructionstobeexecuted.Thismethodwillretrieveanewquestionfromthearrayofquestionsandaskthetoplabeltodisplaythatquestion.
ItisOKifthisdiagramdoesnotmakesenseyet–itwillbytheendofthechapter.ReferWOW! eBook
www.wowebook.org
backtoitasyoubuildtheapptoseehowitistakingshape.
YouaregoingtobuildQuizinsteps,startingwiththevisualinterfacefortheapplication.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
InterfaceBuilderYouareusingtheSingleViewApplicationtemplatebecauseitisthesimplesttemplatethatXcodeoffers.Still,thistemplatehasasignificantamountofmagicinthatsomecriticalcomponentshavealreadybeensetupforyou.Fornow,youwilljustusethesecomponents,withoutattemptingtogainadeepunderstandingofhowtheywork.Therestofthebookwillbeconcernedwiththosedetails.
ClickonceontheMain.storyboardfile.Xcodewillopenitsgraphic-styleeditorcalledInterfaceBuilder.
InterfaceBuilderdividestheeditorareaintotwosections:thedocumentoutline,onthelefthandside,andthecanvas,ontheright.
ThisisshowninFigure1.8.Ifwhatyouseeonyourscreendoesnotexactlymatchthefigure,youmayhavetoclickontheShowDocumentOutlinebutton.(Ifyouhaveadditionalareasshowing,donotworryaboutthem.)Youmayalsohavetoclickonthedisclosuretrianglesonthedocumentoutlinetorevealcontent.
WOW! eBook www.wowebook.org
Figure1.8InterfaceBuildershowingMain.storyboard
ThelargerectanglethatyouseeintheInterfaceBuildercanvasiscalledasceneandrepresentstheonly“screen”orviewyourapplicationhasatthistime(rememberthatyouusedthesingleviewapplicationtemplatetocreatethisproject).
Inthenextsection,youwilllearnhowtocreateauserinterfaceforyourapplicationusingInterfaceBuilder.InterfaceBuilderletsyoudragobjectsfromalibraryontothecanvastocreateinstances,andalsoletsyouestablishconnectionsbetweenthoseobjectsandyourcode.Theseconnectionscanresultincodebeingcalledbyauserinteraction.
AcrucialfeatureofInterfaceBuilderisthatitisnotagraphicalrepresentationofcodecontainedinotherfiles.InterfaceBuilderisanobjecteditorthatcancreateinstancesofobjectsandmanipulatetheirproperties.Whenyouaredoneeditinganinterface,itdoesnotgeneratecodethatcorrespondstotheworkyouhavedone.A.storyboardfileisanarchiveofobjectinstancestobeloadedintomemorywhennecessary.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BuildingtheInterfaceLet’sgetstartedonyourinterface.YouhaveselectedMain.storyboardtorevealitssinglesceneinthecanvas(Figure1.9).
Figure1.9ThesceneinMain.storyboard
Itistimetoaddyourviewobjectstothatblankslate.
Creatingviewobjects
MakesurethattheutilityareawithinXcode’swindowisvisible.Youmayneedtoclickontherightmostbuttonofthe controlinthetop-rightcornerofthewindow.Theutilityareaistotherightoftheeditorareaandhastwosections:theinspectorandthelibrary.Thetopsectionistheinspector,whichcontainssettingsforthefileorobjectthatisselectedintheeditorarea.Thebottomsectionisthelibrary,whichlistsitemsthatyoucanaddtoafileorproject.
Atthetopofeachsectionintheutilityareaisaselectorfordifferentinspectorsandlibraries(Figure1.10).
WOW! eBook www.wowebook.org
Figure1.10Xcodeutilityarea
Yourapplicationinterfacerequiresfourviewobjects:twobuttonstoacceptuserinputandtwotextlabelstodisplayinformation.Toaddthem,firstmakesureyoucanseetheobjectlibrary,asseeninFigure1.10,byselectingthe tabfromthelibraryselector.
Theobjectlibrarycontainstheobjectsthatyoucanaddtoastoryboardfiletocomposeyourinterface.FindtheLabelobjectbyeitherscrollingdownthroughthelistorbyusingthesearchbaratthebottomofthelibrary.Selectthisobjectinthelibraryanddragitontotheviewobjectonthecanvas.Dragthelabelaroundthecanvasandnoticethedashedbluelinesthatappearwhenthelabelisnearthecenterofthecanvas(Figure1.11).Theseguidelineswillhelpyoulayoutyourinterface.
Figure1.11Addingalabeltothecanvas
WOW! eBook www.wowebook.org
Usingtheguidelines,positionthelabelinthehorizontalcenteroftheviewandnearthetop,asshowninFigure1.11.Eventually,thislabelwilldisplayquestionstotheuser.Dragasecondlabelontotheviewandpositionitinthehorizontalcenter,closertothemiddle.Thislabelwilldisplayanswers.
Next,findButtonintheobjectlibraryanddragtwobuttonsontotheview.Positiononebeloweachlabel.
YouhavenowaddedfourviewobjectstotheViewController’suserinterface.Noticethattheyalsoappearinthedocumentoutline.YourcompletedinterfaceshouldlooklikeFigure1.12.
Figure1.12BuildingtheQuizinterface
Configuringviewobjects
Nowthatyouhavecreatedtheviewobjects,youcanconfiguretheirattributes.Someattributesofaview,likesize,position,andtext,canbechangeddirectlyonthecanvas.Forexample,youcanresizeanobjectbyselectingitinthecanvasorthedocumentoutlineandthendraggingitscornersandedgesinthecanvas.
WOW! eBook www.wowebook.org
Beginbyrenamingthelabelsandbuttons.Double-clickoneachlabelandreplacethetextwith???.Thendouble-clicktheupperbuttonandchangeitsnametoNextQuestion.RenamethelowerbuttontoShowAnswer.TheresultsareshowninFigure1.13.
Figure1.13Renamingthelabelsandbuttons
Youmayhavenoticedthatasaresultofchangingthetextinthelabelsandbuttons,andthereforetheirwidths,theyarenolongerneatlycenteredinthescene.Clickoneachofthemanddragtocenterthemagain,asshowninFigure1.14.
Figure1.14Centeringthelabelsandbuttons
Runningonthesimulator
Totestyouruserinterface,youaregoingtorunQuizonXcode’siOSsimulator.
ToprepareQuiztorunonthesimulator,findthecurrentschemepop-upmenuontheXcodetoolbar(Figure1.15).
WOW! eBook www.wowebook.org
Figure1.15iPhone6sschemeselected
IfitsayssomethinggenericlikeiPhone6s,thentheprojectissettorunonthesimulatorandyouaregoodtogo.IfitsayssomethinglikeChristian’siPhone,thenclickandchooseiPhone6sfromthepop-upmenu.TheiPhone6sschemewillbeyoursimulatordefaultthroughoutthisbook.
(Ifyouwouldliketoruntheapplicationonanactualdevice,readouriOSDeviceProvisionGuideathttps://www.bignerdranch.com/we-teach/how-to-prepare/ios-device-provisioning/.)
Clickthetriangularplaybuttoninthetoolbar.Thiswillbuild(compile)andthenruntheapplication.YouwillbedoingthisoftenenoughthatyoumaywanttolearnandusethekeyboardshortcutCommand-R.
Afterthesimulatorlaunchesyouwillseethattheinterfacehasalltheviewsyouadded,buttheyarenotcorrectlyplaced.ThisisbecausethelabelsandbuttonscurrentlyhaveafixedpositiononascreenlargerthaniPhone6s’s,andtheydonotremaincenteredonthemainview.Tocorrectthisproblem,youwilluseatechnologycalledAutoLayout.
AbriefintroductiontoAutoLayout
Asofnow,yourinterfacelooksniceintheInterfaceBuildercanvas.ButiOSdevicescomeinevermorescreensizes,andapplicationsareexpectedtosupportallscreensizesandorientations–andperhapsmorethanonedevicetype.Youneedtoguaranteethatthelayoutofviewobjectswillbecorrectregardlessofthescreensizeororientationofthedevicerunningtheapplication.ThetoolforthistaskisAutoLayout.
AutoLayoutworksbyspecifyingpositionandsizeconstraintsforeachviewobjectinascene.Theseconstraintscanberelativetoneighboringviewsorcontainerviews.Acontainerviewisjustaviewobjectthatcontainsanotherview.Forexample,takealookatthedocumentoutlineforMain.storyboard(Figure1.16).
WOW! eBook www.wowebook.org
Figure1.16Documentlayoutwithacontainerview
YoucanseeinthedocumentoutlinethatthelabelsandbuttonsyouaddedareindentedwithrespecttoaViewobject.Thisviewobjectisthecontainerofthelabelsandbuttons,andtheobjectscanbepositionedandsizedrelativetothisview.
TobeginspecifyingAutoLayoutconstraints,selectthetoplabelbyclickingoniteitheronthecanvasorinthedocumentoutline.Atthebottomofthecanvas,noticetheAutoLayoutmenus,showninFigure1.17.
Figure1.17TheAutoLayoutmenus
Withthetoplabelstillselected,clickonthe icontorevealtheAlignmenushowninFigure1.18.
Figure1.18Centeringthetoplabelinthecontainer
WOW! eBook www.wowebook.org
WithintheAlignmenu,checktheHorizontallyinContainercheckboxtocenterthelabelinthecontainer.ThenclicktheAdd1Constraintbutton.Thisconstraintguaranteesthatonanysizescreen,inanyorientation,thelabelwillbecenteredhorizontally.
Nowyouneedtoaddmoreconstraintstocenterthelowerlabelandthebuttonswithrespecttothetoplabelandtolockthespacingbetweenthem.SelectthefourviewsbyCommand-clickingonthemoneafteranotherandthenclickonthe icontoopenthePinmenushowninFigure1.19.
Figure1.19Pinningthecenteringandspacingbetweenviews
AsseeninFigure1.19,clickontheredverticaldashedsegmentnearthetopofthemenu.Whenyouclickonthesegment,itwillbecomesolidred,indicatingthatthedistanceofeachviewispinnedtoitsnearesttopneighbor.Also,checktheAlignboxandthenselectHorizontalCentersfromthepop-upmenu.ForUpdateFrames,makesurethatyouhaveItemsofNewConstraintsselected.Finally,clickontheAdd7Constraintsbuttonatthebottomofthemenu.
Ifyoumadeanymistakeswhileaddingconstraints,youmayseeredororangeconstraintsandframesonthecanvasinsteadofthecorrectbluelines.Ifthatisthecase,youwillwanttocleartheexistingconstraintsandgothroughthestepsaboveagain.Toclearconstraints,firstselectthebackground(container)view.Thenclickthe icontoopentheResolveAutoLayoutIssuesmenu.SelectClearConstraintsundertheAllViewssection(Figure1.20).Thiswillclearawayanyconstraintsthatyouhaveaddedandgiveyouafreshstartonaddingtheconstraintsbackin.
WOW! eBook www.wowebook.org
Figure1.20Clearingconstraints
AutoLayoutcanbeadifficulttooltomaster,andthatiswhyyouarestartingtouseitinthefirstchapterofthisbook.Bystartingearly,youwillhavemorechancestouseitandgetusedtoitscomplexity.Also,dealingwithproblemsbeforethingsgettoocomplicatedwillhelpyouunderstandhowtodebuglayoutissueswithmoreconfidence.
Toconfirmthatyourinterfacebehavescorrectly,buildandruntheapplicationonthesimulator.
Makingconnections
Aconnectionletsoneobjectknowwhereanotherobjectisinmemorysothatthetwoobjectscancommunicate.TherearetwokindsofconnectionsthatyoucanmakeinInterfaceBuilder:outletsandactions.Anoutletisareferencetoanobject.Anactionisamethodthatgetstriggeredbyabuttonorsomeotherviewthattheusercaninteractwith,likeasliderorapicker.
Let’sstartbycreatingoutletsthatreferencetheinstancesofUILabel.TimetoleaveInterfaceBuilderandwritesomecode.
Declaringoutlets
Intheprojectnavigator,findandselectthefilenamedViewController.swift.TheeditorareawillchangefromInterfaceBuildertoXcode’scodeeditor.
InViewController.swift,startbydeletinganycodethatthetemplateaddedbetweenclassViewController:UIViewController{and}sothatthefilelookslikethis:importUIKit
classViewController:UIViewController{
}
Next,addthefollowingcodethatdeclarestwoproperties.(Throughoutthisbook,newcodeforyoutoaddwillbeshowninbold.Codeforyoutodeletewillbestruckthrough.)
WOW! eBook www.wowebook.org
Donotworryaboutunderstandingthecodeorpropertiesrightnow;justgetitin.importUIKit
classViewController:UIViewController{@IBOutletvarquestionLabel:UILabel!@IBOutletvaranswerLabel:UILabel!}
ThiscodegiveseveryinstanceofViewControlleranoutletnamedquestionLabelandanoutletnamedanswerLabel.TheviewcontrollercanuseeachoutlettoreferenceaparticularUILabelobject(i.e.,oneofthelabelsinyourview).The@IBOutletkeywordtellsXcodethatyouwillconnecttheseoutletstolabelobjectsusingInterfaceBuilder.
Settingoutlets
Intheprojectnavigator,selectMain.storyboardtoreopenInterfaceBuilder.
YouwantthequestionLabeloutlettopointtotheinstanceofUILabelatthetopoftheuserinterface.
Inthedocumentoutline,findtheViewControllerScenesectionandtheViewControllerobjectwithinit.Inyourcase,theViewControllerstandsinforaninstanceofViewController,whichistheobjectresponsibleformanagingtheinterfacedefinedinMain.storyboard.
Control-drag(orright-clickanddrag)fromtheViewControllerinthedocumentoutlinetothetoplabelinthescene.Whenthelabelishighlighted,releasethemouseandkeyboard;ablackpanelwillappear.SelectquestionLabeltosettheoutlet,asshowninFigure1.21.
Figure1.21SettingquestionLabel
(IfyoudonotseequestionLabelintheconnectionspanel,double-checkyourViewController.swiftfilefortypos.)
WOW! eBook www.wowebook.org
Now,whenthestoryboardfileisloaded,theViewController’squestionLabeloutletwillautomaticallyreferencetheinstanceofUILabelatthetopofthescreen,whichwillallowtheViewControllertotellthelabelwhatquestiontodisplay.
SettheanswerLabeloutletthesameway:Control-dragfromtheViewControllertothebottomUILabelandselectanswerLabel(Figure1.22).
Figure1.22SettinganswerLabel
Noticethatyoudragfromtheobjectwiththeoutletthatyouwanttosettotheobjectthatyouwantthatoutlettopointto.
Youroutletsareallset.Thenextconnectionsyouneedtomakeinvolvethetwobuttons.
Definingactionmethods
WhenaUIButtonistapped,itcallsamethodonanotherobject.Thatobjectiscalledthetarget.Themethodthatistriggerediscalledtheaction.Thisactionisthenameofthemethodthatcontainsthecodetobeexecutedinresponsetothebuttonbeingtapped.
Inyourapplication,thetargetforbothbuttonswillbetheinstanceofViewController.Eachbuttonwillhaveitsownaction.Let’sstartbydefiningthetwoactionmethods:showNextQuestion(_:)andshowAnswer(_:).
ReopenViewController.swiftandaddthetwoactionmethodsaftertheoutlets.classViewController:UIViewController{
@IBOutletvarquestionLabel:UILabel!@IBOutletvaranswerLabel:UILabel!
@IBActionfuncshowNextQuestion(sender:AnyObject){
}
@IBActionfuncshowAnswer(sender:AnyObject){
}}
WOW! eBook www.wowebook.org
Youwillfleshoutthesemethodsafteryoumakethetargetandactionconnections.The@IBActionkeywordtellsXcodethatyouwillbemakingtheseconnectionsinInterfaceBuilder.
Settingtargetsandactions
SwitchbacktoMain.storyboard.Let’sstartwiththeNextQuestionbutton.YouwantitstargettobeViewControlleranditsactiontobeshowNextQuestion(_:).
Tosetanobject’starget,youControl-dragfromtheobjecttoitstarget.Whenyoureleasethemouse,thetargetisset,andapop-upmenuappearsthatletsyouselectanaction.
SelecttheNextQuestionbuttoninthecanvasandControl-dragtotheViewController.WhentheViewControllerishighlighted,releasethemousebuttonandchooseshowNextQuestion:underSentEventsinthepop-upmenu,asshowninFigure1.23.
Figure1.23SettingNextQuestiontarget/action
NowfortheShowAnswerbutton.SelectthebuttonandControl-dragfromthebuttontotheViewController.ThenchooseshowAnswer:fromthepop-upmenu.
Summaryofconnections
TherearenowfiveconnectionsbetweentheViewControllerandtheviewobjects.YouhavesetthepropertiesanswerLabelandquestionLabeltoreferencethelabelobjects–twoconnections.TheViewControlleristhetargetforbothbuttons–twomore.Theproject’stemplatemadeoneadditionalconnection:theviewpropertyofViewControllerisconnectedtotheViewobjectthatrepresentsthebackgroundoftheapplication.Thatmakesfive.
WOW! eBook www.wowebook.org
Youcanchecktheseconnectionsintheconnectionsinspector.SelecttheViewControllerinthedocumentoutline.Then,intheutilitiesarea,clickthe tabtorevealtheconnectionsinspector(Figure1.24).
Figure1.24Checkingconnectionsintheinspector
Yourstoryboardfileiscomplete.Theviewobjectshavebeencreatedandconfiguredandallthenecessaryconnectionshavebeenmadetothecontrollerobject.Let’smoveontocreatingandconnectingyourmodelobjects.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingtheModelLayerViewobjectsmakeuptheuserinterface,sodeveloperstypicallycreate,configure,andconnectviewobjectsusingInterfaceBuilder.Thepartsofthemodellayer,ontheotherhand,aretypicallysetupincode.
Intheprojectnavigator,selectViewController.swift.Addthefollowingcodethatdeclarestwoarraysofstringsandaninteger.classViewController:UIViewController{
@IBOutletvarquestionLabel:UILabel!@IBOutletvaranswerLabel:UILabel!
letquestions:[String]=["Fromwhatiscognacmade?","Whatis7+7?","WhatisthecapitalofVermont?"]letanswers:[String]=["Grapes","14","Montpelier"]varcurrentQuestionIndex:Int=0
...}
Thearrayswillbeorderedlistscontainingquestionsandanswers.Theintegerwillkeeptrackofwhatquestiontheuserison.
Noticethatthearraysaredeclaredusingtheletkeyword,whereastheintegerisdeclaredusingthevarkeyword.Aconstantisdenotedwiththeletkeyword;itsvaluecannotchange.Thequestionsandanswersarraysareconstants.Thequestionsandanswersinthisquizwillnotchangeand,infact,cannotbechangedfromtheirinitialvalues.
Avariable,ontheotherhand,isdenotedbythevarkeyword;itsvalueisallowedtochange.YoumadethecurrentQuestionIndexpropertyavariablebecauseitsvaluemustbeabletochangeastheusercyclesthroughthequestionsandanswers.
Implementingactionmethods
Nowthatyouhavequestionsandanswers,youcanfinishimplementingtheactionmethods.InViewController.swift,updateshowNextQuestion(_:)andshowAnswer(_:)....@IBActionfuncshowNextQuestion(sender:AnyObject){++currentQuestionIndexifcurrentQuestionIndex==questions.count{currentQuestionIndex=0}
letquestion:String=questions[currentQuestionIndex]questionLabel.text=questionanswerLabel.text="???"}
@IBActionfuncshowAnswer(sender:AnyObject){letanswer:String=answers[currentQuestionIndex]answerLabel.text=answer}
WOW! eBook www.wowebook.org
Loadingthefirstquestion
Justaftertheapplicationislaunched,youwillwanttoloadthefirstquestionfromthearrayanduseittoreplacethe???placeholderinthequestionLabellabel.AgoodwaytodothisisbyoverridingtheviewDidLoad()methodofViewController.(“Override”meansthatyouareprovidingacustomimplementationforamethod.)AddthemethodtoViewController.swift.classViewController:UIViewController{...overridefuncviewDidLoad(){super.viewDidLoad()questionLabel.text=questions[currentQuestionIndex]}}
Allthecodeforyourapplicationisnowcomplete!
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BuildingtheFinishedApplicationBuildandruntheapplicationontheiPhone6ssimulator,asyoudidearlier.
Ifbuildingturnsupanyerrors,youcanviewthemintheissuenavigatorbyselectingthetabinthenavigatorarea(Figure1.25).
Figure1.25Issuenavigatorwithexampleerrorsandwarnings
Clickonanyerrororwarningintheissuenavigatortobetakentothefileandthelineofcodewheretheissueoccurred.Findandfixanyproblems(i.e.,codetypos!)bycomparingyourcodewiththecodeinthischapter.Thentryrunningtheapplicationagain.Repeatthisprocessuntilyourapplicationcompiles.
Onceyourapplicationhascompiled,itwilllaunchintheiOSsimulator.PlayaroundwiththeQuizapplication.YoushouldbeabletotaptheNextQuestionbuttonandseeanewquestioninthetoplabel;tappingShowAnswershouldshowtherightanswer.Ifyourapplicationisnotworkingasexpected,double-checkyourconnectionsinMain.storyboard.
YouhavebuiltaworkingiOSapp!Takeamomenttobaskintheglory.
OK,enoughbasking.Yourappworks,butitneedssomespitandpolish.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ApplicationIconsWhilerunningQuiz,selectHardware→Homefromthesimulator’smenu.YouwillseethatQuiz’siconisaboring,defaulttile.Let’sgiveQuizabettericon.
AnapplicationiconisasimpleimagethatrepresentstheapplicationontheiOSHomescreen.Differentdevicesrequiredifferent-sizedicons,someofwhichareshowninTable1.1.
Table1.1Applicationiconsizesbydevice
Device Applicationiconsizes
iPhone6sPlusandiPhone6Plus 180x180pixels(@3x)
iPhone6s,iPhone6,andiPhone5 120x120pixels(@2x)
iPadandiPadmini 152x152pixels(@2x)
iPadPro 167x167pixels(@2x)
Wehavepreparedaniconimagefile(size120x120)fortheQuizapplication.Youcandownloadthisicon(alongwithresourcesforotherchapters)fromhttp://www.bignerdranch.com/solutions/iOSProgramming5ed.zip.UnzipiOSProgramming5ed.zipandfindtheIcon@2x.pngfileintheResourcesdirectoryoftheunzippedfolder.
Youaregoingtoaddthisicontoyourapplicationbundleasaresource.Ingeneral,therearetwokindsoffilesinanapplication:codeandresources.Code(likeViewController.swift)isusedtocreatetheapplicationitself.Resourcesarethingslikeimagesandsoundsthatareusedbytheapplicationatruntime.
Intheprojectnavigator,findAssets.xcassets.SelectthisfiletoopenitandthenselectAppIconfromtheresourcelistonthelefthandside(Figure1.26).
Figure1.26ShowingtheAssetCatalog
WOW! eBook www.wowebook.org
ThispanelistheAssetCatalog,whereyoucanmanagealloftheimagesthatyourapplicationwillneed.
DragtheIcon@2x.pngfilefromFinderontothemarginsoftheAppIconsection.Thiswillcopythefileintoyourproject’sdirectoryonthefilesystemandaddareferencetothatfileintheAssetCatalog.(YoucanControl-clickonafileintheAssetCatalogandselecttheoptiontoShowinFindertoconfirmthis,asshowninFigure1.27.)
Figure1.27AddingtheappicontotheAssetCatalog
Buildandruntheapplicationagain.Switchtothesimulator’sHomescreeneitherbyclickingHome→Hardware,asyoudidbefore,orbyusingthekeyboardshortcutCommand-Shift-H.Youshouldseethenewicon.
(Ifyoudonotseetheicon,deletetheapplicationandthenbuildandrunagaintoredeployit.Todothis,theeasiestoptionistoresetthesimulatorbyclickingiOSSimulator→ResetContentandSettings….Thiswillremoveallapplicationsandresetthesimulatortoitsdefaultsettings.Youshouldseetheappiconthenexttimeyouruntheapplication.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
LaunchScreenAnotheritemyoushouldsetfortheprojectisthelaunchimage,whichappearswhileanapplicationisloading.ThelaunchimagehasaspecificroleiniOS:itconveystotheuserthattheapplicationisindeedlaunchinganddepictstheuserinterfacethattheuserwillinteractwithoncetheapplicationloads.Therefore,agoodlaunchimageisacontent-lessscreenshotoftheapplication.Forexample,theClockapplication’slaunchimageshowsthefourtabsalongthebottom,allintheunselectedstate.Oncetheapplicationloads,thecorrecttabisselectedandthecontentbecomesvisible.(Keepinmindthatthelaunchimageisreplacedaftertheapplicationhaslaunched;itdoesnotbecomethebackgroundimageoftheapplication.)
AneasywaytoaccomplishthisistoallowXcodetogeneratethepossiblelaunchscreenimagesforyouusingalaunchscreenfile.
Opentheprojectsettingsbyclickingonthetop-levelQuizintheprojectnavigator.UnderAppIconsandLaunchImages,chooseMain.storyboardfromtheLaunchScreenFiledropdown(Figure1.28).LaunchimageswillnowbegeneratedfromMain.storyboard.
Figure1.28Settingthelaunchscreenfile
Congratulations!Youhavewrittenyourfirstapplicationandevenaddedsomedetailstomakeitpolished.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
2TheSwiftLanguage
SwiftisanewlanguagethatAppleintroducedin2014.ItreplacesObjective-CastherecommendeddevelopmentlanguageforiOSandMac.Inthischapter,youaregoingtofocusonthebasicsofSwift.Youwillnotlearneverything,butyouwilllearnenoughtogetstarted.Then,asyoucontinuethroughthebook,youwilllearnmoreSwiftwhileyoulearniOSdevelopment.
SwiftmaintainstheexpressivenessofObjective-Cwhileintroducingasyntaxthatissafer,succinct,andreadable.Itemphasizestypesafetyandintroducesadvancedfeaturessuchasoptionals,generics,andsophisticatedstructuresandenumerations.Mostimportantly,Swiftallowstheuseofthesenewfeatureswhilerelyingonthesametested,elegantiOSframeworksthatdevelopershavebuiltuponforyears.
IfyouknowObjective-C,thenthechallengeisrecastingwhatyouknow.Itmayseemawkwardatfirst,butwehavecometoloveSwiftatBigNerdRanchandbelieveyouwill,too.
IfyoudonotthinkyouwillbecomfortablepickingupSwiftatthesametimeasiOSdevelopment,youmaywanttostartwithSwiftProgramming:TheBigNerdRanchGuideorApple’sSwifttutorials,whichyoucanfindathttp://developer.apple.com/swift.Butifyouhavesomeprogrammingexperienceandarewillingtolearn“onthejob,”youcanstartyourSwifteducationhereandnow.
WOW! eBook www.wowebook.org
TypesinSwiftSwifttypescanbearrangedintothreebasicgroups:structures,classes,andenumerations(Figure2.1).Allthreecanhave
properties:valuesassociatedwithatype
initializers:codethatinitializesaninstanceofatype
instancemethods:functionsspecifictoatypethatcanbecalledonaninstanceofthattype
classorstaticmethods:functionsspecifictoatypethatcanbecalledonthetypeitself
Figure2.1Swiftbuildingblocks
Swift’sstructures(or“structs”)andenumerations(or“enums”)aresignificantlymorepowerfulthaninmostlanguages.Inadditiontosupportingproperties,initializers,andmethods,theycanalsoconformtoprotocolsandcanbeextended.
Swift’simplementationoftypically“primitive”typessuchasnumbersandBooleanvaluesmaysurpriseyou:theyareallstructures.Infact,alloftheseSwifttypesarestructures:
Numbers: Int,Float,Double
Boolean: Bool
Text: String,Character
Collections: Array<T>,Dictionary<K:Hashable,V>,Set<T:Hashable>
Thismeansthatstandardtypeshaveproperties,initializers,andmethodsoftheirown.Theycanalsoconformtoprotocolsandbeextended.
Finally,akeyfeatureofSwiftisoptionals.Anoptionalallowsyoutostoreeitheravalueofaparticulartypeornovalueatall.Laterinthechapter,youwilllearnmoreaboutoptionalsandtheirroleinSwift.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UsingStandardTypesInthissection,youaregoingtoexperimentwithstandardtypesinanXcodeplayground.Aplaygroundletsyouwritecodeandseetheresultswithouttheoverheadofmanuallyrunninganapplicationandcheckingtheoutput.
InXcode,selectFile→New→Playground….Youcanacceptthedefaultnameforthisfile;youwillonlybeherebriefly.MakesuretheplatformisiOS(Figure2.2).
Figure2.2Configuringaplayground
ClickNextandsavethisfileinaconvenientplace.
Whenthefileopens,noticethattheplaygroundisdividedintotwosections(Figure2.3).Thelargerwhiteareatotheleftistheeditorwhereyouwritecode.Thegraycolumnontherightisthesidebar.Theplaygroundcompilesandexecutesyourcodeaftereverylineandshowstheresultsinthesidebar.
Figure2.3Aplayground
WOW! eBook www.wowebook.org
Intheexamplecode,thevarkeyworddenotesavariable,sothevalueofstrcanbechangedfromitsinitialvalue.Typeinthecodebelowtochangethevalueofstr,andyouwillseetheresultsappearinthesidebartotheright.varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"
(Noticethatweareshowingsidebarresultstotherightofthecodeforthebenefitofreaderswhoarenotactivelydoingtheexercise.)
Theletkeyworddenotesaconstantvalue,whichcannotbechanged.InyourSwiftcode,youshoulduseletunlessyouexpectthevaluewillneedtochange.Addaconstanttothemix:varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"letconstStr=str"Hello,Swift"
BecauseconstStrisaconstant,attemptingtochangeitsvaluewillcauseanerror.varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"letconstStr=str"Hello,Swift"constStr="Hello,world"
Anerrorappears,indicatedbytheredsymboltotheleftoftheoffendingline.Clickthesymboltogetmoreinformationabouttheerror.Inthiscase,theerrorreads“Cannotassigntovalue:‘constStr’isa‘let’constant.”
Anerrorintheplaygroundcodewillpreventyoufromseeinganyfurtherresultsinthesidebar,soyouusuallywanttoaddressitrightaway.RemovethelinethatattemptstochangethevalueofconstStr.varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"letconstStr=str"Hello,Swift"constStr="Hello,world"
Inferringtypes
Atthispoint,youmayhavenoticedthatneithertheconstStrconstantnorthestrvariablehasaspecifiedtype.Thisdoesnotmeantheyareuntyped!Instead,thecompilerinferstheirtypesfromtheinitialvalues.Thisiscalledtypeinference.
YoucanfindoutwhattypewasinferredusingQuickHelp.Option-clickonconstStrtoseetheQuickHelpinformationforthisconstant,showninFigure2.4.
WOW! eBook www.wowebook.org
Figure2.4constStrisoftypeString
Option-clickingtorevealQuickHelpwillworkforanysymbol.
Specifyingtypes
Ifyourconstantorvariablehasaninitialvalue,youcanrelyontypeinference.Ifaconstantorvariabledoesnothaveaninitialvalueorifyouwanttoensurethatitisacertaintype,youcanspecifythetypeinthedeclaration.
Addmorevariableswithspecifiedtypes:varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"letconstStr=str"Hello,Swift"
varnextYear:IntvarbodyTemp:FloatvarhasPet:Bool
Notethatthesidebardoesnotreportanyresultsbecausethesevariablesdonotyethavevalues.
Let’sgooverthesenewtypesandhowtheyareused.
NumberandBooleantypes
ThemostcommontypeforintegersisInt.Thereareadditionalintegertypesbasedonwordsizeandsignedness,butApplerecommendsusingIntunlessyoureallyhaveareasontousesomethingelse.
Forfloating-pointnumbers,Swiftprovidesthreetypeswithdifferentlevelsofprecision:Floatfor32-bitnumbers,Doublefor64-bitnumbers,andFloat80for80-bitnumbers.
ABooleanvalueisexpressedinSwiftusingthetypeBool.ABool’svalueiseithertrueorfalse.
Collectiontypes
TheSwiftstandardlibraryoffersthreecollections:arrays,dictionaries,andsets.
WOW! eBook www.wowebook.org
Anarrayisanorderedcollectionofelements.ThearraytypeiswrittenasArray<T>,whereTisthetypeofelementthatthearraywillcontain.Arrayscancontainelementsofanytype:astandardtype,astructure,oraclass.
Addavariableforanarrayofintegers:varhasPet:BoolvararrayOfInts:Array<Int>
Arraysarestronglytyped.Onceyoudeclareanarrayascontainingelementsof,say,Int,youcannotaddaStringtothisarray.
Thereisashorthandsyntaxfordeclaringarrays:youcansimplyusesquarebracketsaroundthetypethatthearraywillcontain.UpdatethedeclarationofarrayOfIntstousetheshorthand:varhasPet:BoolvararrayOfInts:Array<Int>vararrayOfInts:[Int]
Adictionaryisanunorderedcollectionofkey-valuepairs.Thevaluescanbeofanytype,includingstructuresandclasses.Thekeyscanbeofanytypeaswell,buttheymustbeunique.Specifically,thekeysmustbehashable,whichallowsthedictionarytoguaranteethatthekeysareuniqueaswellasaccessthevalueforagivenkeymoreefficiently.BasicSwifttypessuchasInt,Float,Character,andStringareallhashable.
LikeSwiftarrays,Swiftdictionariesarestronglytypedandcanonlycontainkeysandvaluesofthedeclaredtype.Forexample,youmighthaveadictionarythatstorescapitalcitiesbycountry.Thekeysforthisdictionarywouldbethecountrynames,andthevalueswouldbethecitynames.Bothkeysandvalueswouldbestrings,andyouwouldnotbeabletoaddakeyorvalueofanyothertype.
Addavariableforsuchadictionary:vararrayOfInts:[Int]vardictionaryOfCapitalsByCountry:Dictionary<String,String>
Thereisashorthandsyntaxfordeclaringdictionaries,too.UpdatedictionaryOfCapitalsByCountrytousetheshorthand:vararrayOfInts:[Int]vardictionaryOfCapitalsByCountry:Dictionary<String,String>vardictionaryOfCapitalsByCountry:[String:String]
Asetissimilartoanarrayinthatitcontainsanumberofelementsofacertaintype.However,setsareunordered,andthemembersmustbeuniqueaswellashashable.Theunorderednessofsetsmakesthemfasterwhenyousimplyneedtodeterminewhethersomethingisamemberofaset.Addavariableforaset:varwinningLotteryNumbers:Set<Int>
Literalsandsubscripting
Standardtypescanbeassignedliteralvalues,orliterals.Forexample,strisassignedthevalueofastringliteral.Astringliteralisformedwithdoublequotes.ContrasttheliteralvalueassignedtostrwiththenonliteralvalueassignedtoconstStr:
WOW! eBook www.wowebook.org
varstr="Hello,playground""Hello,playground"str="Hello,Swift""Hello,Swift"letconstStr=str"Hello,Swift"
Addtwonumberliteralstoyourplayground:letnumber=4242letfmStation=91.191.1
Arraysanddictionariescanbeassignedliteralvaluesaswell.Thesyntaxforcreatingliteralarraysanddictionariesresemblestheshorthandsyntaxforspecifyingthesetypes.letcountingUp=["one","two"]["one","two"]letnameByParkingSpace=[13:"Alice",27:"Bob"][13:"Alice",27:"Bob"]
Swiftalsoprovidessubscriptingasshorthandforaccessingarrays.Toretrieveanelementinanarray,youprovidetheelement’sindexinsquarebracketsafterthearrayname.letcountingUp=["one","two"]["one","two"]letsecondElement=countingUp[1]"two"...
Noticethatindex1retrievesthesecondelement;anarray’sindexalwaysstartsat0.
Whensubscriptinganarray,besurethatyouareusingavalidindex.Attemptingtoaccessanout-of-boundsindexresultsinatrap.Atrapisaruntimeerrorthatstopstheprogrambeforeitgetsintoanunknownstate.
Subscriptingalsoworkswithdictionaries–moreonthatlaterinthischapter.
Initializers
Sofar,youhaveinitializedyourconstantsandvariablesusingliteralvalues.Indoingso,youcreatedinstancesofaspecifictype.Aninstanceisaparticularembodimentofatype.Historically,thistermhasbeenonlyusedwithclasses,butinSwiftitisusedtodescribestructuresandenumerations,too.Forexample,theconstantsecondElementholdsaninstanceofString.
Anotherwayofcreatinginstancesisbyusinganinitializeronthetype.Initializersareresponsibleforinitializingthecontentsofanewinstanceofatype.Whenaninitializerisfinished,theinstanceisreadyforaction.Tocreateanewinstanceusinganinitializer,youusethetypenamefollowedbyapairofparenthesesand,ifrequired,arguments.Thissignature–thecombinationoftypeandarguments–correspondstoaspecificinitializer.
Somestandardtypeshaveinitializersthatreturnemptyliteralswhennoargumentsaresupplied.Addanemptystringandanemptyarraytoyourplayground.letemptyString=String()""letemptyArrayOfInts=[Int]()0elementsletemptySetOfFloats=Set<Float>()0elements
Othertypeshavedefaultvalues:letdefaultNumber=Int()0letdefaultBool=Bool()false
Typescanhavemultipleinitializers.Forexample,StringhasaninitializerthatacceptsanIntandcreatesastringbasedonthatvalue.letnumber=4242
WOW! eBook www.wowebook.org
letmeaningOfLife=String(number)"42"
Tocreateaset,youusetheSetinitializerthatacceptsanarrayliteral:letavailableRooms=Set([205,411,412]){412,205,411}
Floathasseveralinitializers.Theparameter-lessinitializerreturnsaninstanceofFloatwiththedefaultvalue.Thereisalsoaninitializerthatacceptsafloating-pointliteral.letdefaultFloat=Float()0.0letfloatFromLiteral=Float(3.14)3.14
Ifyouusetypeinferenceforafloating-pointliteral,thetypedefaultstoDouble.Createthefollowingconstantwithafloating-pointliteral:leteasyPi=3.143.14
UsetheFloatinitializerthatacceptsaDoubletocreateaFloatfromthisDouble:leteasyPi=3.143.14letfloatFromDouble=Float(easyPi)3.14
Youcanachievethesameresultbyspecifyingthetypeinthedeclaration.leteasyPi=3.143.14letfloatFromDouble=Float(easyPi)3.14letfloatingPi:Float=3.143.14
Properties
Apropertyisavalueassociatedwithaninstanceofatype.Forexample,StringhasthepropertyisEmpty,whichisaBoolthattellsyouwhetherthestringisempty.Array<T>hasthepropertycount,whichisthenumberofelementsinthearrayasanInt.Accessthesepropertiesinyourplayground:letcountingUp=["one","two"]["one","two"]letsecondElement=countingUp[1]"two"countingUp.count2
...
letemptyString=""emptyString.isEmptytrue
Instancemethods
Aninstancemethodisafunctionthatisspecifictoaparticulartypeandcanbecalledonaninstanceofthattype.Tryouttheappend(_:)instancemethodfromArray<T>.YouwillfirstneedtochangeyourcountingUparrayfromaconstanttoavariable.letcountingUp=["one","two"]varcountingUp=["one","two"]["one","two"]letsecondElement=countingUp[1]"two"countingUp.count
countingUp.append("three")["one","two","three"]
Theappend(_:)methodacceptsanelementofthearray’stypeandaddsittotheendofthearray.Wewilldiscussmethods,includingnaming,inChapter3.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
OptionalsSwifttypescanbeoptional,whichisindicatedbyappending?toatypename.varanOptionalFloat:Float?varanOptionalArrayOfStrings:[String]?varanOptionalArrayOfOptionalStrings:[String?]?
Anoptionalletsyouexpressthepossibilitythatavariablemaynotstoreavalueatall.Thevalueofanoptionalwilleitherbeaninstanceofthespecifiedtypeornil.
Throughoutthisbook,youwillhavemanychancestouseoptionals.Whatfollowsisanexampletogetyoufamiliarwiththesyntaxsothatyoucanfocusontheuseoftheoptionalslater.
Imagineagroupofinstrumentreadings:varreading1:Floatvarreading2:Floatvarreading3:Float
Sometimes,aninstrumentmightmalfunctionandnotreportareading.Youdonotwantthismalfunctionshowingupas,say,0.0.Youwantittobesomethingcompletelydifferentthattellsyoutocheckyourinstrumentortakesomeotheraction.
Youcandothisbydeclaringthereadingsasoptionals.varreading1:Float?nilvarreading2:Float?nilvarreading3:Float?nil
Asanoptionalfloat,eachreadingcancontaineitheraFloatornil.Ifnotgivenaninitialvalue,thenthevaluedefaultstonil.
Youcanassignvaluestoanoptionaljustlikeanyothervariable.Assignfloating-pointliteralstothereadings:reading1=9.89.8reading2=9.29.2reading3=9.79.7
However,youcannotusetheseoptionalfloatslikenon-optionalfloats–eveniftheyhavebeenassignedFloatvalues.Beforeyoucanreadthevalueofanoptionalvariable,youmustaddressthepossibilityofitsvaluebeingnil.Thisiscalledunwrappingtheoptional.
Youaregoingtotryouttwowaysofunwrappinganoptionalvariable:optionalbindingandforcedunwrapping.Youwillimplementforcedunwrappingfirst.Thisisnotbecauseitisthebetteroption–infact,itisthelesssafeone.Butimplementingforcedunwrappingfirstwillletyouseethedangersandunderstandwhyoptionalbindingistypicallybetter.
Toforciblyunwrapanoptional,youappenda!toitsname.First,tryaveragingthereadingsasiftheywerenon-optionalvariables:reading1=9.89.8reading2=9.29.2reading3=9.79.7letavgReading=(reading1+reading2+reading3)/3
WOW! eBook www.wowebook.org
Thisresultsinanerrorbecauseoptionalsrequireunwrapping.Forciblyunwrapthereadingstofixtheerror:letavgReading=(reading1+reading2+reading3)/3letavgReading=(reading1!+reading2!+reading3!)/39.566667
Everythinglooksfine,andyouseethecorrectaverageinthesidebar.Butadangerlurksinyourcode.Whenyouforciblyunwrapanoptional,youtellthecompilerthatyouaresurethattheoptionalwillnotbenilandcanbetreatedasifitwereanormalFloat.Butwhatifyouarewrong?Tofindout,commentouttheassignmentofreading3,whichwillreturnittoitsdefaultvalue,nil.reading1=9.89.8reading2=9.29.2reading3=9.7//reading3=9.7
Younowhaveanerror.Toseetheconsolewheretheerrorisreportedinaplayground,youmustopenthedebugarea.FromXcode’sViewmenu,selectDebugArea→ShowDebugArea.Theerrorreads:fatalerror:unexpectedlyfoundnilwhileunwrappinganOptionalvalue
Ifyouforciblyunwrapanoptionalandthatoptionalturnsouttobenil,itwillcauseatrap,stoppingyourapplication.
Asaferwaytounwrapanoptionalisoptionalbinding.Optionalbindingworkswithinaconditionalif-letstatement:Youassigntheoptionaltoatemporaryconstantofthecorrespondingnon-optionaltype.Ifyouroptionalhasavalue,thentheassignmentisvalidandyouproceedusingthenon-optionalconstant.Iftheoptionalisnil,thenyoucanhandlethatcasewithanelseclause.
Changeyourcodetouseanif-letstatementthattestsforvalidvaluesinallthreereadings.letavgReading=(reading1!+reading2!+reading3!)/3ifletr1=reading1,r2=reading2,r3=reading3{letavgReading=(r1+r2+r3)/3}else{leterrorString="Instrumentreportedareadingthatwasnil."}
reading3iscurrentlynil,soitsassignmenttor3fails,andthesidebarshowstheerrorstring.
Toseetheothercaseinaction,restorethelinethatassignsavaluetoreading3.Nowthatallthreereadingshavevalues,allthreeassignmentsarevalid,andthesidebarupdatestoshowtheaverageofthethreereadings.
Subscriptingdictionaries
Recallthatsubscriptinganarraybeyonditsboundscausesatrap.Dictionariesaredifferent.Theresultofsubscriptingadictionaryisanoptional:letnameByParkingSpace=[13:"Alice",27:"Bob"][13:"Alice",27:"Bob"]letspace13Assignee:String?=nameByParkingSpace[13]"Alice"
WOW! eBook www.wowebook.org
letspace42Assignee:String?=nameByParkingSpace[42]nil
Ifthekeyisnotinthedictionary,theresultwillbenil.Aswithotheroptionals,itiscommontouseif-letwhensubscriptingadictionary:letspace13Assignee:String?=nameByParkingSpace[13]ifletspace13Assignee=nameByParkingSpace[13]{print("Key13isassignedinthedictionary!")}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
LoopsandStringInterpolationSwifthasallthecontrolflowstatementsthatyoumaybefamiliarwithfromotherlanguages:if-else,while,for,for-in,repeat-while,andswitch.Eveniftheyarefamiliar,however,theremaybesomedifferencesfromwhatyouareaccustomedto.ThekeydifferencebetweenthesestatementsinSwiftandinC-likelanguagesisthatwhileenclosingparenthesesarenotnecessaryonthesestatements’expressions,Swiftdoesrequirebracesonclauses.Additionally,theexpressionsforifandwhile-likestatementsmustevaluatetoaBool.
ConsiderthisverytraditionalC-styleloop,writteninSwift:forvari=0;i<countingUp.count;++i{letstring=countingUp[i]//Use'string'}
YoucoulddothesamethingalittlemorecleanlyusingSwift’sRangetypeandthefor-instatement:letrange=0..<countingUp.countforiinrange{letstring=countingUp[i]//Use'string'}
Themostdirectroutewouldbetoenumeratetheitemsinthearraythemselves:forstringincountingUp{//Use'string'}
Whatifyouwantedtheindexofeachiteminthearray?Swift’senumerate()functionreturnsasequenceofintegersandvaluesfromitsargument:for(i,string)incountingUp.enumerate(){//(0,"one"),(1,"two")}
Whatarethoseparentheses,youask?Theenumerate()functionactuallyreturnsasequenceoftuples.Atupleisanorderedgroupingofvaluessimilartoanarray,excepteachmembermayhaveadistincttype.Inthisexamplethetupleisoftype(Int,String).WewillnotspendmuchtimeontuplesinthisbookastheyarenotusediniOSAPIs(becauseObjective-Cdoesnotsupporttuples).TheycanbeusefulinyourSwiftcode,however.
Anotherapplicationoftuplesisinenumeratingthecontentsofadictionary:letnameByParkingSpace=[13:"Alice",27:"Bob"]
for(space,name)innameByParkingSpace{letpermit="Space\(space):\(name)"}
Didyounoticethatcuriousmarkupinthestringliteral?ThatisSwift’sstringinterpolation.Expressionsenclosedwithin\(and)areevaluatedandinsertedintothestringatruntime.Inthisexampleyouareusinglocalvariables,butanyvalidSwiftexpression,suchasamethodcall,canbeused.
WOW! eBook www.wowebook.org
Toseethevaluesofthepermitvariableintheplayground,Control-clickontheresultandselectValueHistory.Ifyoudonotseeavalue,clickonthecircularShowResultindicator(Figure2.5).Thiscanbeveryusefulforvisualizingwhatishappeninginyourplaygroundcode’sloops.
Figure2.5UsingtheValueHistorytoseetheresultsofstringinterpolation
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
EnumerationsandtheSwitchStatementAnenumerationisatypewithadiscretesetofvalues.Defineanenumdescribingpies:enumPieType{caseApplecaseCherrycasePecan}
letfavoritePie=PieType.Apple
Swifthasapowerfulswitchstatementthat,amongotherthings,isgreatformatchingonenumvalues:letname:StringswitchfavoritePie{case.Apple:name="Apple"case.Cherry:name="Cherry"case.Pecan:name="Pecan"}
Thecasesforaswitchstatementmustbeexhaustive:eachpossiblevalueoftheswitchexpressionmustbeaccountedfor,whetherexplicitlyorviaadefault:case.UnlikeinC,Swiftswitchcasesdonotfallthrough–onlythecodeforthecasethatismatchedisexecuted.(Ifyouneedthefall-throughbehaviorofC,youcanexplicitlyrequestitusingthefallthroughkeyword.)
Switchstatementscanmatchonmanytypes,evenranges:letosxVersion:Int=...switchosxVersion{case0...8:print("Abigcat")case9:print("Mavericks")case10:print("Yosemite")default:print("Greetings,peopleofthefuture!What'snewin10.\(osxVersion)?")}
Formoreontheswitchstatementanditspatternmatchingcapabilities,seetheControlFlowsectionintheTheSwiftProgrammingLanguageguide.(Moreonthatinjustamoment.)
Enumerationsandrawvalues
Swiftenumscanhaverawvaluesassociatedwiththeircases:enumPieType:Int{caseApple=0caseCherrycasePecan}
Withthetypespecified,youcanaskaninstanceofPieTypeforitsrawValueandtheninitializetheenumtypewiththatvalue.Thisreturnsanoptional,sincetherawvaluemaynotcorrespondwithanactualcaseoftheenum,soitisagreatcandidateforoptional
WOW! eBook www.wowebook.org
binding.letpieRawValue=PieType.Pecan.rawValue//pieRawValueisanIntwithavalueof2
ifletpieType=PieType(rawValue:pieRawValue){//Gotavalid'pieType'!}
TherawvalueforanenumisoftenanInt,butitcanbeanyintegerorfloating-pointnumbertypeaswellastheStringandCharactertypes.
Whentherawvalueisanintegertype,thevaluesautomaticallyincrementifnoexplicitvalueisgiven.ForPieType,onlytheApplecaseisgivenanexplicitvalue.CherryandPecanautomaticallygetassignedarawValueof1and2,respectively.
Thereismoretoenumerations.Eachcaseofanenumerationcanhaveassociatedvalues.YouwilllearnmoreaboutassociatedvaluesinChapter19.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ExploringApple’sSwiftDocumentationToexploreApple’sdocumentationonSwift,startathttp://developer.apple.com/swift.Herearetwoparticularresourcestolookfor.Wesuggestbookmarkingthemandvisitingthemwhenyouwanttoreviewaparticularconceptordigalittledeeper.
TheSwiftProgrammingLanguage
ThisguidedescribesmanyfeaturesofSwift.Itstartswiththebasicsandincludesexamplecodeandlotsofdetail.ItalsocontainsthelanguagereferenceandformalgrammarofSwift.
TheSwiftStandardLibraryReference
ThestandardlibraryreferencelaysoutthedetailsofSwifttypes,protocols,andglobal,orfree,functions.
YourhomeworkistobrowsethroughtheTypessectionoftheSwiftStandardLibraryReferenceandthesectionsofTheSwiftProgrammingLanguageguideonTheBasics,StringsandCharacters,andCollectionTypes.Solidifywhatyoulearnedinthischapterandbecomefamiliarwiththeinformationtheseresourcesoffer.Ifyouknowwheretofindthedetailswhenyouneedthem,thenyouwillfeellesspressuretomemorizethem–lettingyoufocusoniOSdevelopmentinstead.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
3ViewsandtheViewHierarchy
Overthenextfivechapters,youaregoingtobuildanapplicationnamedWorldTrotter.Whenitiscomplete,thisappwillconvertvaluesbetweendegreesFahrenheitanddegreesCelsius.Inthischapter,youwilllearnaboutviewsandtheviewhierarchythroughcreatingWorldTrotter’suserinterface.Attheendofthischapter,yourappwilllooklikeFigure3.1.
Figure3.1WorldTrotter
Let’sstartwithalittlebitofthetheorybehindviewsandtheviewhierarchy.
WOW! eBook www.wowebook.org
ViewBasicsRecallfromChapter1thatviewsareobjectsthatarevisibletotheuser,likebuttons,textfields,andsliders.Viewobjectsmakeupanapplication’suserinterface.Aview
isaninstanceofUIVieworoneofitssubclasses
knowshowtodrawitself
canhandleevents,liketouches
existswithinahierarchyofviewswhoserootistheapplication’swindow
Let’slookattheviewhierarchyingreaterdetail.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TheViewHierarchyEveryapplicationhasasingleinstanceofUIWindowthatservesasthecontainerforalltheviewsintheapplication.UIWindowisasubclassofUIView,sothewindowisitselfaview.Thewindowiscreatedwhentheapplicationlaunches.Oncethewindowiscreated,otherviewscanbeaddedtoit.
Whenaviewisaddedtothewindow,itissaidtobeasubviewofthewindow.Viewsthataresubviewsofthewindowcanalsohavesubviews,andtheresultisahierarchyofviewobjectswiththewindowatitsroot(Figure3.2).
Figure3.2Anexampleviewhierarchyandtheinterfacethatitcreates
Oncetheviewhierarchyiscreated,itwillbedrawntothescreen.Thisprocesscanbebrokenintotwosteps:
Eachviewinthehierarchy,includingthewindow,drawsitself.Itrendersitselftoitslayer,whichyoucanthinkofasabitmapimage.(ThelayerisaninstanceofCALayer.)
Thelayersofalltheviewsarecompositedtogetheronthescreen.
Figure3.3showsanotherexampleviewhierarchyandthetwodrawingsteps.
WOW! eBook www.wowebook.org
Figure3.3Viewsrenderthemselvesandthenarecompositedtogether
ForWorldTrotter,youaregoingtocreateaninterfacecomposedofdifferentviews.TherewillbefourinstancesofUILabelandoneinstanceofUITextFieldthatwillallowtheusertoenterinatemperatureinFahrenheit.Let’sgetstarted.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingaNewProjectInXcode,selectFile→New→Project…(orusethekeyboardshortcutCommand-Shift-N).FromtheiOSsection,selectApplication,choosetheSingleViewApplicationtemplate,andclickNext.
EnterWorldTrotterfortheproductname.MakesurethatSwiftisselectedfromtheLanguagedropdownandthatiPhoneisselectedfromtheDevicesdropdown.AlsomakesuretheUseCoreDataboxisunchecked(Figure3.4).ClickNextandthenCreateonthefollowingscreen.
Figure3.4ConfiguringWorldTrotter
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ViewsandFramesWhenyouinitializeaviewprogrammatically,youuseitsinit(frame:)designatedinitializer.Thismethodtakesoneargument,aCGRect,thatwillbecometheview’sframe,apropertyonUIView.varframe:CGRect
Aview’sframespecifiestheview’ssizeanditspositionrelativetoitssuperview.Becauseaview’ssizeisalwaysspecifiedbyitsframe,aviewisalwaysarectangle.
ACGRectcontainsthemembersoriginandsize.TheoriginisastructureoftypeCGPointandcontainstwoCGFloatproperties:xandy.ThesizeisastructureoftypeCGSizeandhastwoCGFloatproperties:widthandheight(Figure3.5).
Figure3.5CGRect
Whentheapplicationislaunched,theviewfortheinitialviewcontrollerisaddedtotheroot-levelwindow.ThisviewcontrollerisrepresentedbytheViewControllerclassdefinedinViewController.swift.WewilldiscusswhataviewcontrollerisinChapter5,butfornow,itissufficienttoknowthataviewcontrollerhasaviewandthattheviewassociatedwiththemainviewcontrollerfortheapplicationisaddedasasubviewofthewindow.
BeforeyoucreatetheviewsforWorldTrotter,youaregoingtoaddsomepracticeviewsprogrammaticallytoexploreviewsandtheirpropertiesandseehowtheinterfacesfor
WOW! eBook www.wowebook.org
applicationsarecreated.
OpenViewController.swiftanddeleteanymethodsthatthetemplatecreated.Yourfileshouldlooklikethis:importUIKit
classViewController:UIViewController{
}
(CuriousabouttheimportUIKitline?UIKitisaframework.Aframeworkisacollectionofrelatedclassesandresources.TheUIKitframeworkdefinesmanyoftheuserinterfaceelementsthatyouruserssee,aswellasotheriOS-specificclasses.Youwillbeusingafewdifferentframeworksasyougothroughthisbook.)
Rightaftertheviewcontroller’sviewisloadedintomemory,itsviewDidLoad()methodiscalled.Thismethodgivesyouanopportunitytocustomizetheviewhierarchy,soitisagreatplacetoaddyourpracticeviews.
InViewController.swift,overrideviewDidLoad().CreateaCGRectthatwillbetheframeofaUIView.Next,createaninstanceofUIViewandsetitsbackgroundColorpropertytoblue.Finally,addtheUIViewasasubviewoftheviewcontroller’sviewtomakeitpartoftheviewhierarchy.(Muchofthiswillnotlookfamiliar.Thatisfine.Wewillexplainmoreafteryouenterthecode.)classViewController:UIViewController{
overridefuncviewDidLoad(){super.viewDidLoad()
letfirstFrame=CGRect(x:160,y:240,width:100,height:150)letfirstView=UIView(frame:firstFrame)firstView.backgroundColor=UIColor.blueColor()view.addSubview(firstView)}
}
TocreateaCGRect,youuseitsinitializerandpassinthevaluesfororigin.x,origin.y,size.width,andsize.height.
TosetthebackgroundColor,youusetheUIColorclassmethodblueColor().ThisisaconveniencemethodthatinitializesaninstanceofUIColorthatisconfiguredtobeblue.ThereareanumberofUIColorconveniencemethodsforcommoncolors,suchasgreenColor(),blackColor(),andclearColor().
Buildandruntheapplication(Command-R).YouwillseeabluerectanglethatistheinstanceofUIView.BecausetheoriginoftheUIView’sframeis(160,240),therectangle’stopleftcorneris160pointstotherightand240pointsdownfromthetopleftcornerofitssuperview.Theviewstretches100pointstotherightand150pointsdownfromitsorigin,inaccordancewithitsframe’ssize(Figure3.6).
WOW! eBook www.wowebook.org
Figure3.6WorldTrotterwithoneUIView
Notethatthesevaluesareinpoints,notpixels.Ifthevalueswereinpixels,thentheywouldnotbeconsistentacrossdisplaysofdifferentresolutions(i.e.,Retinavs.non-Retina).Asinglepointisarelativeunitofameasure;itwillbeadifferentnumberofpixelsdependingonhowmanypixelsareinthedisplay.Sizes,positions,lines,andcurvesarealwaysdescribedinpointstoallowfordifferencesindisplayresolution.
Figure3.7representstheviewhierarchythatyouhavecreated.
WOW! eBook www.wowebook.org
Figure3.7Currentviewhierarchy
EveryinstanceofUIViewhasasuperviewproperty.Whenyouaddaviewasasubviewofanotherview,theinverserelationshipisautomaticallyestablished.Inthiscase,theUIView’ssuperviewistheUIWindow.
Let’sexperimentwiththeviewhierarchy.First,inViewController.swift,createanotherinstanceofUIViewwithadifferentframeandbackgroundcolor.overridefuncviewDidLoad(){super.viewDidLoad()
letfirstFrame=CGRect(x:160,y:240,width:100,height:150)letfirstView=UIView(frame:firstFrame)firstView.backgroundColor=UIColor.blueColor()view.addSubview(firstView)
letsecondFrame=CGRect(x:20,y:30,width:50,height:50)letsecondView=UIView(frame:secondFrame)secondView.backgroundColor=UIColor.greenColor()view.addSubview(secondView)}
Buildandrunagain.Inadditiontothebluerectangle,youwillseeagreensquarenearthetoplefthandcornerofthewindow.Figure3.8showstheupdatedviewhierarchy.
WOW! eBook www.wowebook.org
Figure3.8Updatedviewhierarchywithtwosubviewsassiblings
NowyouaregoingtoadjusttheviewhierarchysothatoneinstanceofUIViewisasubviewoftheotherUIViewinsteadoftheviewcontroller’sview.InViewController.swift,addsecondViewasasubviewoffirstView....
letsecondView=UIView(frame:secondFrame)secondView.backgroundColor=UIColor.greenColor()
view.addSubview(secondView)firstView.addSubview(secondView)
Yourviewhierarchyisnowfourlevelsdeep,asshowninFigure3.9.
WOW! eBook www.wowebook.org
Figure3.9OneUIViewasasubviewoftheother
Buildandruntheapplication.NoticethatsecondView’spositiononthescreenhaschanged(Figure3.10).Aview’sframeisrelativetoitssuperview,sothetopleftcornerofsecondViewisnowinset(20,30)pointsfromthetopleftcorneroffirstView.
WOW! eBook www.wowebook.org
Figure3.10WorldTrotterwithnewhierarchy
(IfthegreeninstanceofUIViewlookssmallerthanitdidpreviously,thatisjustanopticalillusion.Itssizehasnotchanged.)
Nowthatyouhaveseenthebasicsofviewsandtheviewhierarchy,youcanstartworkingontheinterfaceforWorldTrotter.Insteadofbuildinguptheinterfaceprogrammatically,youwilluseInterfaceBuildertovisuallylayouttheinterface,asyoudidinChapter1.
InViewController.swift,startbyremovingyourpracticecode.overridefuncviewDidLoad(){super.viewDidLoad()
letfirstFrame=CGRect(x:160,y:240,width:100,height:150)letfirstView=UIView(frame:firstFrame)firstView.backgroundColor=UIColor.blueColor()view.addSubview(firstView)
letsecondFrame=CGRect(x:20,y:30,width:50,height:50)letsecondView=UIView(frame:secondFrame)secondView.backgroundColor=UIColor.greenColor()firstView.addSubview(secondView)}
Nowlet’saddsomeviewstotheinterfaceandsettheirframes.
OpenMain.storyboard.Noticethattheinterfaceonthescreeniscurrentlyasquare.Whileyouexploreviewsandtheirframes,itwillbenicetohavethesizeoftheinterfaceinXcodematchthescreensizeofthedevicethatyouwillbeusing.
WOW! eBook www.wowebook.org
SelecttheViewControllereitherinthedocumentoutlineorbyclickingtheyellowcircleabovetheinterface.Opentheattributesinspector,whichisthefourthtabintheutilitiesarea.YoucanquicklyopenthispaneusingthekeyboardshortcutCommand-Option-4.
Atthetopofthepane,findthesectionlabeledSimulatedMetricsandchangetheSizetobeiPhone4.7-inch.Thiswillresizethesquareinterfacetomatchthedimensionsofthe4.7-inchdevices.
Fromtheobjectlibrary,dragfiveinstancesofUILabelontothecanvas.Spacethemoutverticallyonthetophalfoftheinterfaceandcenterthemhorizontally.SettheirtexttomatchFigure3.11.
Figure3.11Addinglabelstotheinterface
SelectthetoplabelsoyoucanseeitsframeinInterfaceBuilder.Openitssizeinspector–thefifthtabintheutilitiesarea.(Asyoumighthavenoticedbythispoint,thekeyboardshortcutsfortheutilitiestabsareCommand-Optionplusthetabnumber.Sincethesize
WOW! eBook www.wowebook.org
inspectoristhefifthtab,itskeyboardshortcutisCommand-Option-5.)
UndertheViewsection,findFrameRectangle.(Ifyoudonotseeit,youmightneedtoselectitfromtheShowpop-upmenu.)Thesevaluesaretheview’sframe,andtheydictatethepositionoftheviewonscreen(Figure3.12).
Figure3.12Viewframevalues
BuildandruntheapplicationontheiPhone6ssimulator.Thiscorrespondstothe4.7-inchsimulatedmetricsthatyouspecifiedinthestoryboard.TheinterfaceonthesimulatorwilllookidenticaltotheinterfacethatyoulaidoutinInterfaceBuilder.
Customizingthelabels
Let’smaketheinterfacelookalittlebitbetterbycustomizingtheviewproperties.
InMain.storyboard,selectthebackgroundview.Opentheattributesinspectorandgivetheappanewbackgroundcolor:FindandclicktheBackgrounddropdownandclickOther.Selectthesecondtab(theColorSliderstab)andenteraHexColor#ofF5F4F1(Figure3.13).Thiswillgivethebackgroundawarm,graycolor.
WOW! eBook www.wowebook.org
Figure3.13Changingthebackgroundcolor
Youcancustomizeattributescommontoselectedviewssimultaneously.Youwillusethistogivemanyofthelabelsalargerfontsizeaswellasaburntorangetextcolor.
SelectthetoptwoandbottomtwolabelsbyCommand-clickingtheminthedocumentoutlineandopentheattributesinspector.Updatethetextcolor:UndertheLabelsection,findColorandopenthepop-upmenu.SelecttheColorSliderstabagainandenteraHexColor#ofE15829.
Nowlet’supdatethefont.Selectthe212and100labels.UndertheLabelsectionintheattributesinspector,findFontandclickonthetexticonnexttothecurrentfont.Fromthepopoverthatappears,maketheFontSystem-SystemandtheSize70(Figure3.14).Selecttheremainingthreelabels.OpentheirFontpop-upandmaketheFontSystem-SystemandtheSize36.
WOW! eBook www.wowebook.org
Figure3.14Customizingthelabels’font
Nowthatthefontsizeislarger,thetextnolongerfitswithintheboundsofthelabel.Youcouldresizethelabelsmanually,butthereisaneasierway.
Selectthetoplabelonthecanvas.FromXcode’sEditormenu,selectSizetoFitContent(Command-=).Thiswillresizethelabeltoexactlyfititstextcontents.Repeattheprocessfortheotherfourlabels.(Youcanselectallfourlabelstoresizethemallatonce.)Nowmovethelabelssothattheyareagainnicelyalignedverticallyandcenteredhorizontally(Figure3.15).
WOW! eBook www.wowebook.org
Figure3.15Updatingthelabelframes
BuildandruntheapplicationontheiPhone6ssimulator.NowbuildandruntheapplicationontheiPhone6sPlussimulator.Noticethatthelabelsarenolongercentered–instead,theyappearshiftedslightlytotheleft.
Youhavejustseentwoofthemajorproblemswithabsoluteframes.First,whenthecontentschange(likewhenyouchangedthefontsize),theframesdonotautomaticallyupdate.Second,theviewdoesnotlookequallygoodondifferentsizesofscreens.
Ingeneral,youshouldnotuseabsoluteframesforyourviews.Instead,youshoulduseAutoLayouttoflexiblycomputetheframesforyoubasedonconstraintsthatyouspecifyforeachview.Forexample,whatyoureallywantforWorldTrotterisforthelabelstoremainthesamedistancefromthetopofthescreenandtoremainhorizontallycenteredwithintheirsuperview.Theyshouldalsoupdateifthefontortextofthelabelschange.Thisiswhatyouwillaccomplishinthenextsection.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TheAutoLayoutSystemBeforeyoucanfixthelabelstohavethemlayoutflexibly,youneedtolearnalittletheoryabouttheAutoLayoutsystem.
AsyousawinChapter1,absolutecoordinatesmakeyourlayoutfragilebecausetheyassumethatyouknowthesizeofthescreenaheadoftime.
UsingAutoLayout,youcandescribethelayoutofyourviewsinarelativewaythatenablestheirframestobedeterminedatruntimesothattheframes’definitionscantakeintoaccountthescreensizeofthedevicethattheapplicationisrunningon.
Alignmentrectangleandlayoutattributes
TheAutoLayoutsystemisbasedonthealignmentrectangle.Thisrectangleisdefinedbyseverallayoutattributes(Figure3.16).
Figure3.16Layoutattributesdefininganalignmentrectangleofaview
Width/Height
Thesevaluesdeterminethealignmentrectangle’ssize.Top/Bottom/Left/Right
Thesevaluesdeterminethespacingbetweenthegivenedgeofthealignmentrectangleandthealignmentrectangleofanotherviewinthehierarchy.
CenterX/CenterY
Thesevaluesdeterminethecenterpointofthealignmentrectangle.Baseline
Thisvalueisthesameasthebottomattributeformost,butnotall,views.Forexample,UITextFielddefinesitsbaselineasthebottomofthetextitdisplaysratherthanthebottomofthealignmentrectangle.Thiskeeps“descenders”(letterslike‘g’and‘p’thatdescendbelowthebaseline)frombeingobscuredbya
WOW! eBook www.wowebook.org
viewrightbelowthetextfield.Leading/Trailing
Thesevaluesarelanguage-specificattributes.Ifthedeviceissettoalanguagethatreadslefttoright(e.g.,English),thentheleadingattributeisthesameastheleftattributeandthetrailingattributeisthesameastherightattribute.Ifthelanguagereadsrighttoleft(e.g.,Arabic),thentheleadingattributeisontherightandthetrailingattributeisontheleft.InterfaceBuilderautomaticallyprefersleadingandtrailingoverleftandright,and,ingeneral,youshouldaswell.
Bydefault,everyviewhasanalignmentrectangle,andeveryviewhierarchyusesAutoLayout.
Thealignmentrectangleisverysimilartotheframe.Infact,thesetworectanglesareoftenthesame.Whereastheframeencompassestheentireview,thealignmentrectangleonlyencompassesthecontentthatyouwishtouseforalignmentpurposes.Figure3.17showsanexamplewheretheframeandthealignmentrectanglearedifferent.
Figure3.17Framevs.alignmentrectangle
Youcannotdefineaview’salignmentrectangledirectly.Youdonothaveenoughinformation(likescreensize)todothat.Instead,youprovideasetofconstraints.Takentogether,theseconstraintsenablethesystemtodeterminethelayoutattributes,andthusthealignmentrectangle,foreachviewintheviewhierarchy.
Constraints
Aconstraintdefinesaspecificrelationshipinaviewhierarchythatcanbeusedtodeterminealayoutattributeforoneormoreviews.Forexample,youmightaddaconstraintlike,“Theverticalspacebetweenthesetwoviewsshouldalwaysbe8points,”or,“Theseviewsmustalwayshavethesamewidth.”Aconstraintcanalsobeusedtogiveaviewafixedsize,like,“Thisview’sheightshouldalwaysbe44points.”
Youdonotneedaconstraintforeverylayoutattribute.Somevaluesmaycomedirectlyfromaconstraint;otherswillbecomputedbythevaluesofrelatedlayoutattributes.Forexample,ifaview’sconstraintssetitsleftedgeanditswidth,thentherightedgeisalreadydetermined(leftedge+width=rightedge,always).Asageneralruleofthumb,youneedatleasttwoconstraintsperdimension(horizontalandvertical).
WOW! eBook www.wowebook.org
If,afteralloftheconstraintshavebeenconsidered,thereisstillanambiguousormissingvalueforalayoutattribute,thentherewillbeerrorsandwarningsfromAutoLayoutandyourinterfacewillnotlookasyouexpectonalldevices.Debuggingtheseproblemsisimportant,andyouwillgetsomepracticelaterinthischapter.
Howdoyoucomeupwithconstraints?Let’sseehowusingthelabelsthatyouhavelaidoutonthecanvas.
First,describewhatyouwanttheviewtolooklikeindependentofscreensize.Forexample,youmightsaythatyouwantthetoplabeltobe:
8pointsfromthetopofthescreen
centeredhorizontallyinitssuperview
aswideandastallasitstext
ToturnthisdescriptionintoconstraintsinInterfaceBuilder,itwillhelptounderstandhowtofindaview’snearestneighbor.Thenearestneighboristheclosestsiblingviewinthespecifieddirection(Figure3.18).
Figure3.18Nearestneighbor
Ifaviewdoesnothaveanysiblingsinthespecifieddirection,thenthenearestneighborisitssuperview,alsoknownasitscontainer.
WOW! eBook www.wowebook.org
Nowyoucanspellouttheconstraintsforthelabel:
1. Thelabel’stopedgeshouldbe8pointsawayfromitsnearestneighbor(whichisitscontainer–theviewoftheViewController).
2. Thelabel’scentershouldbethesameasitssuperview’scenter.
3. Thelabel’swidthshouldbeequaltothewidthofitstextrenderedatitsfontsize.
4. Thelabel’sheightshouldbeequaltotheheightofitstextrenderedatitsfontsize.
Ifyouconsiderthefirstandfourthconstraints,youcanseethatthereisnoneedtoexplicitlyconstrainthelabel’sbottomedge.Itwillbedeterminedfromtheconstraintsonthelabel’stopedgeandthelabel’sheight.Similarly,thesecondandthirdconstraintstogetherdeterminethelabel’srightandleftedges.
Nowthatyouhaveaplanforthetoplabel,youcanaddtheseconstraints.ConstraintscanbeaddedusingInterfaceBuilderorincode.ApplerecommendsthatyouaddconstraintsusingInterfaceBuilderwheneverpossible,andthatiswhatyouwilldohere.However,ifyourviewsarecreatedandconfiguredprogrammatically,thenyoucanaddconstraintsincode.InChapter6,youwillpracticethatapproach.
AddingconstraintsinInterfaceBuilder
Let’sgetstartedconstrainingthattoplabel.
Selectthetoplabelonthecanvas.Inthebottomrighthandcornerofthecanvas,findtheAutoLayoutconstraintmenu(Figure3.19).
WOW! eBook www.wowebook.org
Figure3.19UsingtheAutoLayoutconstraintmenu
Clickthe icon(thethirdfromtheleft)torevealthePinmenu.Thismenushowsyouthecurrentsizeandpositionofthelabel.
AtthetopofthePinmenuarefourvaluesthatdescribethelabel’scurrentspacingfromitsnearestneighboronthecanvas.Forthislabel,youareonlyinterestedinthetopvalue.
Toturnthisvalueintoaconstraint,clickthetopredstrutseparatingthevaluefromthesquareinthemiddle.Thestrutwillbecomeasolidredline.
Inthemiddleofthemenu,findthelabel’sWidthandHeight.ThevaluesnexttoWidthandHeightindicatethecurrentcanvasvalues.Toconstrainthelabel’swidthandheighttothecurrentcanvasvalues,checktheboxesnexttoWidthandHeight.ThebuttonatthebottomofthemenureadsAdd3Constraints.Clickthisbutton.
Atthispoint,youhavenotspecifiedenoughconstraintstofullydeterminethealignmentrectangle.InterfaceBuilderwillhelpyoudeterminewhattheproblemis.
InthetoprightcornerofInterfaceBuilder,noticetheyellowwarningsign(Figure3.20).Clickonthisicontorevealtheissue:“Horizontalpositionisambiguousfor“212”.”
WOW! eBook www.wowebook.org
Figure3.20Horizontalambiguity
Youhaveaddedtwoverticalconstraints(atopedgeconstraintandaheightconstraint),butyouhaveonlyaddedonehorizontalconstraint(awidthconstraint).Havingonlyoneconstraintmakesthehorizontalpositionofthelabelambiguous.Youwillfixthisissuebyaddingacenteralignmentconstraintbetweenthelabelanditssuperview.
Withthetoplabelstillselected,clickthe icon(thesecondfromtheleftintheAutoLayoutconstraintsmenu)torevealtheAlignmenu.Ifyouhavemultipleviewsselected,thismenuwillallowyoutoalignattributesamongtheviews.Sinceyouhaveonlyselectedonelabel,theonlyoptionsyouaregivenaretoaligntheviewwithinitscontainer.
IntheAlignmenu,selectHorizontallyinContainer(donotclickAdd1Constraintyet).Onceyouaddthisconstraint,therewillbeenoughconstraintstofullydeterminethealignmentrectangle.Toensurethattheframeofthelabelmatchestheconstraintsspecified,opentheUpdateFramespop-upmenufromtheAlignmenuandselectItemsofNewConstraints.Thiswillrepositionthelabeltomatchtheconstraintsthathavebeenadded.NowclickonAdd1Constrainttoaddthecenteringconstraintandrepositionthelabel.
Thelabel’sconstraintsareallbluenowthatthealignmentrectangleforthelabelisfullyspecified.Additionally,thewarningatthetoprightcornerofInterfaceBuilderisnowgone.
BuildandruntheapplicationontheiPhone6ssimulatorandtheiPhone6sPlussimulator.Thetoplabelwillremaincenteredinbothsimulators.
Intrinsiccontentsize
Althoughthetoplabel’spositionisflexible,itssizeisnot.Thisisbecauseyouhaveaddedexplicitwidthandheightconstraintstothelabel.Ifthetextorfontweretochange,youwouldbeinthesamepositionyouwereinearlier.Thesizeoftheframeisabsolute,sotheframewouldnothugtothecontent.
Thisiswheretheintrinsiccontentsizeofaviewcomesintoplay.Youcanthinkoftheintrinsiccontentsizeasthesizethataview“wants”tonaturallybe.Forlabels,thissizeisthesizeofthetextrenderedatthegivenfont.Forimages,thisisthesizeoftheimageitself.
Aview’sintrinsiccontentsizeactsasimplicitwidthandheightconstraints.Ifyoudonotspecifyconstraintsthatexplicitlydeterminethewidth,theviewwillbeitsintrinsicwidth.Thesamegoesfortheheight.
Withthisknowledge,letthetoplabelhaveaflexiblesizebyremovingtheexplicitwidthandheightconstraints.
InMain.storyboard,selectthewidthconstraintonthelabel.YoucandothisbyWOW! eBook
www.wowebook.org
clickingontheconstraintonthecanvas.Alternatively,inthedocumentoutline,youcanclickonthedisclosuretrianglenexttothe212label,thendisclosethelistofconstraintsforthelabel(Figure3.21).Onceyouhaveselectedthewidthconstraint,presstheDeletekey.Dothesamefortheheightconstraint.
Figure3.21Selectingthewidthconstraint
Noticethattheconstraintsforthelabelarestillblue.Sincethewidthandheightarebeinginferredfromthelabel’sintrinsiccontentsize,therearestillenoughconstraintstodeterminethelabel’salignmentrectangle.
Misplacedviews
Asyouhaveseen,blueconstraintsindicatethatthealignmentrectangleforaviewisfullyspecified.Orangeconstraintsoftenindicateamisplacedview.ThismeansthattheframefortheviewinInterfaceBuilderisdifferentthantheframethatAutoLayouthascomputed.
WOW! eBook www.wowebook.org
Amisplacedviewisveryeasytofix.Thatisgood,becauseitisalsoaverycommonissuethatyouwillencounterwhenworkingwithAutoLayout.
Giveyourtoplabelamisplacedviewsothatyoucanseehowtoresolvethisissue.Resizethetoplabelonthecanvasusingtheresizecontrolsandlookfortheyellowwarninginthetoprightcornerofthecanvas.Clickonthiswarningicontorevealtheproblem:“Framefor“212”willbedifferentatruntime”(Figure3.22).
Figure3.22Misplacedviewwarning
Asthewarningsays,theframeatruntimewillnotbethesameastheframespecifiedonthecanvas.Ifyoulookclosely,youwillseeanorangedottedlinethatindicateswhattheruntimeframewillbe.
Buildandruntheapplication.NoticethatthelabelisstillcentereddespitethenewframethatyougaveitinInterfaceBuilder.Thismightseemgreat–yougettheresultthatyouwant,afterall.ButthedisconnectbetweenwhatyouhavespecifiedinInterfaceBuilderandtheconstraintscomputedbyAutoLayoutwillcauseproblemsdownthelineasyoucontinuetobuildyourviews.Let’sfixthemisplacedview.
Backinthestoryboard,selectthetoplabelonthecanvas.Clickthe icon(theright-mosticon)torevealtheResolveAutoLayoutIssuesmenu.SelectUpdateFramesfromtheSelectedViewssection.Thiswillupdatetheframeofthelabeltomatchtheframethattheconstraintswillcompute.
YouwillgetveryusedtoupdatingtheframesofviewsasyouworkwithAutoLayout.Onewordofcaution:ifyoutrytoupdatetheframesforaviewthatdoesnothaveenoughconstraints,youwillalmostcertainlygetunexpectedresults.Ifthathappens,undothechangeandinspecttheconstraintstoseewhatismissing.
Atthispoint,thetoplabelisingoodshape.Ithasenoughconstraintstodetermineitsalignmentrectangle,andtheviewislayingoutthewayyouwant.
BecomingproficientwithAutoLayouttakesalotofexperience,sointhenextsectionyouWOW! eBook
www.wowebook.org
aregoingtoremovetheconstraintsfromthetoplabelandthenaddconstraintstoallofthelabels.
Addingmoreconstraints
Let’sfleshouttheconstraintsfortherestoftheviews.Beforeyoudothat,youwillfirstremovetheexistingconstraintsfromthetoplabel.
Selectthetoplabelonthecanvas.OpentheResolveAutoLayoutIssuesmenuandselectClearConstraintsfromtheSelectedViewssection(Figure3.23).
Figure3.23Clearingconstraints
Youaregoingtoaddtheconstraintstoalloftheviewsintwosteps.Firstyouwillcenterthetoplabelhorizontallywithinthesuperview.Thenyouwilladdconstraintsthatpinthetopofeachlabeltoitsnearestneighborwhilealigningthecentersofallofthelabels.
Selectthetoplabel.OpentheAlignmenuandchooseHorizontallyinContainerwithaconstantof0.MakesurethatUpdateFrameshasNoneselected;rememberthatyoudonotwanttoupdatetheframeofaviewthatdoesnothaveenoughconstraints,andthisoneconstraintwillcertainlynotprovideenoughinformationtocomputethealignmentrectangle.GoaheadandAdd1Constraint.
Nowselectallfivelabelsonthecanvas.Itcanbeveryconvenienttoaddconstraintstomultipleviewssimultaneously.OpenthePinmenuandmakethefollowchoices:
1. Selectthetopstrutandmakesureithasaconstantof8.
2. FromtheAlignmenu,chooseHorizontalCenters.
3. FromtheUpdateFramesmenu,chooseItemsofNewConstraints.
YourmenushouldmatchFigure3.24.Onceitdoes,clickAdd9Constraints.ThiswilladdtheconstraintstotheviewsandupdatetheirframestoreflecttheAutoLayoutchanges.
WOW! eBook www.wowebook.org
Figure3.24AddingmoreconstraintswiththePinmenu
BuildandruntheapplicationontheiPhone6ssimulator.Theviewswillbecenteredwithintheinterface.NowbuildandruntheapplicationontheiPhone6sPlussimulator.Unlikeearlierinthechapter,allofthelabelsremaincenteredonthelargerinterface.
AutoLayoutisacrucialtechnologyforeveryiOSdeveloper.Ithelpsyoucreateflexiblelayoutsthatworkacrossarangeofdevicesandinterfacesizes.Italsotakesalotofpracticetomaster.YouwillgetalotofexperienceworkingwithAutoLayoutasyouworkthroughthisbook.
NowthattheinterfaceforWorldTrotterisusingAutoLayouttoadapttovariousscreensizes,thereisnoneedforyoutospecifyaniPhonescreensizewhenworkinginthestoryboard.
InMain.storyboard,selecttheViewControllerandopenitsattributesinspector.FindtheSimulatedMetricssectionandchangetheSizetoInferred.Theinterfaceupdatestobethesquareshapethatitwasinitially.Noticethatthelabelsstillremaincenteredinthissquareinterfaceduetotheconstraintsthatyouadded.
Designinginterfacesusingtheinferredsquareshapehelpstoforceyoutothinkaboutdesigningadaptiveinterfacesthatworkwithavarietyofscreensizesinsteadofdesigningforoneparticularscreensize.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:MoreAutoLayoutPracticeRemovealloftheconstraintsfromtheViewControllerinterfaceandthenaddthembackin.Trytodothiswithoutconsultingthebook.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
4TextInputandDelegation
WorldTrotterlooksgood,butsofaritdoesnotdoanything.Inthischapter,youaregoingtoaddaninstanceofUITextFieldtoWorldTrotter.ThetextfieldwillallowtheusertotypeinatemperatureinFahrenheitthatwillthenbeconvertedtoCelsiusanddisplayedontheinterface(Figure4.1).
Figure4.1WorldTrotterwithaUITextField
WOW! eBook www.wowebook.org
TextEditingThefirstthingyouaregoingtodoisaddaUITextFieldtotheinterfaceandsetuptheconstraintsforthattextfield.Thistextfieldwillreplacethetoplabelintheinterfacethatcurrentlyhasthetext“212.”
OpenMain.storyboard.SelectthetoplabelandpresstheDeletekeytoremovethissubview.Theconstraintsforalloftheotherlabelswillturnredsincetheywerealldirectlyorindirectlyanchoredtothattoplabel(Figure4.2).ThatisOK;youwillfixthemshortly.
Figure4.2Ambiguousframesforthelabels
OpentheobjectlibraryanddragaTextFieldtothetopofthecanvaswherethelabelyoudeletedwaspreviouslyplaced.
Nowsetuptheconstraintsforthistextfield.Withthetextfieldselected,opentheAlignmenuandaligntheviewHorizontallyinContainerwithaconstantof0.MakesurethatUpdateFramesissettoNoneandthenAdd1Constraint.
NowopenthePinmenu.Givethetextfieldatopedgeconstraintof8points,abottomedgeconstraintof8points,andawidthof250(Figure4.3).Addthesethreeconstraints.
WOW! eBook www.wowebook.org
Figure4.3TextfieldPinmenu
Finally,selectthetextfieldandthelabelrightbelowit.OpentheAlignmenu,selectHorizontalCenterswithaconstantof0,UpdateFramesforAllFramesinContainer,andthenAdd1Constraint(Figure4.4).
Figure4.4Aligningthetextfield
Next,customizesomeofthetextfieldproperties.OpentheattributesinspectorforthetextWOW! eBook
www.wowebook.org
fieldandmakethefollowingchanges:
Setthetextcolor(fromtheColormenu)toburntorange.
SetthefonttoSystemwithasizeof70.
SettheAlignmenttocentered.
Settheplaceholdertexttobevalue.Thisisthetextthatwillbedisplayedwhentheuserhasnotenteredanytext.
SettheBorderStyletobenone,whichisthefirstelementofthesegmentedcontrolwiththedottedlines.
TheattributesinspectorforyourtextfieldshouldlooklikeFigure4.5.
Figure4.5Textfieldattributesinspector
Sincethetextfield’sfontchanged,theviewsonthecanvasnowaremisplaced.Selectthegraybackgroundview,opentheResolveAutoLayoutIssuesmenu,andselectUpdateFramesfromtheAllViewsinViewControllersection.Thetextfieldandlabelswillberepositionedtomatchtheirconstraints(Figure4.6).
WOW! eBook www.wowebook.org
Figure4.6Updatedframes
Buildandruntheapplication.Taponthetextfieldandentersometext.Ifyoudonotseethekeyboardappear,clickthesimulator’sHardwaremenuandselectKeyboard→ToggleSoftwareKeyboardorusethekeyboardshortcutCommand-K.Bydefault,thesimulatortreatsyourcomputer’skeyboardasaBluetoothkeyboardconnectedtothesimulator.Thisisnotusuallywhatyouwant.Instead,youwantthesimulatortomimicaniOSdevicerunningwithoutanyaccessoriesattachedbyusingtheonscreenkeyboard.
Keyboardattributes
Whenatextfieldistapped,thekeyboardautomaticallyslidesupontothescreen.(Youwillseewhythishappenslaterinthischapter.)Thekeyboard’sappearanceisdeterminedbyasetoftheUITextField’spropertiescalledtheUITextInputTraits.Oneofthesepropertiesisthetypeofkeyboardthatisdisplayed.Forthisapplication,youwanttousethedecimalpad.
Intheattributesinspectorforthetextfield,findtheattributenamedKeyboardTypeandchooseDecimalPad.Inthesamesection,youcanseesomeoftheothertextinputtraitsthatyoucancustomizeforthekeyboard.ChangebothCorrectionandSpellCheckingtoNo(Figure4.7).
WOW! eBook www.wowebook.org
Figure4.7Keyboardtextinputtraits
Buildandruntheapplication.Tappingonthetextfieldwillnowrevealthedecimalpad.
Respondingtotextfieldchanges
ThenextstepoftheprojectwillbetoupdatetheCelsiuslabelwhentextistypedintothetextfield.Youaregoingtoneedtowritesomecodetodothis.Specifically,thiscodewillgointotheviewcontrollersubclassassociatedwiththisinterface.
Currently,thatcorrespondswiththeViewControllerclassdefinedinViewController.swift.However,ViewControllerisnotaverydescriptivenameforaviewcontrollerthatmanagestheconversionbetweenFahrenheitandCelsius.Havingdescriptivetypenamesallowsyoutomoreeasilymaintainyourprojectsastheygrowlarger.Youaregoingtodeletethisfileandreplaceitwithamoredescriptiveclass.
Intheprojectnavigator,findViewController.swiftanddeleteit.ThencreateanewfilebyselectingFile→New→File…(orpressCommand-N).IntheiOSsection,selectSource,chooseSwiftFile,andclickNext.
Onthenextpane,namethisfileConversionViewController.SavethefileintheWorldTrottergroupwithintheWorldTrotterprojectandmakesurethattheWorldTrottertargetischecked,asshowninFigure4.8.ClickCreate,andXcodewillopenConversionViewController.swiftintheeditor.
WOW! eBook www.wowebook.org
Figure4.8SavingaSwiftfile
InConversionViewController.swift,importUIKitanddefineanewviewcontrollernamedConversionViewController.importFoundationimportUIKit
classConversionViewController:UIViewController{
}
NowyouneedtoassociatetheinterfaceyoucreatedinMain.storyboardwiththisnewviewcontrollerthatyoudefined.
OpenMain.storyboardandselecttheViewController,eitherinthedocumentoutlineorbyclickingtheyellowcircleabovetheinterface.
Opentheidentityinspector,whichisthethirdtabintheutilitiesview(Command-Option-3).Atthetop,findtheCustomClasssectionandchangetheClasstoConversionViewController(Figure4.9).YouwilllearnwhatallofthisisdoinginChapter5.
Figure4.9Changingthecustomclass
YousawinChapter1thatabuttoncansendeventstoacontrollerwhenthebuttonis
WOW! eBook www.wowebook.org
tapped.Textfieldsareanothercontrol(bothUIButtonandUITextFieldaresubclassesofUIControl)andcansendaneventwhenthetextchanges.
Togetthisallworking,youwillneedtocreateanoutlettotheCelsiustextlabelandcreateanactionforthetextfieldtocallwhenthetextchanges.
OpenConversionViewController.swiftanddefinethisoutletandaction.Fornow,thelabelwillbeupdatedwithwhatevertexttheusertypesintothetextfield.classConversionViewController:UIViewController{
@IBOutletvarcelsiusLabel:UILabel!
@IBActionfuncfahrenheitFieldEditingChanged(textField:UITextField){celsiusLabel.text=textField.text}}
OpenMain.storyboardtomaketheseconnections.TheoutletwillbeconnectedjustasyoudidinChapter1.Control-dragfromtheConversionViewControllertotheCelsiuslabel(theonethatcurrentlysays“100”)andconnectittothecelsiusLabel.
Connectingtheactionwillbealittledifferentsinceyouwanttheactiontobetriggeredwhentheeditingchanges.
Selectthetextfieldonthecanvasandopenitsconnectionsinspectorfromtheutilitypane(theright-mosttab,orCommand-Option-6).Theconnectionsinspectorisagreatplacetomakeconnectionsandseewhatconnectionshavealreadybeenmade.
YouaregoingtohavechangestothetextfieldtriggertheactionyoudefinedinConversionViewController.Intheconnectionsinspector,locatetheSentEventsandtheEditingChangedevent.ClickanddragfromthecircletotherightofEditingChangedtotheConversionViewControllerandclickthefahrenheitFieldEditingChanged:actioninthepop-upmenu(Figure4.10).
Figure4.10Connectingtheeditingchangedevent
WOW! eBook www.wowebook.org
Buildandruntheapplication.Tapthetextfieldandtypesomenumbers.TheCelsiuslabelwillmimicthetextthatistypedin.Nowdeletethetextinthetextfieldandnoticehowthelabelseemstogoaway.Alabelwithnotexthasanintrinsiccontentwidthandheightof0,sothelabelsbelowitmoveup.Let’sfixthisissue.
InConversionViewController.swift,updatefahrenheitFieldEditingChanged(_:)todisplay“???”ifthetextfieldisempty.@IBActionfuncfahrenheitFieldEditingChanged(textField:UITextField){celsiusLabel.text=textField.text
iflettext=textField.textwhere!text.isEmpty{celsiusLabel.text=text}else{celsiusLabel.text="???"}}
Ifthetextfieldhastextandthattextisnotempty,itwillbesetonthecelsiusLabel.Ifeitherofthoseconditionsarenottrue,thenthecelsiusLabelwillbegiventhestring“???”.
Buildandruntheapplication.Addsometext,deleteit,andthenconfirmthatthecelsiusLabelispopulatedwith“???”oncethetextfieldisempty.
Dismissingthekeyboard
Currently,thereisnowaytodismissthekeyboard.Let’saddthatfunctionality.OnecommonwayofdoingthisisbydetectingwhentheusertapstheReturnkeyandusingthatactiontodismissthekeyboard;youwillusethisapproachinChapter13.SincethedecimalpaddoesnothaveaReturnkey,youwillallowtheusertotaponthebackgroundviewtotriggerthedismissal.
Whenthetextfieldistapped,themethodbecomeFirstResponder()iscalledonthetextfield.Thisisthemethodthat,amongotherthings,causesthekeyboardtoappear.Todismissthekeyboard,youcallthemethodresignFirstResponder()onthetextfield.YouwilllearnmoreaboutthesemethodsinChapter13.
Toaccomplishthis,youwillneedanoutlettothetextfieldandamethodthatistriggeredwhenthebackgroundviewistapped.ThismethodwillcallresignFirstResponder()onthetextfieldoutlet.Let’stakecareofthecodefirst.
OpenConversionViewController.swiftanddeclareanoutletnearthetoptoreferencethetextfield.@IBOutletvarcelsiusLabel:UILabel!@IBOutletvartextField:UITextField!
Nowimplementanactionmethodthatwilldismissthekeyboardwhencalled.
(Inthecodeabove,weincludedexistingcodesothatyoucouldpositionthenewcodecorrectly.Inthecodebelow,wedonotprovidethatcontextbecausethepositionofthenewcodeisnotimportantsolongasitiswithinthecurlybracesforthetypebeing
WOW! eBook www.wowebook.org
implemented–inthiscase,theConversionViewControllerclass.Whenacodeblockincludesallnewcode,wesuggestthatyouputitattheendofthetype’simplementation,justinsidethefinalclosingbrace.InChapter14,youwillseehowtoeasilynavigatewithinanimplementationfilewhenyourfilesgetlongerandmorecomplex.)@IBActionfuncdismissKeyboard(sender:AnyObject){textField.resignFirstResponder()}
Twothingsarestillneeded:thetextFieldoutletneedstobeconnectedinthestoryboardfile,andyouneedawayoftriggeringthedismissKeyboard(_:)methodyouadded.
Totakecareofthefirstitem,openMain.storyboardandselecttheConversionViewController.Control-dragfromtheConversionViewControllertothetextfieldonthecanvasandconnectittothetextFieldoutlet.
Nowyouneedawayoftriggeringthemethodyouimplemented.Youwilluseagesturerecognizertoaccomplishthis.
AgesturerecognizerisasubclassofUIGestureRecognizerthatdetectsaspecifictouchsequenceandcallsanactiononitstargetwhenthatsequenceisdetected.Therearegesturerecognizersthatdetecttaps,swipes,longpresses,andmore.Inthischapter,youwilluseaUITapGestureRecognizertodetectwhentheusertapsthebackgroundview.YouwilllearnmoreaboutgesturerecognizersinChapter18.
InMain.storyboard,findTapGestureRecognizerintheobjectlibrary.DragthisobjectontothebackgroundviewfortheConversionViewController.Youwillseeareferencetothisgesturerecognizerinthescenedock,therowoficonsabovethecanvas.
Control-dragfromthegesturerecognizerinthescenedocktotheConversionViewControllerandconnectittothedismissKeyboard:method(Figure4.11).
Figure4.11Connectingthegesturerecognizeraction
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ImplementingtheTemperatureConversionWiththebasicsoftheinterfacewiredup,let’simplementtheconversionfromFahrenheittoCelsius.YouaregoingtostorethecurrentFahrenheitvalueandcomputetheCelsiusvaluewheneverthetextfieldchanges.
InConversionViewController.swift,addapropertyfortheFahrenheitvalue.Thiswillbeanoptionaldouble(aDouble?).@IBOutletvarcelsiusLabel:UILabel!
varfahrenheitValue:Double?
Thereasonthispropertyisoptionalisbecausetheusermightnothavetypedinanumber,similartotheemptystringissueyoufixedearlier.
NowaddacomputedpropertyfortheCelsiusvalue.ThisvaluewillbecomputedbasedontheFahrenheitvalue.varfahrenheitValue:Double?
varcelsiusValue:Double?{ifletvalue=fahrenheitValue{return(value-32)*(5/9)}else{returnnil}}
FirstyouchecktoseeifthereisaFahrenheitvalue.Ifthereis,youconvertthisvaluetotheequivalentvalueinCelsius.IfthereisnoFahrenheitvalue,thenyoucannotcomputeaCelsiusvalueandsoyoureturnnil.
AnytimetheFahrenheitvaluechanges,theCelsiuslabelneedstobeupdated.
AddamethodtoConversionViewControllerthatupdatesthecelsiusLabel.funcupdateCelsiusLabel(){ifletvalue=celsiusValue{celsiusLabel.text="\(value)"}else{celsiusLabel.text="???"}}
YouwantthismethodtobecalledwhenevertheFahrenheitvaluechanges.Todothis,youwilluseapropertyobserver,whichisachunkofcodethatgetscalledwheneveraproperty’svaluechanges.
Apropertyobserverisdeclaredusingcurlybracesimmediatelyafterthepropertydeclaration.Insidethebraces,youdeclareyourobserverusingeitherwillSetordidSetdependingonwhetheryouwanttobenotifiedimmediatelybeforeorimmediatelyafterthepropertyvaluechanges,respectively.
AddapropertyobservertofahrenheitValuethatgetscalledafterthepropertyvaluechanges.varfahrenheitValue:Double?{didSet{
WOW! eBook www.wowebook.org
updateCelsiusLabel()}}
(Onesmallnote:propertyobserversarenottriggeredwhenthepropertyvalueischangedfromwithinaninitializer.)
Withthatlogicinplace,youcannowupdatetheFahrenheitvaluewhenthetextfieldchanges(which,inturn,willtriggeranupdateoftheCelsiuslabel).
InfahrenheitFieldEditingChanged(_:),deleteyourearliernonconvertingimplementationandinsteadupdatetheFahrenheitvalue.@IBActionfuncfahrenheitFieldEditingChanged(textField:UITextField){iflettext=textField.textwhere!text.isEmpty{celsiusLabel.text=text}else{celsiusLabel.text="???"}
iflettext=textField.text,value=Double(text){fahrenheitValue=value}else{fahrenheitValue=nil}}
Firstyoucheckwhetherthetextfieldhassometext.Ifso,youchecktoseewhetherthattextcanberepresentedbyaDouble.Forexample,“3.14”canberepresentedbyaDouble,butboth“three”and“1.2.3”cannot.Ifbothofthosecheckspass,thentheFahrenheitvalueissettothatDoublevalue.Ifeitherofthosechecksfails,thentheFahrenheitvalueissettonil.
Buildandruntheapplication.TheconversionbetweenFahrenheitandCelsiusworksgreat–solongasyouenteravalidnumber.
Intheremainderofthischapter,youwillupdateWorldTrottertoaddresstwoissues:youwillformattheCelsiusvaluetoshowaprecisionuptoonefractionaldigit,andyouwillnotallowtheusertotypeinmorethanonedecimalseparator.
Thereareacoupleofotherissueswithyourapp,butyouwillfocusonthesetwofornow.Someoftheotherissueswillbepresentedaschallengesattheendofthischapter.Let’sstartwithupdatingtheprecisionoftheCelsiusvalue.
Numberformatters
Youuseanumberformattertocustomizethedisplayofanumber.Thereareotherformattersforformattingdates,energy,mass,length,andmore.
CreateaconstantnumberformatterinConversionViewController.swift.letnumberFormatter:NSNumberFormatter={letnf=NSNumberFormatter()nf.numberStyle=.DecimalStylenf.minimumFractionDigits=0nf.maximumFractionDigits=1returnnf}()
WOW! eBook www.wowebook.org
Hereyouareusingaclosuretoinstantiatethenumberformatter.YouarecreatinganNSNumberFormatterwiththe.Decimalstyleandconfiguringittodisplaynomorethanonefractionaldigit.YouwilllearnmoreaboutthisnewsyntaxfordeclaringpropertiesinChapter15.
NowmodifyupdateCelsiusLabel()tousethisformatter.funcupdateCelsiusLabel(){ifletvalue=celsiusValue{celsiusLabel.text="\(value)"celsiusLabel.text=numberFormatter.stringFromNumber(value)}else{celsiusLabel.text="???"}}
Buildandruntheapplication.PlayaroundwithFahrenheitvaluestoseetheformatteratwork.YoushouldneverseemorethanonefractionaldigitontheCelsiuslabel.
Inthenextsection,youwillupdatetheapplicationtoacceptamaximumofonedecimalseparatorinthetextfield.Todothis,youwilluseacommoniOSdesignpatterncalleddelegation.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DelegationDelegationisanobject-orientedapproachtocallbacks.Acallbackisafunctionthatissuppliedinadvanceofaneventandiscalledeverytimetheeventoccurs.Someobjectsneedtomakeacallbackformorethanoneevent.Forinstance,thetextfieldwantsto“callback”whentheuserenterstextaswellaswhentheuserpressestheReturnkey.
However,thereisnobuilt-inwayfortwo(ormore)callbackfunctionstocoordinateandshareinformation.Thisistheproblemaddressedbydelegation–yousupplyasingledelegatetoreceivealloftheevent-relatedcallbacksforaparticularobject.Thisdelegateobjectcanthenstore,manipulate,acton,andrelaytheinformationfromthecallbacksasitseesfit.
Whentheusertypesintoatextfield,thattextfieldwillaskitsdelegateifitwantstoacceptthechangesthattheuserhasmade.ForWorldTrotter,youwanttodenythatchangeiftheuserattemptstoenteraseconddecimalseparator.ThedelegateforthetextfieldwillbetheinstanceofConversionViewController.
Conformingtoaprotocol
ThefirststepisenablinginstancesoftheConversionViewControllerclasstoperformtheroleofUITextFielddelegatebydeclaringthatConversionViewControllerconformstotheUITextFieldDelegateprotocol.Foreverydelegaterole,thereisacorrespondingprotocolthatdeclaresthemethodsthatanobjectcancallonitsdelegate.
TheUITextFieldDelegateprotocollookslikethis:protocolUITextFieldDelegate:NSObjectProtocol{optionalfunctextFieldShouldBeginEditing(textField:UITextField)->BooloptionalfunctextFieldDidBeginEditing(textField:UITextField)optionalfunctextFieldShouldEndEditing(textField:UITextField)->BooloptionalfunctextFieldDidEndEditing(textField:UITextField)optionalfunctextField(textField:UITextField,shouldChangeCharactersInRangerange:NSRange,replacementStringstring:String)->BooloptionalfunctextFieldShouldClear(textField:UITextField)->BooloptionalfunctextFieldShouldReturn(textField:UITextField)->Bool}
Thisprotocol,likeallprotocols,isdeclaredwithprotocolfollowedbyitsname,UITextFieldDelegate.TheNSObjectProtocolafterthecolonreferstotheNSObjectprotocolandtellsyouthatUITextFieldDelegateinheritsallofthemethodsintheNSObjectprotocol.ThemethodsspecifictoUITextFieldDelegatearedeclarednext.
Youcannotcreateinstancesofaprotocol;itissimplyalistofmethodsandproperties.Instead,implementationislefttoeachtypethatconformstotheprotocol.
Inaclass’sdeclaration,theprotocolsthattheclassconformstoareinacomma-delimitedlistfollowingthesuperclass(ifthereisone).InConversionViewController.swift,declarethatConversionViewControllerconformstotheUITextFieldDelegate
WOW! eBook www.wowebook.org
protocol.classConversionViewController:UIViewController,UITextFieldDelegate{
Protocolsusedfordelegationarecalleddelegateprotocols,andthenamingconventionforadelegateprotocolisthenameofthedelegatingclassplusthewordDelegate.Notallprotocolsaredelegateprotocols,however,andyouwillseeanexampleofadifferentkindofprotocolinChapter15.TheprotocolswehavementionedsofararepartoftheiOSSDK,butyoucanalsowriteyourownprotocols.
Usingadelegate
NowthatyouhavedeclaredConversionViewControllerasconformingtotheUITextFieldDelegateprotocol,youcansetthedelegatepropertyofthetextfield.
OpenMain.storyboardandControl-dragfromthetextfieldtotheConversionViewController.ChoosedelegatefromthepopovertoconnectthedelegatepropertyofthetextfieldtotheConversionViewController.
Next,youaregoingtoimplementtheUITextFieldDelegatemethodthatyouareinterestedin–textField(_:shouldChangeCharactersInRange:replacementString:)
Becausethetextfieldcallsthismethodonitsdelegate,youmustimplementitinConversionViewController.swift.
InConversionViewController.swift,implementtextField(_:shouldChangeCharactersInRange:replacementString:)
toprintthetextfield’scurrenttextaswellasthereplacementstring.Fornow,justreturntruefromthismethod.functextField(textField:UITextField,shouldChangeCharactersInRangerange:NSRange,replacementStringstring:String)->Bool{
print("Currenttext:\(textField.text)")print("Replacementtext:\(string)")
returntrue}
NoticethatXcodewasabletoautocompletethismethodbecauseConversionViewControllerconformstoUITextFieldDelegate.ItisagoodideatodeclareaprotocolbeforeimplementingmethodsfromtheprotocolsothatXcodecanofferthissupport.
Buildandruntheapplication.EnterseveraldigitsinthetextfieldandwatchXcode’sconsole(Figure4.12).Itprintsoutthecurrenttextofthetextfieldaswellasthereplacementstring.
WOW! eBook www.wowebook.org
Figure4.12Printingtotheconsole
Considerthis“currenttext”and“replacementtext”informationinlightofyourgoalofpreventingmultipledecimalseparators.Logically,iftheexistingstringhasadecimalseparatorandthereplacementstringhasadecimalseparator,thechangeshouldberejected.
UpdatetextField(_:shouldChangeCharactersInRange:replacementString:)
tousethislogic.functextField(textField:UITextField,shouldChangeCharactersInRangerange:NSRange,replacementStringstring:String)->Bool{
print("Currenttext:\(textField.text)")print("Replacementtext:\(string)")
returntrue
letexistingTextHasDecimalSeparator=textField.text?.rangeOfString(".")letreplacementTextHasDecimalSeparator=string.rangeOfString(".")
ifexistingTextHasDecimalSeparator!=nil&&replacementTextHasDecimalSeparator!=nil{returnfalse}else{returntrue}}
Buildandruntheapplication.Attempttoentermultipledecimalseparators;theapplicationwillrejecttheseconddecimalseparatorthatyouenter.
Moreonprotocols
IntheUITextFieldDelegateprotocol,therearetwokindsofmethods:methodsthathandleinformationupdatesandmethodsthathandlerequestsforinput.Forexample,the
WOW! eBook www.wowebook.org
textfield’sdelegateimplementsthetextFieldDidBeginEditing(_:)methodifitwantstoknowwhentheusertapsonthetextfield.
Ontheotherhand,textField(_:shouldChangeCharactersInRange:replacementString:)
isarequestforinput.Atextfieldcallsthismethodonitsdelegatetoaskwhetherthereplacementstringshouldbeacceptedorrejected.ThemethodreturnsaBool,whichisthedelegate’sanswer.
Methodsdeclaredinaprotocolcanberequiredoroptional.Bydefault,protocolmethodsarerequired,meaningthataclassconformingtotheprotocolmusthaveanimplementationofthosemethods.Ifaprotocolhasoptionalmethods,theseareprecededbythedirectiveoptional.LookingbackattheUITextFieldDelegateprotocol,youcanseethatallofitsmethodsareoptional.Thisistypicallytrueofdelegateprotocols.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:DisallowAlphabeticCharactersCurrently,theusercanenteralphabeticcharacterseitherbyusingaBluetoothkeyboardorbypastingcopiedtextintothetextfield.Fixthisissue.Hint:youwillwanttousetheNSCharacterSetclass.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
5ViewControllers
AviewcontrollerisaninstanceofasubclassofUIViewController.Aviewcontrollermanagesaviewhierarchy.Itisresponsibleforcreatingviewobjectsthatmakeupthehierarchyandforhandlingeventsassociatedwiththeviewobjectsinitshierarchy.
Sofar,WorldTrotterhasasingleviewcontroller,ConversionViewController.Inthischapter,youwillupdateittousemultipleviewcontrollers.Theuserwillbeabletoswitchbetweentwoviewhierarchies–oneforviewingtheConversionViewControllerandanotherfordisplayingamap(Figure5.1).
Figure5.1ThetwofacesofWorldTrotter
WOW! eBook www.wowebook.org
TheViewofaViewControllerAssubclassesofUIViewController,allviewcontrollersinheritanimportantproperty:varview:UIView!
ThispropertypointstoaUIViewinstancethatistherootoftheviewcontroller’sviewhierarchy.Whentheviewofaviewcontrollerisaddedasasubviewofthewindow,theviewcontroller’sentireviewhierarchyisadded,asshowninFigure5.2.
Figure5.2ObjectdiagramforWorldTrotter
Aviewcontroller’sviewisnotcreateduntilitneedstoappearonthescreen.Thisoptimizationiscalledlazyloading,anditcanconservememoryandimproveperformance.
Therearetwowaysthataviewcontrollercancreateitsviewhierarchy:
programmatically,byoverridingtheUIViewControllermethodloadView()
inInterfaceBuilder,byusinganinterfacefilesuchasastoryboard
YouhavealreadyseenbothofthesemethodsinChapter3.First,youcreatedasampleviewhierarchyprogrammatically,thenyouswitchedtoInterfaceBuildertocreatetheinterfaceforConversionViewControllerusingastoryboardfile.YouwillcontinuetouseInterfaceBuilderinthischapterasyoufurtherexploreviewcontrollers.InChapter6,youwillgetmoreexperiencecreatingprogrammaticviewsusingloadView().
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SettingtheInitialViewControllerAlthoughastoryboardcanhavemanyviewcontrollers,eachstoryboardfilehasexactlyoneinitialviewcontroller.Theinitialviewcontrolleractsasanentrypointintothestoryboard.Youaregoingtoaddandconfigureanotherviewcontrollertothecanvasandsetittobetheinitialviewcontrollerforthestoryboard.
OpenMain.storyboard.Fromtheobjectlibrary,dragaViewControllerontothecanvas(Figure5.3).Ifyouarerunningoutofavailablespace,youcanControl-clickonthebackgroundtochooseadifferentzoomlevel.
Figure5.3Addingaviewcontrollertothecanvas
YouwantthisviewcontrollertodisplayanMKMapView–aclassdesignedtodisplayamap–insteadoftheexistingwhiteUIView.
SelecttheviewoftheViewController–nottheViewControlleritself!–andpressDeletetoremovethisviewfromthecanvas.ThendragaMapKitViewfromtheobjectlibraryontotheviewcontrollertosetitastheviewforthisviewcontroller(Figure5.4).
WOW! eBook www.wowebook.org
Figure5.4Addingamapviewtothecanvas
NowselecttheViewControllerandopenitsattributesinspector.UndertheViewControllersection,checktheboxnexttoIsInitialViewController(Figure5.5).DidyounoticethatthegrayarrowonthecanvasthatwaspointingattheConversionViewControllerisnowpointingtotheViewController?Thearrow,asyouhaveprobablysurmised,indicatestheinitialviewcontroller.Anotherwaytoassigntheinitialviewcontrolleristodragthatarrowfromoneviewcontrollertoanotheronthecanvas.
Figure5.5Settingtheinitialviewcontroller
Thereisaquirkthatwouldcauseproblemsifyouweretobuildandruntheapprightnow.(Tryit,ifyoulike.)MKMapViewisinaframeworkthatisnotcurrentlybeingloadedintotheapplication.Aframeworkisasharedlibraryofcodethatincludesassociatedresources
WOW! eBook www.wowebook.org
suchasinterfacefilesandimages.YoubrieflylearnedaboutframeworksinChapter3,andyouhavebeenusingacoupleofframeworksalready:UIKitandFoundationarebothframeworks.
Sofar,youhavebeenincludingframeworksinyourappbyusingtheimportkeyword,likeso:importUIKit
NowyouneedtoimporttheMapKitframeworkfortheMKMapViewtoload.However,ifyouimporttheMapKitframeworkusingtheimportkeywordwithoutincludinganycodethatusesthatframework,thecompilerwilloptimizeitout–eventhoughyouareusingamapviewinyourstoryboard.
Instead,youneedtomanuallylinktheMapKitframeworktotheapp.
Withtheprojectnavigatoropen,clickontheWorldTrotterprojectatthetopofthelisttoopentheprojectsettings.FindandopentheGeneraltabinthesettings.ScrolldowntothebottomandfindthesectionlabeledLinkedFrameworksandLibraries.Clickonthe+atthebottomandsearchforMapKit.framework.SelectthisframeworkandclickAdd(Figure5.6).
Figure5.6AddingtheMapKitframework
Nowyoucanbuildandruntheapplication.Sinceyouhavechangedtheinitialviewcontroller,themapshowsupinsteadoftheviewoftheConversionViewController.
Asmentionedabove,therecanonlyeverbeoneinitialviewcontrollerassociatedwithagivenstoryboard.YousawthisearlierwhenyousettheViewControllertobetheinitialviewcontroller.Atthatpoint,theConversionViewControllerwasnolongertheinitialviewcontrollerforthisstoryboard.Let’stakealookathowthisrequirementworkswiththerootlevelUIWindowtoaddtheinitialviewcontroller’sviewtothewindowhierarchy.
UIWindowhasarootViewControllerproperty.Whenaviewcontrollerissetasthewindow’srootViewController,thatviewcontroller’sviewgetsaddedtothewindow’sviewhierarchy.Whenthispropertyisset,anyexistingsubviewsonthewindowareremovedandviewcontroller’sviewgetsaddedtothewindowwiththeappropriateAutoLayoutconstraints.
Eachapplicationhasonemaininterface,areferencetoastoryboard.Whentheapplicationlaunches,theinitialviewcontrollerforthemaininterfacegetssetastherootViewControllerofthewindow.
WOW! eBook www.wowebook.org
Themaininterfaceforanapplicationissetintheprojectsettings.StillintheGeneraltaboftheprojectsettings,findtheDeploymentInfosection.HereyouwillseetheMainInterfacesetting(Figure5.7).ThisissettoMain,whichcorrespondstoMain.storyboard.
Figure5.7Anapplication’smaininterface
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UITabBarControllerViewcontrollersbecomemoreinterestingwhentheuserhasawaytoswitchbetweenthem.Throughoutthebook,youwilllearnanumberofwaystopresentviewcontrollers.Inthischapter,youwillcreateaUITabBarControllerthatwillallowtheusertoswapbetweentheConversionViewControllerandtheUIViewControllerdisplayingthemap.
UITabBarControllerkeepsanarrayofviewcontrollers.Italsomaintainsatabbaratthebottomofthescreenwithatabforeachviewcontrollerinitsarray.Tappingonatabresultsinthepresentationoftheviewoftheviewcontrollerassociatedwiththattab.
OpenMain.storyboardandselecttheViewController.FromtheEditormenu,chooseEmbedIn→TabBarController.ThiswilladdtheViewControllertotheviewcontrollersarrayoftheTabBarController.YoucanseethisrepresentedbytheRelationshiparrowpointingfromtheTabBarControllertotheViewController(Figure5.8).Additionally,InterfaceBuilderknowstomaketheTarBarControllertheinitialviewcontrollerforthestoryboard.
Figure5.8Tabbarcontrollerwithoneviewcontroller
Atabbarcontrollerisnotveryusefulwithjustoneviewcontroller.AddtheConversionViewControllertotheTabBarController’sviewcontrollersarray.
Control-dragfromtheTabBarControllertotheConversionViewController.FromtheRelationshipSeguesection,chooseviewcontrollers(Figure5.9).
WOW! eBook www.wowebook.org
Figure5.9Addingaviewcontrollertothetabbarcontroller
Buildandruntheapplication.Taponthetwotabsatthebottomtoswitchbetweenthetwoviewcontrollers.Atthemoment,thetabsjustsay“Item,”whichisnotveryhelpful.Inthenextsection,youwillupdatethetabbaritemstomakethetabsmoredescriptiveandobvious.
UITabBarControllerisitselfasubclassofUIViewController.AUITabBarController’sviewisaUIViewwithtwosubviews:thetabbarandtheviewoftheselectedviewcontroller(Figure5.10).
WOW! eBook www.wowebook.org
Figure5.10UITabBarControllerdiagram
Tabbaritems
Eachtabonthetabbarcandisplayatitleandanimage,andeachviewcontrollermaintainsatabBarItempropertyforthispurpose.WhenaviewcontrolleriscontainedbyaUITabBarController,itstabbaritemappearsinthetabbar.Figure5.11showsanexampleofthisrelationshipiniPhone’sPhoneapplication.
WOW! eBook www.wowebook.org
Figure5.11UITabBarItemexample
First,youneedtoaddafewfilestoyourprojectthatwillbetheimagesforthetabbaritems.Intheprojectnavigator,opentheAssetCatalogbyopeningAssets.xcassets.Then,findConvertIcon.png,[email protected],[email protected],MapIcon.png,[email protected],andMapIcon@3x.pngintheResourcesdirectoryofthefilethatyoudownloadedearlier(http://www.bignerdranch.com/solutions/iOSProgramming5ed.zip).DragthesefilesintotheimagessetlistontheleftsideoftheAssetCatalog(Figure5.12).
WOW! eBook www.wowebook.org
Figure5.12AddingimagestotheAssetCatalog
Thetabbaritempropertiescanbeseteitherprogrammaticallyorinastoryboard.Sinceyourdataisstatic,thestoryboardwillbethebestplacetosetthetabbaritemproperties.
InMain.storyboard,locatetheViewController.Noticethatatabbarwiththetabbariteminitwasaddedtotheinterfacesincetheviewcontrollerwillbepresentedwithinatabbarcontroller.Thisisveryusefulwhenlayingoutyourinterface.
Selectthistabbaritemandopenitsattributesinspector.UndertheBarItemsection,changetheTitleto“Map”andchooseMapIconfromtheImagemenu.Youcanalsochangethetextofthetabbaritembydouble-clickingonthetextonthecanvas.Thetabbarwillbeupdatedtoreflectthesevalues(Figure5.13).
Figure5.13ViewController’stabbaritem
NowfindtheConversionViewControllerandselectitstabbaritem.SettheTitletobe“Convert”andtheImagetobeConvertIcon.
Let’salsochangethefirsttabtobetheConvertViewController.Theorderofthetabsisdeterminedbytheorderoftheviewcontrollerswithinthetabbarcontroller’sviewControllersarray.YoucanchangetheorderinastoryboardbydraggingthetabsatthebottomoftheTabBarController.
WOW! eBook www.wowebook.org
FindtheTabBarControlleronthecanvas.DragtheConverttabtobeinthefirstposition.
Buildandruntheapplication.Notonlyarethetabbaritemsatthebottommoredescriptive,buttheConvertViewControllerisnowthefirstviewcontrollerthatisdisplayed(Figure5.14).
Figure5.14Tabbaritemswithlabelsandicons
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
LoadedandAppearingViewsNowthatyouhavetwoviewcontrollers,thelazyloadingofviewsmentionedearlierbecomesmoreimportant.
Whentheapplicationlaunches,thetabbarcontrollerdefaultstoloadingtheviewofthefirstviewcontrollerinitsarray,whichistheConvertViewController.ThismeansthattheMapViewController’sviewisnotneededandwillonlybeneededwhen(orif)theusertapsthetabtoseeit.
Youcantestthisbehaviorforyourself.Whenaviewcontrollerfinishesloadingitsview,viewDidLoad()iscalled,andyoucanoverridethismethodtomakeitprintamessagetotheconsole,allowingyoutoseethatitwascalled.
Youaregoingtoaddcodetobothviewcontrollers.However,thereisnocodecurrentlyassociatedwiththeviewcontrollerdisplayingthemapbecauseeverythinghasbeenconfiguredusingthestoryboard.Nowthatyouwanttoaddcodetothatviewcontroller,youaregoingtocreateaviewcontrollersubclassandassociateitwiththatinterface.
CreateanewSwiftfile(Command-N)andnameitMapViewController.OpenMapViewController.swiftanddefineaUIViewControllersubclassnamedMapViewController.importFoundationimportUIKit
classMapViewController:UIViewController{
}
NowopenMain.storyboardandselectthemap’sviewcontroller.OpenitsidentityinspectorandchangetheClasstoMapViewController.
NowthatyouhaveassociatedtheMapViewControllerclasswiththeviewcontrolleronthecanvas,youcanaddcodetobothConversionViewControllerandMapViewControllertoprinttotheconsolewhentheirviewDidLoad()methodiscalled.
InConversionViewController.swift,overrideviewDidLoad()toprintastatementtotheconsole.overridefuncviewDidLoad(){//AlwayscallthesuperimplementationofviewDidLoadsuper.viewDidLoad()
print("ConversionViewControllerloadeditsview.")}
InMapViewController.swift,overridethesamemethod.overridefuncviewDidLoad(){//AlwayscallthesuperimplementationofviewDidLoadsuper.viewDidLoad()
print("MapViewControllerloadeditsview.")}
Buildandruntheapplication.Theconsolereportsthat
WOW! eBook www.wowebook.org
ConversionViewControllerloadeditsviewrightaway.TapMapViewController’stab,andtheconsolewillreportthatitsviewisnowloaded.Atthispoint,bothviewshavebeenloaded,soswitchingbetweenthetabsnowwillnolongertriggerviewDidLoad().(Tryitandsee.)
Accessingsubviews
Often,youwillwanttodosomeextrainitializationorconfigurationofsubviewsdefinedinInterfaceBuilderbeforetheyappeartotheuser.Sowherecanyouaccessasubview?Therearetwomainoptions,dependingonwhatyouneedtodo.ThefirstoptionistheviewDidLoad()methodthatyouoverrodetospotlazyloading.Thismethodiscalledaftertheviewcontroller’sinterfacefileisloaded,atwhichpointalloftheviewcontroller’soutletswillreferencetheappropriateobjects.ThesecondoptionisanotherUIViewControllermethod,viewWillAppear(_:).Thismethodiscalledjustbeforeaviewcontroller’sviewisaddedtothewindow.
Whichshouldyouchoose?OverrideviewDidLoad()iftheconfigurationonlyneedstobedoneonceduringtherunoftheapp.OverrideviewWillAppear(_:)ifyouneedtheconfigurationtobedoneeachtimetheviewcontroller’sviewappearsonscreen.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
InteractingwithViewControllersandTheirViewsLet’slookatsomemethodsthatarecalledduringthelifecycleofaviewcontrolleranditsview.Someofthesemethodsyouhavealreadyseen,andsomearenew.
init(coder:)istheinitializerforUIViewControllerinstancescreatedfromastoryboard.
Whenaviewcontrollerinstanceiscreatedfromastoryboard,itsinit(coder:)getscalledonce.YouwilllearnmoreaboutthismethodinChapter15.
init(nibName:bundle:)isthedesignatedinitializerforUIViewController.
Whenaviewcontrollerinstanceiscreatedwithouttheuseofastoryboard,itsinit(nibName:bundle:)getscalledonce.Notethatinsomeapps,youmayendupcreatingseveralinstancesofthesameviewcontrollerclass.Thismethodwillgetcalledonceoneachviewcontrollerasitiscreated.
loadView()isoverriddentocreateaviewcontroller’sviewprogrammatically.
viewDidLoad()isoverriddentoconfigureviewscreatedbyloadinganinterfacefile.Thismethodgetscalledaftertheviewofaviewcontrolleriscreated.
viewWillAppear(_:)isoverriddentoconfigureviewscreatedbyloadinganinterfacefile.
ThismethodandviewDidAppear(_:)getcalledeverytimeyourviewcontrollerismovedonscreen.viewWillDisappear(_:)andviewDidDisappear(_:)getcalledeverytimeyourviewcontrollerismovedoffscreen.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:DarkModeWhenevertheConversionViewControllerisviewed,updateitsbackgroundcolorbasedonthetimeofday.Intheevening,thebackgroundshouldbeadarkcolor.Otherwise,thebackgroundshouldbealightcolor.YouwillneedtooverrideviewWillAppear(_:)toaccomplishthis.(Ifthatisnotenoughexcitementinyourlife,youcanchangethebackgroundcoloreachtimetheviewcontrollerisviewed.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:RetinaDisplayWiththereleaseofiPhone4,AppleintroducedtheRetinadisplayforiPhoneandiPodtouch.TheRetinadisplayhasmuchhigherresolutioncomparedtoearlierdevices.Let’slookatwhatyoushoulddotomakegraphicslooktheirbestonbothdisplays.
Forvectorgraphics,youdonotneedtodoanything;yourcodewillrenderascrisplyasthedeviceallows.However,ifyoudrawusingCoreGraphicsfunctions,thesegraphicswillappeardifferentlyondifferentdevices.InCoreGraphics(alsocalledQuartz),lines,curves,text,etc.aredescribedintermsofpoints.Onanon-Retinadisplay,apointis1x1pixel.OnmostRetinadisplays,apointis2x2pixels(Figure5.15).TheexceptionsareiPhone6PlusandiPhone6sPlus,whichhaveahigherresolutionRetinadisplaywhereapointis3x3pixels.
Figure5.15Renderingtodifferentresolutions
Giventhesedifferences,bitmapimages(likeJPEGorPNGfiles)willbeunattractiveiftheimageisnottailoredtothedevice’sscreentype.Sayyourapplicationincludesasmallimageof25x25pixels.Ifthisimageisdisplayedona2xRetinadisplay,thentheimagemustbestretchedtocoveranareaof50x50pixels.Atthispoint,thesystemdoesatypeofaveragingcalledanti-aliasingtokeeptheimagefromlookingjagged.Theresultisanimagethatisnotjagged–butitisfuzzy(Figure5.16).
WOW! eBook www.wowebook.org
Figure5.16Fuzzinessfromstretchinganimage
Youcouldusealargerfileinstead,buttheaveragingwouldthencauseproblemsintheotherdirectionwhentheimageisshrunkforanon-Retinadisplay.Theonlysolutionistobundletwoimagefileswithyourapplication:oneatapixelresolutionequaltothenumberofpointsonthescreenfornon-RetinadisplaysandonetwicethatsizeinpixelsforRetinadisplays.
Fortunately,youdonothavetowriteanyextracodetohandlewhichimagegetsloadedonwhichdevice.Allyouhavetodoissuffixthehigher-resolutionimagefor2xRetinadisplayswith@2xandthehigher-resolutionimagefor3xRetinadisplayswith@3x.Makesurethe@2x(or@3x)goesbeforethefileextension:[email protected],[email protected],whenyouuseUIImage’sinit(named:)initializertoloadtheimage,thismethodlooksinthebundleandgetstheappropriatefilefortheparticulardevice.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
6ProgrammaticViews
Inthischapter,youwillupdateWorldTrottertocreatetheviewforMapViewControllerprogrammatically(Figure6.1).Indoingso,youwilllearnmoreaboutviewcontrollersandhowtosetupconstraintsandcontrols(suchasUIButtons)programmatically.
Figure6.1WorldTrotterwithprogrammaticviews
Currently,theviewforMapViewControllerisdefinedinthestoryboard.Thefirststep,then,istoremovethisviewfromthestoryboardsoyoucaninsteadcreateitprogrammatically.
InMain.storyboard,selectthemapviewassociatedwithMapViewControllerandpressDelete(Figure6.2).
WOW! eBook www.wowebook.org
Figure6.2Deletingtheview
WOW! eBook www.wowebook.org
CreatingaViewProgrammaticallyYoulearnedinChapter5thattocreateaviewcontroller’sviewprogrammatically,youoverridetheUIViewControllermethodloadView().
OpenMapViewController.swiftandoverrideloadView()tocreateaninstanceofMKMapViewandsetitastheviewoftheviewcontroller.Youwillneedareferencetothemapviewlateron,socreateapropertyforitaswell.importUIKitimportMapKit
classMapViewController:UIViewController{
varmapView:MKMapView!
overridefuncloadView(){//CreateamapviewmapView=MKMapView()
//Setitas*the*viewofthisviewcontrollerview=mapView}
overridefuncviewDidLoad(){super.viewDidLoad()
print("MapViewControllerloadeditsview.")}
}
Whenaviewcontrolleriscreated,itsviewpropertyisnil.Ifaviewcontrollerisaskedforitsviewanditsviewisnil,thentheloadView()methodiscalled.
Buildandruntheapplication.Althoughtheapplicationlooksthesame,themapviewisbeingcreatedprogrammaticallyinsteadofthroughInterfaceBuilder.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ProgrammaticConstraintsInChapter3,youlearnedaboutAutoLayoutconstraintsandhowtoaddthemusingInterfaceBuilder.Inthissection,youwilllearnhowtoaddconstraintstoaninterfaceprogrammatically.
ApplerecommendsthatyoucreateandconstrainyourviewsinInterfaceBuilderwheneverpossible.However,ifyourviewsarecreatedincode,thenyouwillneedtoconstrainthemprogrammatically.TheinterfaceforMapViewControlleriscreatedprogrammatically,soitisagreatcandidateforprogrammaticconstraints.
Tolearnaboutprogrammaticconstraints,youaregoingtoaddaUISegmentedControltoMapViewController’sinterface.Asegmentedcontrolallowstheusertochoosebetweenadiscretesetofoptions,andyouwilluseonetoallowtheusertoswitchbetweenmaptypes:standard,hybrid,andsatellite.
InMapViewController.swift,updateloadView()toaddasegmentedcontroltotheinterface.overridefuncloadView(){//CreateamapviewmapView=MKMapView()
//Setitas*the*viewofthisviewcontrollerview=mapView
letsegmentedControl=UISegmentedControl(items:["Standard","Hybrid","Satellite"])segmentedControl.backgroundColor=UIColor.whiteColor().colorWithAlphaComponent(0.5)segmentedControl.selectedSegmentIndex=0
segmentedControl.translatesAutoresizingMaskIntoConstraints=falseview.addSubview(segmentedControl)}
Thelineofcoderegardingtranslatingconstraintshastodowithanoldersystemforscalinginterfaces–autoresizingmasks.BeforeAutoLayoutwasintroduced,iOSapplicationsusedautoresizingmaskstoallowviewstoscalefordifferent-sizedscreensatruntime.
Everyviewhasanautoresizingmask.Bydefault,iOScreatesconstraintsthatmatchtheautoresizingmaskandaddsthemtotheview.Thesetranslatedconstraintswilloftenconflictwithexplicitconstraintsinthelayoutandcauseanunsatisfiableconstraintsproblem.ThefixistoturnoffthisdefaulttranslationbysettingthepropertytranslatesAutoresizingMaskIntoConstraintstofalse.(ThereismoreaboutAutoLayoutandautoresizingmasksattheendofthischapter.)
Anchors
WhenyouworkwithAutoLayoutprogrammatically,youwilluseanchorstocreateyourconstraints.Anchorsarepropertiesontheviewthatcorrespondtoattributesthatyoumightwanttoconstraintoananchoronanotherview.Forexample,youmightconstraintheleadinganchorofoneviewtotheleadinganchorofanotherview.Thiswouldhavethe
WOW! eBook www.wowebook.org
effectofthetwoviews’leadingedgesbeingaligned.
Let’screatesomeconstraintstodothefollowing.
Thetopanchorofthesegmentedcontrolshouldbeequaltothetopanchorofitssuperview.
Theleadinganchorofthesegmentedcontrolshouldbeequaltotheleadinganchorofitssuperview.
Thetrailinganchorofthesegmentedcontrolshouldbeequaltothetrailinganchorofitssuperview.
InMapViewController.swift,createtheseconstraintsinloadView().letsegmentedControl=UISegmentedControl(items:["Standard","Hybrid","Satellite"])segmentedControl.backgroundColor=UIColor.whiteColor().colorWithAlphaComponent(0.5)segmentedControl.selectedSegmentIndex=0
segmentedControl.translatesAutoresizingMaskIntoConstraints=falseview.addSubview(segmentedControl)
lettopConstraint=segmentedControl.topAnchor.constraintEqualToAnchor(view.topAnchor)letleadingConstraint=segmentedControl.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)lettrailingConstraint=segmentedControl.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
(Notethatduetopagesizerestrictionsweareshowingeachconstant’sdeclarationsplitacrosstwolines.Youshouldentereachdeclarationonasingleline.)
Xcodewillalertyoutoaproblemwitheachlineyouhaveentered.Youwillfixtheminamoment.
AnchorshaveamethodconstraintEqualToAnchor(_:)thatwillcreateaconstraintbetweenthetwoanchors.ThereareafewotherconstraintcreationmethodsonNSLayoutAnchor,includingonethatacceptsaconstantasanargument:funcconstraintEqualToAnchor(anchor:NSLayoutAnchor!,constantc:CGFloat)->NSLayoutConstraint!
Activatingconstraints
YounowhavethreeNSLayoutConstraintinstances.However,theseconstraintswillhavenoeffectonthelayoutuntilyouexplicitlyactivatethembysettingtheiractivepropertiestotrue.ThiswillresolveXcode’scomplaint.
InMapViewController.swift,activatetheconstraintsattheendofloadView().lettopConstraint=segmentedControl.topAnchor.constraintEqualToAnchor(view.topAnchor)letleadingConstraint=segmentedControl.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)lettrailingConstraint=segmentedControl.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
topConstraint.active=trueleadingConstraint.active=truetrailingConstraint.active=true
WOW! eBook www.wowebook.org
Constraintsneedtobeaddedtothemostrecentcommonancestorfortheviewsassociatedwiththeconstraint.Figure6.3showsaviewhierarchyalongwiththecommonancestorfortwoviews.
Figure6.3Commonancestor
Ifaconstraintonlyhasoneview(suchaswhenaddingawidthorheightconstrainttoaview),thenthatviewisconsideredthecommonancestor.
Bysettingtheactivepropertyonaconstrainttotrue,theconstraintwillworkitswayupthehierarchyfortheitemstofindthecommonancestortoaddtheconstraintto.ItwillthencallthemethodaddConstraint(_:)ontheappropriateview.SettingtheactivepropertyispreferabletocallingaddConstraint(_:)orremoveConstraint(_:)yourself.
BuildandruntheapplicationandswitchtotheMapViewController.Thesegmentedcontrolisnowpinnedtothetop,leading,andtrailingedgesofitssuperview(Figure6.4).
WOW! eBook www.wowebook.org
Figure6.4Segmentedcontroladdedtothescreen
Althoughtheconstraintsaredoingtherightthing,theinterfacedoesnotlookgood.Thesegmentedcontrolisunderlappingthestatusbar,anditwouldlookbetterifthesegmentedcontrolwasinsetfromtheleadingandtrailingedgesofthescreen.Let’stacklethestatusbarissuefirst.
Layoutguides
Viewcontrollersexposetwolayoutguidestoassistwithlayoutcontent:thetopLayoutGuideandthebottomLayoutGuide.Thelayoutguidesindicatetheextenttowhichtheviewcontroller’sviewcontentswillbevisible.UsingtopLayoutGuidewillallowyourcontenttonotunderlapthestatusbarornavigationbaratthetopofthescreen.(YouwilllearnaboutnavigationbarsinChapter13.)UsingthebottomLayoutGuidewillallowyourcontenttonotunderlapthetabbaratthebottomofthescreen.
Thelayoutguidesexposethreeanchorsthatyoucanusetoaddconstraints:topAnchor,bottomAnchor,andheightAnchor.Sinceyouwantthesegmentedcontroltobeunderthestatusbar,youwillconstrainthebottomanchorofthetoplayoutguidetothetopanchorofthesegmentedcontrol.
InMapViewController.swift,updatethesegmentedcontrol’sconstraintsinloadView().Makethesegmentedcontrolbe8pointsbelowthetoplayoutguide.lettopConstraint=segmentedControl.topAnchor.constraintEqualToAnchor(view.topAnchor)lettopConstraint=segmentedControl.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor,constant:8)letleadingConstraint=segmentedControl.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)lettrailingConstraint=segmentedControl.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
topConstraint.active=trueleadingConstraint.active=truetrailingConstraint.active=true
Buildandruntheapplication.Thesegmentedcontrolnowappearsbelowthestatusbar.Byusingthelayoutguidesinsteadofahardcodedconstant,theviewswilladaptbasedonthecontexttheyappearin.
WOW! eBook www.wowebook.org
Nowlet’supdatethesegmentedcontrolsothatitisinsetfromtheleadingandtrailingedgesofitssuperview.
Margins
Althoughyoucouldinsetthesegmentedcontrolusingaconstantontheconstraint,itismuchbettertousethemarginsoftheviewcontroller’sview.
EveryviewhasalayoutMarginspropertythatdenotesthedefaultspacingtousewhenlayingoutcontent.ThispropertyisaninstanceofUIEdgeInsets,whichyoucanthinkofasatypeofframe.Whenaddingconstraints,youwillusethelayoutMarginsGuide,whichexposesanchorsthataretiedtotheedgesofthelayoutMargins.
Theprimaryadvantageofusingthemarginsisthatthemarginscanchangedependingonthedevicetype(iPadoriPhone)aswellasthesizeofthedevice(iPhone6soriPhone6sPlus).Usingthemarginswillgiveyoucontentthatlooksgoodonanydevice.
Updatethesegmentedcontrol’sleadingandtrailingconstraintsinloadView()tousethemargins.lettopConstraint=segmentedControl.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor,constant:8)
letleadingConstraint=segmentedControl.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)lettrailingConstraint=segmentedControl.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
letmargins=view.layoutMarginsGuideletleadingConstraint=segmentedControl.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor)lettrailingConstraint=segmentedControl.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
topConstraint.active=trueleadingConstraint.active=truetrailingConstraint.active=true
Buildandruntheapplicationagain.Thesegmentedcontrolisnowinsetfromtheview’smargins(Figure6.5).
Figure6.5Segmentedcontrolwithupdatedconstraints
WOW! eBook www.wowebook.org
Explicitconstraints
Itishelpfultounderstandhowthesemethodsthatyouhaveusedcreateconstraints.NSLayoutConstrainthasthefollowinginitializer:convenienceinit(itemview1:AnyObject,attributeattr1:NSLayoutAttribute,relatedByrelation:NSLayoutRelation,toItemview2:AnyObject?,attributeattr2:NSLayoutAttribute,multiplier:CGFloat,constantc:CGFloat)
Thisinitializercreatesasingleconstraintusingtwolayoutattributesoftwoviewobjects.Themultiplieristhekeytocreatingaconstraintbasedonaratio.Theconstantisafixednumberofpoints,likeyouhaveusedinyourspacingconstraints.
ThelayoutattributesaredefinedasconstantsintheNSLayoutConstraintclass:
NSLayoutAttribute.Left
NSLayoutAttribute.Leading
NSLayoutAttribute.Trailing
NSLayoutAttribute.Top
NSLayoutAttribute.Bottom
NSLayoutAttribute.Width
NSLayoutAttribute.Height
NSLayoutAttribute.CenterX
NSLayoutAttribute.CenterY
NSLayoutAttribute.FirstBaseline
NSLayoutAttribute.LastBaseline
Thereareadditionalattributesthathandlethemarginsassociatedwithaview,suchasNSLayoutAttribute.LeadingMargin.
Let’sconsiderahypotheticalconstraint.Sayyouwantedtheimageviewtobe1.5timesaswideasitistall.Youcouldcreateitwiththefollowingcode.(Donottypethishypotheticalconstraintinyourcode!Itwillconflictwithothersyoualreadyhave.)letaspectConstraint=NSLayoutConstraint(item:imageView,attribute:.Width,relatedBy:.Equal,toItem:imageView,attribute:.Height,multiplier:1.5,constant:0.0)
Tounderstandhowthisinitializerworks,thinkofthisconstraintastheequationshowninFigure6.6.
WOW! eBook www.wowebook.org
Figure6.6NSLayoutConstraintequation
Yourelatealayoutattributeofoneviewtothelayoutattributeofanotherviewusingamultiplierandaconstanttodefineasingleconstraint.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ProgrammaticControlsNowlet’supdatethesegmentedcontroltochangethemaptypewhentheusertapsonasegment.
AUISegmentedControlisasubclassofUIControl.YouhaveworkedwithanotherUIControlsubclassinChapter1,theUIButtonclass.Controlsareresponsibleforcallingmethodsontheirtargetinresponsetosomeevent.
ControleventsareoftypeUIControlEvents.Hereareafewofthecommoncontroleventsthatyouwilluse:
UIControlEvents.TouchDown
Atouchdownonthecontrol.UIControlEvents.TouchUpInside
Atouchdownfollowedbyatouchupwhilestillwithintheboundsofthecontrol.
UIControlEvents.ValueChanged
Atouchthatcausesthevalueofthecontroltochange.
UIControlEvents.EditingChanged
AtouchthatcausesaneditingchangeforaUITextField.
Youused.TouchUpInsidefortheUIButtoninChapter1(itisthedefaulteventwhenyouControl-dragtoconnectactionsinInterfaceBuilder),andyousawthe.EditingChangedeventinChapter4.Forthesegmentedcontrol,youwillusethe.ValueChangedevent.
InMapViewController.swift,updateloadView()toaddatarget-actionpairtothesegmentedcontrolandassociateitwiththe.ValueChangedevent.overridefuncloadView(){
mapView=MKMapView()view=mapView
letsegmentedControl=UISegmentedControl(items:["Standard","Satellite","Hybrid"])segmentedControl.backgroundColor=UIColor.whiteColor().colorWithAlphaComponent(0.5)segmentedControl.selectedSegmentIndex=0
segmentedControl.addTarget(self,action:"mapTypeChanged:",forControlEvents:.ValueChanged)
...
Next,implementtheactionmethodthattheeventwilltrigger.Thismethodwillcheckwhichsegmentwasselectedandupdatethemapaccordingly.funcmapTypeChanged(segControl:UISegmentedControl){switchsegControl.selectedSegmentIndex{case0:mapView.mapType=.Standardcase1:
WOW! eBook www.wowebook.org
mapView.mapType=.Hybridcase2:mapView.mapType=.Satellitedefault:break}}
Buildandruntheapplication.Changetheselectedsegmentandthemapwillupdateaccordingly.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:AnotherTabCreateanewviewcontrollerandaddittothetabbarcontroller.ThisviewcontrollershoulddisplayaWKWebView,whichisaclassusedtodisplaywebcontent.Thewebviewshoulddisplayhttps://www.bignerdranch.comforyoutobookyournextvacation.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:User’sLocationAddabuttontotheMapViewControllerthatdisplaysandzoomsinontheuser’scurrentlocation.Youwillneedtousedelegationtoaccomplishthis.RefertothedocumentationforMKMapViewDelegate.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:DroppingPinsMapviewscandisplaypins,whichareinstancesofMKPinAnnotationView.Addthreepinstothemapview:onewhereyouwereborn,onewhereyouarenow,andoneataninterestinglocationyouhavevisitedinthepast.Addabuttontotheinterfacethatallowsthemaptodisplaythelocationofapin.Subsequenttapsshouldsimplycyclethroughthelistofpins.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:NSAutoresizingMaskLayoutConstraintAswementionedearlier,beforeAutoLayoutiOSapplicationsusedanothersystemformanaginglayout:autoresizingmasks.Eachviewhadanautoresizingmaskthatconstrainedtherelationshipbetweenaviewanditssuperview,butthismaskcouldnotaffectrelationshipsbetweensiblingviews.
Bydefault,viewscreateandaddconstraintsbasedontheirautoresizingmasks.However,thesetranslatedconstraintsoftenconflictwithyourexplicitconstraintsinyourlayout,whichresultsanunsatisfiableconstraintsproblem.
Toseethishappen,commentoutthelineinloadView()thatturnsoffthetranslationofautoresizingmasks.//segmentedControl.translatesAutoresizingMaskIntoConstraints=falseview.addSubview(segmentedControl)
Nowthesegmentedcontrolhasaresizingmaskthatwillbetranslatedintoaconstraint.Buildandruntheapplicationandnavigatetothemapinterface.Youwillnotlikewhatyousee.Theconsolewillreporttheproblemanditssolution.Unabletosimultaneouslysatisfyconstraints.Probablyatleastoneoftheconstraintsinthefollowinglistisoneyoudon'twant.Trythis:(1)lookateachconstraintandtrytofigureoutwhichyoudon'texpect;(2)findthecodethataddedtheunwantedconstraintorconstraintsandfixit.(Note:Ifyou'reseeingNSAutoresizingMaskLayoutConstraintsthatyoudon'tunderstand,refertothedocumentationfortheUIViewpropertytranslatesAutoresizingMaskIntoConstraints)("<NSAutoresizingMaskLayoutConstraint:0x7fb6b8e0ad00h=--&v=--&H:[UISegmentedControl:0x7fb6b9897390(212)]>","<NSLayoutConstraint:0x7fb6b9975350UISegmentedControl:0x7fb6b9897390.leading==UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide'.leading>","<NSLayoutConstraint:0x7fb6b9975460UISegmentedControl:0x7fb6b9897390.trailing==UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide'.trailing>","<NSLayoutConstraint:0x7fb6b8e0b370'UIView-Encapsulated-Layout-Width'H:[MKMapView:0x7fb6b8d237c0(0)]>","<NSLayoutConstraint:0x7fb6b9972020'UIView-leftMargin-guide-constraint'H:|-(0)-[UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide'](LTR)(Names:'|':MKMapView:0x7fb6b8d237c0)>","<NSLayoutConstraint:0x7fb6b9974f50'UIView-rightMargin-guide-constraint'H:[UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide']-(0)-|(LTR)(Names:'|':MKMapView:0x7fb6b8d237c0)>")
Willattempttorecoverbybreakingconstraint<NSLayoutConstraint:0x7fb6b9975460UISegmentedControl:0x7fb6b9897390.trailing==UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide'.trailing>
MakeasymbolicbreakpointatUIViewAlertForUnsatisfiableConstraintstocatchthisinthedebugger.ThemethodsintheUIConstraintBasedLayoutDebuggingcategoryonUIViewlistedin<UIKit/UIView.h>mayalsobehelpful.
Let’sgooverthisoutput.AutoLayoutisreportingthatitis“Unabletosimultaneouslysatisfyconstraints.”Thishappenswhenaviewhierarchyhasconstraintsthatconflict.
Then,theconsolespitsoutsomehandytipsandalistofallconstraintsthatareinvolved.Eachconstraint’sdescriptionisshownintheconsole.Let’slookattheformatofoneoftheseconstraintsmoreclosely.<NSLayoutConstraint:0x7fb6b9975350UISegmentedControl:0x7fb6b9897390.leading
WOW! eBook www.wowebook.org
==UILayoutGuide:0x7fb6b9972640'UIViewLayoutMarginsGuide'.leading>
Thisdescriptionindicatesthattheconstraintlocatedatmemoryaddress0x7fb6b9975350issettingtheleadingedgeoftheUISegmentedControl(at0x7fb6b9897390)equaltotheleadingedgeofthemarginoftheUILayoutGuide(at0x7fb6b9972640).
FiveoftheseconstraintsareinstancesofNSLayoutConstraint.One,however,isaninstanceofNSAutoresizingMaskLayoutConstraint.Thisconstraintistheproductofthetranslationoftheimageview’sautoresizingmask.
Finally,ittellsyouhowitisgoingtosolvetheproblembylistingtheconflictingconstraintthatitwillignore.Unfortunately,itchoosespoorlyandignoresoneofyourexplicitinstancesofNSLayoutConstraintinsteadoftheNSAutoresizingMaskLayoutConstraint.Thisiswhyyourinterfacelookslikeitdoes.
Thenotebeforetheconstraintsarelistedisveryhelpful:theNSAutoresizingMaskLayoutConstraintneedstoberemoved.Betteryet,youcanpreventthisconstraintfrombeingaddedinthefirstplacebyexplicitlydisablingtranslationinloadView()://segmentedControl.translatesAutoresizingMaskIntoConstraints=falseview.addSubview(segmentedControl)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
7Localization
TheappealofiOSisglobal–iOSusersliveinmanydifferentcountriesandspeakmanydifferentlanguages.Youcanensurethatyourapplicationisreadyforaglobalaudiencethroughtheprocessesofinternationalizationandlocalization.
Internationalizationismakingsureyournativeculturalinformation(likelanguage,currency,dateformat,numberformat,etc.)isnothardcodedintoyourapplication.Localizationistheprocessofprovidingtheappropriatedatainyourapplicationbasedontheuser’sLanguageandRegionFormatsettings.YoucanfindthesesettingsintheiOSSettingsapplication(Figure7.1).SelecttheGeneralrowandthentheLanguage&Regionrow.
Figure7.1Languageandregionsettings
Applemakestheseprocessesrelativelysimple.AnapplicationthattakesadvantageofthelocalizationAPIsdoesnotevenneedtoberecompiledtobedistributedinotherlanguagesorregions.(Bytheway,since“internationalization”and“localization”arelongwords,youwillsometimesseethemabbreviatedasi18nandL10n,respectively.)
WOW! eBook www.wowebook.org
Inthischapter,youwillfirstinternationalizetheWorldTrotterapplicationandthenlocalizeitintoSpanish.
WOW! eBook www.wowebook.org
InternationalizationInthisfirstsection,youwillusetheNSNumberFormatterandNSNumberclassestointernationalizetheConversionViewController.
Formatters
InChapter4,youusedaninstanceofNSNumberFormattertosetthetextoftheCelsiuslabelinConversionViewController.NSNumberFormatterhasalocaleproperty,whichissettothedevice’scurrentlocale.WheneveryouuseanNSNumberFormattertocreateanumber,itchecksitslocalepropertyandsetstheformataccordingly.SothetextoftheCelsiuslabelhasbeeninternationalizedfromthestart.
NSLocaleknowshowdifferentregionsdisplaysymbols,dates,anddecimalsandwhethertheyusethemetricsystem.AninstanceofNSLocalerepresentsoneregion’ssettingsforthesevariables.IntheSettingsapplication,theusercanchoosearegion,likeUnitedStatesorUnitedKingdom.(WhydoesAppleuse“region”insteadof“country”?Somecountrieshavemorethanoneregionwithdifferentsettings.ScrollthroughtheoptionsinApplicationRegiontoseeforyourself.)
WhenyoucallthemethodcurrentLocale()onNSLocale,theinstanceofNSLocalethatrepresentstheuser’sregionsettingisreturned.OnceyouhavethatinstanceofNSLocale,youcanaskitquestionslike,“Whatisthecurrencysymbolforthisregion?”or,“Doesthisregionusethemetricsystem?”
Toaskoneofthesequestions,youcallthemethodobjectForKey(_:)ontheNSLocaleinstancewithoneoftheNSLocaleconstantsasanargument.letcurrentLocale=NSLocale.currentLocale()letisMetric=currentLocale.objectForKey(NSLocaleUsesMetricSystem)?.boolValueletcurrencySymbol=currentLocale.objectForKey(NSLocaleCurrencyCode)
EventhoughtheCelsiuslabelisalreadyinternationalized,thereisstillaproblemwithit.ChangethesystemregiontoSpaintosee.Selecttheactiveschemepop-upandselectEditScheme(Figure7.2).
Figure7.2Editscheme
MakesurethatRunisselectedonthelefthandsideandthenselecttheOptionstabatthetop.
WOW! eBook www.wowebook.org
FortheApplicationRegionpop-up,selectEuropeandthenSpain(Figure7.3).Finally,Closetheactiveschemewindow.
Figure7.3Selectingadifferentregion
Buildandruntheapplication.OntheConversionViewController,tapthetextfieldandmakesurethesoftwarekeyboardisvisible.Youmayalreadynoticeonedifference:inSpain,thedecimalseparatorisacommainsteadofaperiod(andthethousandsseparatorisaperiodinsteadofacomma).Thenumber123,456.789intheUnitedStateswouldbewritten123.456,789inSpain.
Attempttotypeinmultipledecimalseparators(thecomma)andnoticethattheapplicationhappilyallowsit.Whoops!Yourcodefordisallowingmultipledecimalseparatorschecksforaperiodinsteadofusingalocale-specificdecimalseparator.Let’sfixthat.
OpenConversionViewController.swiftandupdatetextfield(_:shouldChangeCharactersInRange:replacementString:)
tousethelocale-specificdecimalseparator.functextField(textField:UITextField,shouldChangeCharactersInRangerange:NSRange,replacementStringstring:String)->Bool{
letexistingTextHasDecimalSeparator=textField.text?.rangeOfString(".")letreplacementTextHasDecimalSeparator=string.rangeOfString(".")
letcurrentLocale=NSLocale.currentLocale()letdecimalSeparator=currentLocale.objectForKey(NSLocaleDecimalSeparator)as!String
letexistingTextHasDecimalSeparator=textField.text?.rangeOfString(decimalSeparator)letreplacementTextHasDecimalSeparator=string.rangeOfString(decimalSeparator)
WOW! eBook www.wowebook.org
ifexistingTextHasDecimalSeparator!=nil&&replacementTextHasDecimalSeparator!=nil{returnfalse}else{returntrue}}
Buildandruntheapplication.Theapplicationnolongerallowsyoutotypeinmultipledecimalseparators,anditdoesthisinawaythatisindependentoftheuser’sregionchoice.
Butthereisstillaproblem.Ifyoutypeinanumberwithadecimalseparatorthatisnotaperiod,theconversiontoCelsiusisnothappening–theCelsiuslabeldisplays“???”.Whatisgoingonhere?InfahrenheitFieldEditingChanged(_:),youareusinganinitializerfortheDoubletypethattakesinastringasitsargument.Thisinitializerdoesnotknowhowtohandleastringthatusessomethingotherthanaperiodforitsdecimalseparator.
Let’sfixthiscodeusingtheNSNumberFormatterclass.InConversionViewController.swift,updatefahrenheitFieldEditingChanged(_:)toconvertthetextfield’sstringintoanumberinalocale-independentway.@IBActionfuncfahrenheitFieldEditingChanged(textField:UITextField){
iflettext=textField.text,value=Double(text){fahrenheitValue=value}else{fahrenheitValue=nil}
iflettext=textField.text,number=numberFormatter.numberFromString(text){fahrenheitValue=number.doubleValue}else{fahrenheitValue=nil}}
Hereyouareusingthenumberformatter’sinstancemethodnumberFromString(_:)toconvertthestringintoanumber.Sincethenumberformatterisawareofthelocale,itisabletoconvertthestringintoanumber.Ifthestringcontainsavalidnumber,themethodreturnsaninstanceofNSNumber.NSNumberisaclassthatcanrepresentavarietyofnumbertypes,includingInt,Float,Double,andmore.YoucanaskaninstanceofNSNumberforitsvaluerepresentedasoneofthosevalues.YouaredoingthatheretogetthedoubleValueofthenumber.
Buildandruntheapplication.Nowthatyouareconvertingthestringinalocale-independentway,thetextfield’svalueisproperlyconvertedtoitsCelsiusvalue.
Baseinternationalization
Wheninternationalizing,youasktheinstanceofNSLocalequestions.ButtheNSLocaleonlyhasafewregion-specificvariables.Thisiswherelocalization–creatingapplication-specificsubstitutionsfordifferentregionandlanguagesettings–comesinto
WOW! eBook www.wowebook.org
play.Localizationusuallyinvolveseithergeneratingmultiplecopiesofresources(likeimages,sounds,andinterfacefiles)fordifferentregionsandlanguagesorcreatingandaccessingstringstables(whichyouwillseelaterinthechapter)totranslatetextintodifferentlanguages.
Beforeyougothroughtheprocessoflocalizingresources,youmustunderstandhowaniOSapplicationhandleslocalizedresources.
WhenyoubuildatargetinXcode,anapplicationbundleiscreated.AlloftheresourcesthatyouaddedtothetargetinXcodearecopiedintothisbundle,alongwiththeexecutableitself.ThisbundleisrepresentedatruntimebyaninstanceofNSBundleknownasthemainbundle.ManyclassesworkwiththeNSBundletoloadresources.
Localizingaresourceputsanothercopyoftheresourceintheapplicationbundle.Theseresourcesareorganizedintolanguage-specificdirectories,knownaslprojdirectories.Eachoneofthesedirectoriesisthenameofthelocalizationsuffixedwithlproj.Forexample,theAmericanEnglishlocalizationisen_US,whereenistheEnglishlanguagecodeandUSistheUnitedStatesofAmericaregioncode,sothedirectoryforAmericanEnglishresourcesisen_US.lproj.(Theregioncanbeomittedifyoudonotneedtomakeregionaldistinctionsinyourresourcefiles.)Theselanguageandregioncodesarestandardonallplatforms,notjustiOS.
Whenabundleisaskedforthepathofaresourcefile,itfirstlooksattherootlevelofthebundleforafileofthatname.Ifitdoesnotfindone,itlooksatthelocaleandlanguagesettingsofthedevice,findstheappropriatelprojdirectory,andlooksforthefilethere.Thus,justbylocalizingresourcefiles,yourapplicationwillautomaticallyloadthecorrectfile.
Oneoptionforlocalizingresourcefilesistocreateseparatestoryboardfilesandmanuallyediteachstringineachfile.However,thisapproachdoesnotscalewellifyouareplanningmultiplelocalizations.Whathappenswhenyouaddanewlabelorbuttontoyourlocalizedstoryboard?Youhavetoaddthisviewtothestoryboardforeverylanguage.Notfun.
Tosimplifytheprocessoflocalizinginterfacefiles,Xcodehasafeaturecalledbaseinternationalization.BaseinternationalizationcreatestheBase.lprojdirectory,whichcontainsthemaininterfacefiles.LocalizingindividualinterfacefilescanthenbedonebycreatingjusttheLocalizable.stringsfiles.Itisstillpossibletocreatethefullinterfacefiles,incaselocalizationcannotbedonebychangingstringsalone.However,withthehelpofAutoLayout,stringsreplacementissufficientformostlocalizationneeds.Inthenextsection,youwilluseAutoLayouttoprepareyourlayoutforlocalization.
Preparingforlocalization
OpenMain.storyboard,andthenshowtheassistanteditoreitherbyclickingView→AssistantEditor→ShowAssistantEditororwiththekeyboardshortcutOption-Command-Return.Fromthejumpbardropdown,selectPreview(Figure7.4).Thepreviewassistantallowsyoutoeasilyseehowyourinterfacewilllookacrossscreensizesandorientationsaswellasbetweendifferentlocalizedlanguages.
WOW! eBook www.wowebook.org
Figure7.4Openingthepreviewassistant
Inthestoryboard,selecttheConversionViewControllertoseeitspreview(Figure7.5).
WOW! eBook www.wowebook.org
Figure7.5Previewassistant
Noticethecontrolsinthelowercornersofthepreviewassistant.The+buttonontheleftsideallowsyoutoaddadditionalscreensizestothepreviewcanvas.Thisallowsyoutoeasilyseehowchangestoyourinterfacepropagateacrossscreensizesandorientationssimultaneously.Thebuttonontherightsideallowsyoutoselectalanguagetopreviewthisinterfacein.
(Ifyourpreviewisforaconfigurationotherthan“iPhone4.7-inch,”usethe+buttontoaddthisconfiguration.ThenclickonwhateverpreviewopenedbydefaultandpresstheDeletekeytoremoveit.)
Youhavenotlocalizedtheapplicationintoanotherlanguageyet,butXcodesuppliesapseudolanguageforyoutouse.Pseudolanguageshelpyouinternationalizeyourapplicationsbeforereceivingtranslationsforallofyourstringsandassets.Thebuilt-in
WOW! eBook www.wowebook.org
pseudolanguage,Double-LengthPseudolanguage,mimicslanguagesthataremoreverbosebyrepeatingwhatevertextstringisinthetextelement.So,forexample,“isreally”becomes“isreallyisreally.”
SelecttheLanguagepop-upthatsaysEnglishandchooseDouble-LengthPseudolanguage.Thelabelsallhavetheirtextdoubled(Figure7.6).
Figure7.6Doubledtextstrings
Thedouble-lengthpseudolanguagerevealsaproblemimmediately:thelabelsgooffboththeleftandrightedgesofthescreenandyouareunabletoreadtheentirestrings.Thefixistoconstrainallofthelabelssothattheirleadingandtrailingedgesstaywithinthemarginsoftheirsuperview.Thenyouwillneedtochangethelinecountforthelabelsto0,whichtellsthelabelsthattheirtextshouldwraptomultiplelinesifneeded.Youaregoingtostartbyfixingonelabel,thenrepeatthestepsfortherestofthelabels.
WOW! eBook www.wowebook.org
Inthecanvas,selectthe“degreesFahrenheit”label.Youaregoingtoaddconstraintstothislabelinanewway.Control-dragfromthelabeltotheleftsideofthesuperview.Whenyoudo,acontext-sensitivepop-upwillappeargivingyoutheconstraintsthatmakesenseforthisdirection(Figure7.7).SelectLeadingSpacetoContainerMarginfromthelist.
Figure7.7CreatingconstraintsbyControl-dragging
Thedirectionthatyoudraginfluenceswhichpossibleconstraintsaredisplayed.Ahorizontaldragwillshowhorizontalconstraints,andaverticaldragwillshowverticalconstraints.Adiagonaldragwillshowbothhorizontalandverticalconstraints,whichisusefulforsettingupmanyconstraintssimultaneously.
NowControl-dragfromthe“degreesFahrenheit”labeltotherightsideofthesuperviewandselectTrailingSpacetoContainerMargin.
Ontheirown,theseconstraintsarenotverygood.Theymaintaintheexistingfixeddistancebetweentheleadingandtrailingedgesofthelabel,asyoucanseeinthepreviewassistant(Figure7.8).
WOW! eBook www.wowebook.org
Figure7.8Previewassistantwithnewconstraints
Whatyoureallywantisforthedistancebetweenthelabelandthemarginstobegreaterthanorequalto0.Youcandothiswithinequalityconstraints.
SelecttheleadingconstraintbyclickingontheI-bartotheleftofthelabel.OpenitsattributesinspectorandchangetheRelationtoGreaterThanorEqualandtheConstantto0(Figure7.9).
WOW! eBook www.wowebook.org
Figure7.9Inequalityconstraint
Dothesameforthetrailingconstraint.Takealookatthepreviewassistant;theinterfaceislookingbetter,butthelabelisnowbeingtruncated.
Selectthelabelandopenitsattributesinspector.ChangetheLinescountto0.Nowtakealookatthepreviewassistant;thelabelisnolongerbeingtruncatedandinsteadthetextflowstoasecondline.Sincetheotherlabelsareallrelatedtothelabelabovethem,theyhaveautomaticallybeenmoveddown.
Repeatthestepsabovefortheotherlabels.Youwillneedto:
Addaleadingandtrailingconstrainttoeachlabel.
Settheconstraints’relationtoGreaterThanorEqualandtheconstantto0.(Ashortcutforeditingaconstraintistodouble-clickonit.)
Changethelabel’slinecountto0.
Onceyouaredone,thepreviewassistantwiththedouble-lengthpseudolanguagewilllooklikeFigure7.10.
WOW! eBook www.wowebook.org
Figure7.10Previewassistantwithfinalconstraints
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
LocalizationWorldTrotterisnowinternationalized–itsinterfaceisabletoadapttovariouslanguagesandregions.Nowitistimetolocalizetheapp–thatis,toupdatethestringsandresourcesintheapplicationforanewlanguage.Inthissection,youaregoingtolocalizetheinterfaceofWorldTrotter:theMain.storyboardfile.YouwillcreateEnglishandSpanishlocalizations,whichwillcreatetwolprojdirectoriesinadditiontothebaseone.
Startbylocalizingastoryboardfile.SelectMain.storyboardintheprojectnavigator.
Openthefileinspectorbyclickingthe tabintheinspectorselectororbyusingthekeyboardshortcutOption-Command-1.FindthesectioninthisinspectornamedLocalization.ChecktheEnglishboxandmakesurethatthedropdownsaysLocalizableStrings(Figure7.11).Thiswillcreateastringstablethatyouwilluselatertolocalizetheapplication.
Figure7.11LocalizingintoEnglish
Next,intheprojectnavigator,selecttheWorldTrotterprojectatthetop.ThenselectWorldTrotterundertheProjectsectioninthesidelist,andmakesuretheInfotabisopen.(Ifyoucannotseethesidelist,youcanopenitusingtheShowprojectsandtargetslistbuttonintheupperlefthandcorner(Figure7.12).)
Figure7.12Showingtheprojectsettings
Clickthe+buttonunderLocalizationsandselectSpanish(es).Inthedialog,youcanunchecktheLaunchScreen.storyboardfile;keeptheMain.storyboardfilechecked.
WOW! eBook www.wowebook.org
MakesurethatthereferencelanguageisBaseandthefiletypeisLocalizableStrings.ClickFinish.Thiscreatesanes.lprojfolderandgeneratestheMain.stringsfileinitthatcontainsallthestringsfromthebaseinterfacefile.TheLocalizationsconfigurationshouldlooklikeFigure7.13.
Figure7.13Localizations
Lookintheprojectnavigator.ClickthedisclosurebuttonnexttoMain.storyboard(Figure7.14).XcodemovedtheMain.storyboardfiletotheBase.lprojdirectoryandcreatedtheMain.stringsfileinthees.lprojdirectory.
Figure7.14Localizedstoryboardintheprojectnavigator
Intheprojectnavigator,clicktheSpanishversionofMain.strings.Whenthisfileopens,thetextisnotinSpanish.Youhavetotranslatelocalizedfilesyourself;Xcodeisnotthatsmart.
WOW! eBook www.wowebook.org
Editthisfileaccordingtothefollowingtext.Thenumbersandordermaybedifferentinyourfile,butyoucanusethetextandtitlefieldsinthecommentstomatchupthetranslations./*Class="UITabBarItem";title="Map";ObjectID="6xh-o5-yRt";*/"6xh-o5-yRt.title"="Map""Mapa";
/*Class="UILabel";text="degreescelsius";ObjectID="7la-u7-mx6";*/"7la-u7-mx6.text"="degreesCelsius""gradosCelsius";
/*Class="UILabel";text="degreesfahrenheit";ObjectID="Dic-rs-P0S";*/"Dic-rs-P0S.text"="degreesFahrenheit""gradosFahrenheit";
/*Class="UILabel";text="100";ObjectID="Eso-Wf-EyH";*/"Eso-Wf-EyH.text"="100";
/*Class="UITextField";placeholder="value";ObjectID="On4-jV-YlY";*/"On4-jV-YlY.placeholder"="value""valor";
/*Class="UILabel";text="isreally";ObjectID="wtF-xR-gbZ";*/"wtF-xR-gbZ.text"="isreally""esrealmente";
/*Class="UITabBarItem";title="Convert";ObjectID="zLY-50-CeX";*/"zLY-50-CeX.title"="Convert""Convertir";
Nowthatyouhavefinishedlocalizingthisstoryboardfile,let’stestitout.First,thereisalittleXcodeglitchtobeawareof:sometimesXcodeignoresaresourcefile’schangeswhenyoubuildanapplication.Toensurethatyourapplicationisbeingbuiltfromscratch,firstdeleteitfromyourdeviceorsimulator.(Pressandholditsiconinthelauncher.Whenitstartstowiggle,tapthedeletebadge.)RelaunchXcode.(Yes,exitandstartitagain.)Then,chooseCleanfromtheProductmenu.Finally,tobeabsolutelysure,pressandholdtheOptionkeywhileopeningtheProductmenuandchooseCleanBuildFolder….Thiswillforcetheapplicationtobeentirelyrecompiled,rebundled,andreinstalled.
Opentheactiveschemepop-upandselectEditScheme.MakesureRunisselectedonthelefthandsideandopentheOptionstab.OpentheApplicationLanguagepop-upandselectSpanish.Finally,confirmthatSpainisstillselectedfromtheApplicationRegionpop-up.Closethewindow.
Buildandruntheapplication.MakesureyouareviewingtheConversionViewController,andyouwillseetheinterfaceinSpanish.Becauseyousettheconstraintsonthelabelstoaccommodatedifferentlengthsoftext,theyresizethemselvesappropriately(Figure7.15).
WOW! eBook www.wowebook.org
Figure7.15SpanishConversionViewController
NSLocalizedStringandstringstables
Inmanyplacesinyourapplications,youcreateStringinstancesdynamicallyordisplaystringliteralstotheuser.Todisplaytranslatedversionsofthesestrings,youmustcreateastringstable.Astringstableisafilecontainingalistofkey-valuepairsforallofthestringsthatyourapplicationusesandtheirassociatedtranslations.Itisaresourcefilethatyouaddtoyourapplication,butyoudonotneedtodoalotofworktogetdatafromit.
Youmightuseastringinyourcodelikethis:letgreeting="Hello!"
Tointernationalizethestringinyourcode,youreplaceliteralstringswiththefunctionNSLocalizedString(_:comment:).letgreeting=NSLocalizedString("Hello!",comment:"Thegreetingfortheuser")
Thisfunctiontakestwoarguments:akeyandacommentthatdescribesthestring’suse.Thekeyisthelookupvalueinastringstable.Atruntime,NSLocalizedString(_:comment:)willlookthroughthestringstablesbundledwithyourapplicationforatablethatmatchestheuser’slanguagesettings.Then,inthattable,thefunctiongetsthetranslatedstringthatmatchesthekey.
NowyouaregoingtointernationalizethestringsthattheMapViewControllerdisplaysinitssegmentedcontrol.InMapViewController.swift,locatetheloadView()methodandupdatetheinitializerforthesegmentedcontroltouse
WOW! eBook www.wowebook.org
localizedstrings.overridefuncloadView(){
mapView=MKMapView()view=mapView
letsegmentedControl=UISegmentedControl(items:["Standard","Satellite","Hybrid"])
letstandardString=NSLocalizedString("Standard",comment:"Standardmapview")letsatelliteString=NSLocalizedString("Satellite",comment:"Satellitemapview")lethybridString=NSLocalizedString("Hybrid",comment:"Hybridmapview")letsegmentedControl=UISegmentedControl(items:[standardString,satelliteString,hybridString])
OnceyouhavefilesthathavebeeninternationalizedwiththeNSLocalizedString(_:comment:)function,youcangeneratestringstableswithacommand-lineapplication.
OpentheTerminalapp.ThisisaUnixterminal;itisusedtoruncommand-linetools.YouwanttonavigatetothelocationofMapViewController.swift.IfyouhaveneverusedtheTerminalappbefore,hereisahandytrick.InTerminal,typethefollowing:cd
followedbyaspace.(DonotpressEnteryet.)
Next,openFinderandlocateMapViewController.swiftandthefolderthatcontainsit.DragtheiconofthatfolderontotheTerminalwindow.Terminalwillfilloutthepathforyou.Itwilllooksomethinglikethis:cd/Users/cbkeur/iOSDevelopment/WorldTrotter/WorldTrotter/
PressReturn.ThecurrentworkingdirectoryofTerminalisnowthisdirectory.
UsetheterminalcommandlstoprintoutthecontentsoftheworkingdirectoryandconfirmthatMapViewController.swiftisinthatlist.
Togeneratethestringstable,enterthefollowingintoTerminalandpressEnter:genstringsMapViewController.swift
Theresultingfile,Localizable.strings,containsthestringsfromMapViewController.DragthisnewfilefromFinderintotheprojectnavigator(orusetheFile→AddFilesto“WorldTrotter”…menuitem).Whentheapplicationiscompiled,thisresourcewillbecopiedintothemainbundle.
OpenLocalizable.strings.Thefileshouldlooksomethinglikethis:/*Hybridmapview*/"Hybrid"="Hybrid";
/*Satellitemapview*/"Satellite"="Satellite";
/*Standardmapview*/"Standard"="Standard";
NoticethatthecommentaboveyourstringisthesecondargumentyousuppliedtotheNSLocalizedStringfunction.Eventhoughthefunctiondoesnotrequirethecommentargument,includingitwillmakeyourlocalizinglifeeasier.
WOW! eBook www.wowebook.org
NowthatyouhavecreatedLocalizable.strings,youneedtolocalizeitinXcode.OpenitsfileinspectorandclicktheLocalize…buttonintheutilityarea.MakesureBaseisselectedfromthepop-upandclickLocalize.AddtheSpanishandEnglishlocalizationbycheckingtheboxnexttoeachlanguage.
Intheprojectnavigator,clickonthedisclosuretrianglethatnowappearsnexttoLocalizable.strings.OpentheSpanishversion.ThestringonthelefthandsideisthekeythatispassedtotheNSLocalizedString(_:comment:)function,andthestringontherighthandsideiswhatisreturned.ChangethetextontherighthandsidetotheSpanishtranslationshownbelow.(Totypeanaccentedcharacter,suchas“é,”pressandholdtheappropriatecharacteronyourkeyboardandthenpresstheappropriatenumberfromthepop-up.)/*Hybridmapview*/"Hybrid"="Hybrid""Híbrido";
/*Satellitemapview*/"Satellite"="Satellite""Satélite";
/*Standardmapview*/"Standard"="Standard""Estándar";
Buildandruntheapplicationagain.Nowallthesestrings,includingthetitlesinthesegmentedcontrol,willappearinSpanish(Figure7.16).Iftheydonot,youmightneedtodeletetheapplication,cleanyourproject,andrebuild.(Orcheckyourschemelanguagesetting.)
Figure7.16SpanishMapViewController
Internationalizationandlocalizationareveryimportantforyourapptoreachthelargestaudience.Additionally,asyousawearlyinthischapter,yourappmightnotworkforsomeusersifyouhavenotproperlyinternationalizedit.Youwillinternationalize(butnotlocalize)yourprojectsintherestofthisbook.
Overthepastfivechapters,youhavebuiltaratherimpressiveapplicationthatallowsthe
WOW! eBook www.wowebook.org
usertoconvertbetweenCelsiusandFahrenheitaswellasdisplayamapinafewdifferentways.NotonlydoesthisapplicationscalewellonalliPhonescreensizes,butitisalsolocalizedintoanotherlanguage.Congratulations!
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:AnotherLocalizationPracticemakesperfect.LocalizeWorldTrotterforanotherlanguage.Useatranslationwebsiteifyouneedhelpwiththelanguage.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:NSBundle’sRoleinInternationalizationTherealworkoftakingadvantageoflocalizationsisdoneforyoubytheclassNSBundle.Abundlerepresentsalocationonthefilesystemthatgroupsthecompiledcodeandresourcestogether.The“mainbundle”isanothernamefortheapplicationbundle,whichcontainsalloftheresourcesandtheexecutablefortheapplication.YouwilllearnmoreabouttheapplicationbundleinChapter15.
Whenanapplicationisbuilt,allofthelprojdirectoriesarecopiedintothemainbundle.Figure7.17showsthemainbundleforWorldTrotterwithsomeadditionalimagesaddedtotheproject.
Figure7.17Applicationbundle
NSBundleknowshowtosearchthroughlocalizationdirectoriesforeverytypeofresourceusingtheinstancemethodURLForResource(_:withExtension:).Whenyouwantapathtoaresourcebundledwithyourapplication,youcallthismethodonthemainbundle.HereisanexampleusingtheresourcefileBoo.png:letpath=NSBundle.mainBundle().URLForResource:("Boo"withExtension:"png")
Whenattemptingtolocatetheresource,thebundlefirstcheckstoseeiftheresourceexistsatthetopleveloftheapplicationbundle.Ifso,itreturnsthefullURLtothatfile.Ifnot,
WOW! eBook www.wowebook.org
thebundlegetsthedevice’slanguageandregionsettingsandlooksintheappropriatelprojdirectoriestoconstructtheURL.Ifitstilldoesnotfindit,itlookswithintheBase.lprojdirectory.Finally,ifnofileisfound,itreturnsnil.
IntheapplicationbundleshowninFigure7.17,iftheuser’slanguageissettoSpanish,NSBundlewillfindBoo.pngatthetoplevel,Tom.pngines.lproj,andHat.pnginBase.lproj.
Whenyouaddanewlocalizationtoyourproject,Xcodedoesnotautomaticallyremovetheresourcesfromthetop-leveldirectory.Thisiswhyyoumustdeleteandcleananapplicationwhenyoulocalizeafile–otherwise,thepreviousunlocalizedfilewillstillbeintherootleveloftheapplicationbundle.Eventhoughtherearelprojfoldersintheapplicationbundle,thebundlefindsthetop-levelfilefirstandreturnsitsURL.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:ImportingandExportingasXLIFFTheindustry-standardformatforlocalizationdataistheXLIFFdatatype,whichstandsforXMLLocalisationInterchangeFileFormat.Whenworkingwithatranslator,youwilloftensendthemanXLIFFfilecontainingthedataintheapplicationtolocalize,andtheywillgiveyoubackalocalizedXLIFFfileforyoutoimport.
XcodenativelysupportsimportingandexportinglocalizationdataintheXLIFFformat.Theexportingprocesswilltakecareoffindingandexportingthelocalizedstringswithintheproject,whichyoupreviouslydidmanuallyusingthegenstringstool.
ToexportthelocalizablestringsinXLIFFformat,selecttheproject(WorldTrotter)intheprojectnavigator.ThenselecttheEditormenu,andthenExportForLocalization….Onthenextscreen,youcanchoosewhethertoexportexistingtranslations(whichisprobablyagoodideasothetranslatordoesnotdoredundantwork)andwhichlanguagesyouwouldlikeexported(Figure7.18).
Figure7.18ExportinglocalizationdataasXLIFF
Toimportlocalizations,selecttheproject(WorldTrotter)intheprojectnavigator.ThenselectEditor→ImportLocalizations….Afterchoosingafile,youwillbeabletoconfirmtheupdatesbeforeyouimport.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
8ControllingAnimations
Theword“animation”isderivedfromaLatinwordthatmeans“theactofbringingtolife.”Inyourapplications,animationscansmoothlybringinterfaceelementsonscreenorintofocus,theycandrawtheuser’sattentiontoanactionableitem,andtheycangiveclearindicationsofhowyourappisrespondingtotheuser’sactions.Inthischapter,youwillreturntoyourQuizappanduseavarietyofanimationtechniquestobringittolife.
BeforeupdatingQuiz,though,let’stakealookatwhatcanbeanimatedbylookingatthedocumentation.Toopenthedocumentation,openXcode’sHelpmenuandselectDocumentationandAPIReference.Thiswillopenthedocumentationinanewwindow.
Withthedocumentationopen,usethesearchbaratthetoptosearchfortheUIViewClassReference.Double-clickinthesearchresultstoopenitandthenscrolldowntothesectiontitledAnimations.Thedocumentationgivessomeanimationrecommendations(whichwewillfollowinthisbook)andliststhepropertiesonUIViewthatcanbeanimated(Figure8.1).
Figure8.1UIViewanimationdocumentation
WOW! eBook www.wowebook.org
BasicAnimationsThedocumentationisalwaysagoodstartingpointforlearningaboutanyiOStechnology.Withthatlittlebitofinformationunderyourbelt,let’sgoaheadandaddsomeanimationstoQuiz.Thefirsttypeofanimationyouaregoingtouseisthebasicanimation.Abasicanimationanimatesbetweenastartvalueandanendvalue(Figure8.2).
Figure8.2Basicanimation
Thefirstanimationyouaregoingtoaddwillanimatethealphavalue(thedegreeoftransparency)ofthequestionlabelassociatedwithViewController.
OpenQuiz.xcodeprojandViewController.swift.Whentheuseradvancestothenextquestion,youwilluseananimationtofadeinthelabel.ThereareclassmethodsonUIViewthatwillallowyoutoaccomplishthis.ThesimplestUIViewanimationmethodis:classfuncanimateWithDuration(duration:NSTimeInterval,animations:()->Void)
Thisclassmethodtakestwoarguments:adurationoftypeNSTimeInterval(whichisanaliasforaDouble)andananimationsvariablethatisaclosure.
Closures
Aclosureisadiscretebundleoffunctionalitythatcanbepassedaroundyourcode.Closuresarealotlikefunctionsandmethods.Infact,functionsandmethodsarejustspecialcasesofclosures.
Closureshavealightweightsyntaxthatallowsthemtobeeasilypassedinasargumentstofunctionsandmethods.Aclosurecanevenbethereturntypeofafunctionormethod.Inthissection,youwilluseaclosuretospecifytheanimationsthatyouwanttooccur.
Thesignatureofaclosureisacomma-separatedlistofargumentswithinparenthesesfollowedbyareturnarrowandthereturntype:(arguments)->returntype
Noticethatthissyntaxissimilartothesyntaxforfunctions:funcfunctionName(arguments)->returntype
Nowtakealookagainattheclosuresignaturethattheanimationsargumentexpects:classfuncanimateWithDuration(duration:NSTimeInterval,animations:()->Void)
Thisclosuretakesinnoargumentsanddoesnotreturnanything.(Youwillalsoseethis
WOW! eBook www.wowebook.org
returntypeexpressedas(),whichmeansthesamethingasVoid.)
Theclosuresignatureisprettystraightforwardandfamiliar,buthowdoyoudeclareaclosureincode?Closuresyntaxtakesthefollowingform:{(arguments)->returntypein//code}
Youwriteaclosureexpressioninsidebraces({}).Theclosure’sargumentsarelistedinsideparenthesesimmediatelyaftertheopeningbrace.Aclosure’sreturntypecomesaftertheparametersandusestheregularsyntax.Thekeywordinisusedtoseparatetheclosure’sargumentsandreturntypefromthestatementsinsideofitsbody.
InViewController.swift,addanewmethodtohandletheanimationsanddeclareaclosureconstantthattakesinnoargumentsanddoesnotreturnanything.funcanimateLabelTransitions(){
letanimationClosure={()->Voidin
}}
Nowyouhaveaconstantthatreferencesachunkoffunctionality.Currently,however,thisclosuredoesnotactuallydoanything.AddfunctionalitytotheclosurethatsetsthealphaofthequestionLabelto1.Then,passthisclosureasanargumenttoanimateWithDuration(_:animations:).funcanimateLabelTransitions(){
letanimationClosure={()->Voidinself.questionLabel.alpha=1}
//AnimatethealphaUIView.animateWithDuration(0.5,animations:animationClosure)}
Currently,thequestionLabelalreadyhasanalphaof1whenitcomesonscreen,soyouwillnotseeanythinganimateifyoubuildandrun.Toaddressthis,overrideviewWillAppear(_:)toresetthequestionLabel’salphato0eachtimetheViewController’sviewcomesonscreen.overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
//Setthelabel'sinitialalphaquestionLabel.alpha=0}
Thecodeaboveworksgreat,butthereareafewmodificationsyoucanmakesoitislessverbose.Updatethecodetomakeitmoreconcise.funcanimateLabelTransitions(){
letanimationClosure={()->Voidinself.questionLabel.alpha=1}
//AnimatethealphaUIView.animateWithDuration(0.5,animations:animationClosure)
UIView.animateWithDuration(0.5,animations:{self.questionLabel.alpha=1
WOW! eBook www.wowebook.org
})}
Youhavemadetwochanges:First,youarepassingintheclosureanonymously(i.e.,passingitdirectlyintothemethodinsteadofassigningittoavariableorconstant).Second,youhaveremovedthetypeinformationsincetheclosurecaninferthisfromthecontext.
NowcalltheanimateLabelTransitions()methodwhenevertheusertapstheNextQuestionbutton.@IBActionfuncshowNextQuestion(sender:AnyObject){++currentQuestionIndexifcurrentQuestionIndex==questions.count{currentQuestionIndex=0}
letquestion:String=questions[currentQuestionIndex]questionLabel.text=question
answerLabel.text="???"
animateLabelTransitions()}
Buildandruntheapplication.WhenyoutapontheNextQuestionbutton,thelabelwillfadeintoview.Animationsprovidealessjarringuserexperiencethanhavingviewsjustpopintoexistence.
ThemethodanimateWithDuration(_:animations:)returnsimmediately.Thatis,itstartstheanimation,butitdoesnotwaitfortheanimationtocomplete.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AnotherLabelTheanimationworksgreatthefirsttimetheNextQuestionbuttonispressed,butthereisnovisibleanimationonsubsequentbuttonpressessincethelabelalreadyhasanalphavalueof1.Inthissection,youaregoingtoaddanotherlabeltotheinterface.WhentheNextQuestionbuttonispressed,theexistinglabelwillfadeoutwhilethenewlabel(withthetextofthenextquestion)willfadein.
AtthetopofViewController.swift,replaceyourdeclarationofasinglelabelwithtwolabels.@IBOutletvarquestionLabel:UILabel!@IBOutletvarcurrentQuestionLabel:UILabel!@IBOutletvarnextQuestionLabel:UILabel!
@IBOutletvaranswerLabel:UILabel!
XcodeflagsfourplaceswhereyouneedtoreplacequestionLabelwithoneofyournewlabels.UpdateviewDidLoad()tousecurrentQuestionLabel.UpdateviewWillAppear(_:)andshowNextQuestion(_:)tousenextQuestionLabel.funcviewDidLoad(){super.viewDidLoad()
letquestion=questions[currentQuestionIndex]questionLabel.text=questioncurrentQuestionLabel.text=question}
overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
//Setthelabel'sinitialalphaquestionLabel.alpha=0nextQuestionLabel.alpha=0}
@IBActionfuncshowNextQuestion(sender:AnyObject){++currentQuestionIndexifcurrentQuestionIndex==questions.count{currentQuestionIndex=0}
letquestion:String=questions[currentQuestionIndex]questionLabel.text=questionnextQuestionLabel.text=question
answerLabel.text="???"
animateLabelTransitions()}
NowupdateanimateLabelTransitions()toanimatethealphaofthetwolabels.YouwillfadeoutthecurrentQuestionLabelandfadeinthenextQuestionLabelsimultaneously.funcanimateLabelTransitions(){
//AnimatethealphaUIView.animateWithDuration(0.5,animations:{self.questionLabel.alpha=1self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1})
WOW! eBook www.wowebook.org
}
OpenMain.storyboard.Nowthatthecodehasbeenupdatedforthesetwolabels,youneedtomaketheconnections.Control-clickontheViewControllertoseealistofconnections.NoticethattheexistingquestionLabelisstillpresentwithayellowwarningsignnexttoit(Figure8.3).Clickonthextoremovethisconnection.
Figure8.3Missingconnection
ConnectthecurrentQuestionLabeloutlettotheexistingquestionlabelbydraggingfromthecirclenexttocurrentQuestionLabeltothelabelonthecanvas.
NowdraganewLabelontotheinterfaceandpositionitnexttotheexistingquestionlabel.ConnectthenextQuestionLabeltothisnewlabel.
Youwantthislabeltobeinthesamepositionastheexistingquestionlabel.Asyouhavelikelyguessed,thebestwaytoachievethisisthroughconstraints.Control-dragfromthenextQuestionLabeltothecurrentQuestionLabelandselectTop.ThenControl-dragupwardfromthenextQuestionLabeltoitssuperviewandselectCenterHorizontallyinContainer.
Atthispoint,nextQuestionLabelismisplaced.Selectthelabel,opentheResolveAutoLayoutIssuesmenu,andselectUpdateFrames.Thelabelswillnowbeontopofoneanother.
Buildandruntheapplication.TaptheNextQuestionbuttonandyouwillseeagracefulfadeforbothofthelabels.
Ifyoutapitagain,however,nofadeoccursbecausethenextQuestionLabelalreadyhasanalphaof1.Tofixthis,youwillswapthereferencestothetwolabels.Whentheanimationcompletes,thecurrentQuestionLabelneedstobesettotheonscreenlabel,andthenextQuestionLabelneedstobesettotheoffscreenlabel.Youwilluseacompletionhandlerontheanimationtoaccomplishthis.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AnimationCompletionItcanoftenbeusefultoknowwhenananimationcompletes.Forinstance,youmightwanttochainanimationstogetherorupdateanotherobjectwhentheanimationcompletes.Toknowwhentheanimationfinishes,passaclosureforthecompletionargument.Youwillusethisopportunitytoswapthetwolabelreferences.
InViewController.swift,updateanimateLabelTransitions()tousetheUIViewanimationmethodthathasthemostparameters,includingonethattakesinacompletionclosure.funcanimateLabelTransitions(){
//AnimatethealphaUIView.animateWithDuration(0.5,animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1})UIView.animateWithDuration(0.5,delay:0,options:[],animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1},completion:{_inswap(&self.currentQuestionLabel,&self.nextQuestionLabel)})}
Thedelayindicateshowlongthesystemshouldwaitbeforetriggeringtheanimation.Wewilltalkabouttheoptionslaterinthischapter.Fornow,youarepassinginanemptyarrayofnooptions.
Inthecompletionclosure,youneedtotellthesystemthatwhatusedtobethecurrentQuestionLabelisnowthenextQuestionLabelandthatwhatusedtobethenextQuestionLabelisnowthecurrentQuestionLabel.Toaccomplishthis,youusetheswap(_:_:)function,whichacceptstworeferencesandswapsthem.
Buildandruntheapplication.Nowyouareabletotransitionbetweenallofthequestions.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AnimatingConstraintsInthissection,youaregoingtoextendyouranimationtohavethenextQuestionLabelpropertyflyinfromtheleftsideofthescreenandthecurrentQuestionLabelflyouttotherightsideofthescreenwhenevertheuserpressestheNextQuestionbutton.Indoingso,youwilllearnhowtoanimateconstraints.
First,youneedareferencetotheconstraintsthatneedtobemodified.Sofar,allofyour@IBOutletshavebeentoviewobjects.Butoutletsarenotlimitedtoviews–infact,anyobjectinyourinterfacefilecanhaveanoutlet,includingconstraints.
AtthetopofViewController.swift,declaretwooutletsforthetwolabels’centeringconstraints.@IBOutletvarcurrentQuestionLabel:UILabel!@IBOutletvarcurrentQuestionLabelCenterXConstraint:NSLayoutConstraint!
@IBOutletvarnextQuestionLabel:UILabel!@IBOutletvarnextQuestionLabelCenterXConstraint:NSLayoutConstraint!
@IBOutletvaranswerLabel:UILabel!
NowopenMain.storyboard.Youwanttoconnectthesetwooutletstotheirrespectiveconstraints.Theeasiestwaytoaccomplishthisisusingthedocumentoutline.ClickthedisclosuretrianglenexttoConstraintsinthedocumentoutlineandlocateCurrentQuestionLabel.centerX.Control-dragfromtheViewControllertothatconstraint(Figure8.4)andselectthecorrectoutlet.DothesameforNextQuestionLabel.centerX.
WOW! eBook www.wowebook.org
Figure8.4Connectingaconstraintoutlet
Currently,theNextQuestionbuttonandtheanswersubviewshavetheircenterXconstrainedtothecenterXofthecurrentQuestionLabel.Whenyouimplementtheanimationforthislabeltoslideoffscreen,theothersubviewswillgowithit.Thisisnotwhatyouwant.
SelecttheconstraintthatcenterstheXvalueoftheNextQuestionbuttontothecurrentQuestionLabelanddeleteit.ThenControl-dragupwardfromtheNextQuestionbuttontoitssuperviewandselectCenterHorizontallyinContainer.
Next,youwantthetwoquestionlabelstobeonescreenwidthapart.ThecenterofnextQuestionLabelwillbehalfofthescreenwidthtotheleftoftheview.ThecenterofthecurrentQuestionLabelwillbeatitscurrentposition,centeredinthescreen.
Whentheanimationistriggered,bothlabelswillmoveafullscreenwidthtotheright,placingthenextQuestionLabelatthecenterofthescreenandthecurrentQuestionLabelhalfascreenwidthtotherightofthescreen(Figure8.5).
WOW! eBook www.wowebook.org
Figure8.5Slidingthelabels
Toaccomplishthis,whentheviewofViewControllerisloadedyouneedtomovethenextQuestionLabeltoitsoffscreenposition.
InViewController.swift,addanewmethodandcallitfromviewDidLoad().funcviewDidLoad(){super.viewDidLoad()
letquestion=questions[currentQuestionIndex]currentQuestionLabel.text=question
updateOffScreenLabel()}
funcupdateOffScreenLabel(){letscreenWidth=view.frame.widthnextQuestionLabelCenterXConstraint.constant=-screenWidth}
Nowyouwanttoanimatethelabelstogofromlefttoright.Animatingconstraintsisabitdifferentthananimatingotherproperties.Ifyoumodifytheconstantofaconstraintwithinananimationblock,noanimationwilloccur.Why?Afteraconstraintismodified,thesystemneedstorecalculatetheframesforalloftherelatedviewsinthehierarchytoaccommodatethischange.Itwouldbeexpensiveforanyconstraintchangetotriggerthisautomatically.(Imagineifyouupdatedquiteafewconstraints–youwouldnotwantittorecalculatetheframesaftereachchange.)Soyoumustaskthesystemtorecalculatetheframeswhenyouaredone.Todothis,youcallthemethodlayoutIfNeeded()onaview.Thiswillforcetheviewtolayoutitssubviewsbasedonthelatestconstraints.
InViewController.swift,updateanimateLabelTransitions()tochangetheconstraintconstantsandthenforcethelayoutoftheviews.funcanimateLabelTransitions(){
//Animatethealpha//andthecenterXconstraintsletscreenWidth=view.frame.widthself.nextQuestionLabelCenterXConstraint.constant=0self.currentQuestionLabelCenterXConstraint.constant+=screenWidth
UIView.animateWithDuration(0.5,delay:0,options:[],
WOW! eBook www.wowebook.org
animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1
self.view.layoutIfNeeded()},completion:{_inswap(&self.currentQuestionLabel,&self.nextQuestionLabel)})}
Finally,inthecompletionhandler,youneedtoswapthetwoconstraintoutletsandresetthenextQuestionLabeltobeontheleftsideofthescreen.funcanimateLabelTransitions(){
//Animatethealpha//andthecenterXconstraintsletscreenWidth=view.frame.widthself.nextQuestionLabelCenterXConstraint.constant=0self.currentQuestionLabelCenterXConstraint.constant+=screenWidth
UIView.animateWithDuration(0.5,delay:0,options:[],animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1
self.view.layoutIfNeeded()},completion:{_inswap(&self.currentQuestionLabel,&self.nextQuestionLabel)swap(&self.currentQuestionLabelCenterXConstraint,&self.nextQuestionLabelCenterXConstraint)
self.updateOffScreenLabel()})}
Buildandruntheapplication.Theanimationworksalmostperfectly.Thelabelsslideonandoffthescreen,andthealphavalueanimatesappropriatelyaswell.
Thereisonesmallproblemtofix,butitcanbeabitdifficulttosee.Toseeitmoreeasily,turnonSlowAnimationsfromtheDebugmenuinthesimulator(Command-T).Thewidthofallofthelabelsgetsanimated(toseethisontheanswerLabel,youneedtoclicktheShowAnswerbutton).Thisisbecausetheintrinsiccontentsizechangeswhenthetextchanges.Thefixistoforcetheviewtolayoutitssubviewsbeforetheanimationbegins.Thiswillupdatetheframesofallthreelabelstoaccommodatethenexttextbeforethealphaandslidinganimationsstart.
UpdateanimateLabelTransitions()toforcetheviewtolayoutitssubviewsbeforetheanimationbegins.funcanimateLabelTransitions(){
//Forceanyoutstandinglayoutchangestooccurview.layoutIfNeeded()
//Animatethealpha//andthecenterXconstraintsletscreenWidth=view.frame.widthself.nextQuestionLabelCenterXConstraint.constant=0self.currentQuestionLabelCenterXConstraint.constant+=screenWidth
UIView.animateWithDuration(0.5,delay:0,
WOW! eBook www.wowebook.org
options:[],animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1
self.view.layoutIfNeeded()},completion:{_inswap(&self.currentQuestionLabel,&self.nextQuestionLabel)swap(&self.currentQuestionLabelCenterXConstraint,&self.nextQuestionLabelCenterXConstraint)
self.updateOffScreenLabel()})}
Buildandruntheapplicationandcyclethroughsomequestionsandanswers.Theminoranimationissueisnowresolved.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TimingFunctionsTheaccelerationoftheanimationiscontrolledbyitstimingfunction.Bydefault,animationsuseanease-in/ease-outtimingfunction.Touseadrivinganalogy,thiswouldmeanthedriveracceleratessmoothlyfromresttoaconstantspeedandthengraduallyslowsdownattheend,comingtorest.
Othertimingfunctionsincludelinear(aconstantspeedfrombeginningtoend),ease-in(acceleratingtoaconstantspeedandthenendingabruptly),andease-out(beginningatfullspeedandthenslowingdownattheend).
InViewController.swift,updatetheanimationinanimateLabelTransitions()tousealineartimingfunction.UIView.animateWithDuration(0.5,delay:0,options:[.CurveLinear],animations:{self.currentQuestionLabel.alpha=0self.nextQuestionLabel.alpha=1
self.view.layoutIfNeeded()},completion:{_inswap(&self.currentQuestionLabel,&self.nextQuestionLabel)swap(&self.currentQuestionLabelCenterXConstraint,&self.nextQuestionLabelCenterXConstraint)
self.updateOffScreenLabel()})
Now,asopposedtousingthedefaultease-in/ease-outanimationcurve,theanimationwillhavealinearanimationcurve.TheoptionsparametertakesinaUIViewAnimationOptionsargument.Whyisthisargumentinsquarebrackets?Therearemanyoptionsforananimationinadditiontothetimingfunction.Becauseofthis,youneedawayofspecifyingmorethanoneoption–anarray.UIViewAnimationOptionsconformstotheOptionSetTypeprotocol,whichallowsyoutogroupmultiplevaluesusinganarray.
Herearesomeofthepossibleanimationoptionsthatyoucanpassintotheoptionsparameter.
Animationcurveoptions
Thesecontroltheaccelerationoftheanimation.Possiblevaluesare
UIViewAnimationOptions.CurveEaseInOut
UIViewAnimationOptions.CurveEaseIn
UIViewAnimationOptions.CurveEaseOut
UIViewAnimationOptions.CurveLinear
UIViewAnimationOptions.AllowUserInteraction
Bydefault,viewscannotbeinteractedwithwhenanimating.Specifyingthis
WOW! eBook www.wowebook.org
optionoverridesthedefault.Thiscanbeusefulforrepeatinganimations,suchasapulsingview.
UIViewAnimationOptions.Repeat
Repeatstheanimationindefinitely;oftenpairedwiththeUIViewAnimationOptions.Autoreverseoption.
UIViewAnimationOptions.Autoreverse
Runstheanimationforwardandthenbackward,returningtheviewtoitsinitialstate.
BesuretocheckouttheConstantssectionoftheUIViewClassReferencetoseeallofthepossibleoptions.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:SpringAnimationsiOShasapowerfulphysicsenginebuiltin.Aneasywaytoharnessthispowerisbyusingaspringanimation.//UIView
classfuncanimateWithDuration(duration:NSTimeInterval,delay:NSTimeInterval,usingSpringWithDampingdampingRatio:CGFloat,initialSpringVelocityvelocity:CGFloat,options:UIViewAnimationOptions,animations:()->Void,completion:((Bool)->Void)?)
Usethismethodtohavethetwolabelsanimateonandoffthescreeninaspring-likefashion.RefertotheUIViewdocumentationtounderstandeachofthearguments.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:LayoutGuidesIfyourotateintolandscape,thenextQuestionLabelbecomesvisible.Insteadofhardcodingthespacingconstraint’sconstant,useaninstanceofUILayoutGuidetospacethetwolabelsapart.ThislayoutguideshouldhaveawidthconstraintequaltotheViewController’sviewtoensurethatthenextQuestionLabelremainsoffscreenwhennotanimating.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
9UITableViewand
UITableViewControllerManyiOSapplicationsshowtheuseralistofitemsandallowtheusertoselect,delete,orreorderitemsonthelist.Whetheranapplicationdisplaysalistofpeopleintheuser’saddressbookoralistofbest-sellingitemsontheAppStore,itisaUITableViewdoingthework.
AUITableViewdisplaysasinglecolumnofdatawithavariablenumberofrows.Figure9.1showssomeexamplesofUITableView.
Figure9.1ExamplesofUITableView
WOW! eBook www.wowebook.org
BeginningtheHomepwnerApplicationInthischapter,youaregoingtostartanapplicationcalledHomepwnerthatkeepsaninventoryofallyourpossessions.Inthecaseofafireorothercatastrophe,youwillhavearecordforyourinsurancecompany.(“Homepwner,”bytheway,isnotatypo.Ifyouneedadefinitionfortheword“pwn,”pleasevisithttp://www.urbandictionary.com.)
Sofar,youriOSprojectshavebeensmall,butHomepwnerwillgrowintoarealisticallycomplexapplicationoverthecourseofeightchapters.Bytheendofthischapter,HomepwnerwillpresentalistofIteminstancesinaUITableView,asshowninFigure9.2.
Figure9.2Homepwner:phase1
CreateanewiOSSingleViewApplicationprojectandconfigureitasshowninFigure9.3.
WOW! eBook www.wowebook.org
Figure9.3ConfiguringHomepwner
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UITableViewControllerAUITableViewisaviewobject.RecallthatintheModel-View-Controllerdesignpattern,whichiOSdevelopersdotheirbesttofollow,eachclassfallsintoexactlyoneofthefollowingcategories:
model:holdsdataandknowsnothingabouttheuserinterface
view:isvisibletotheuserandknowsnothingaboutthemodelobjects
controller:keepstheuserinterfaceandthemodelobjectsinsyncandcontrolstheflowoftheapplication
Thus,aUITableView,aviewobject,doesnothandleapplicationlogicordata.WhenusingaUITableView,youmustconsiderwhatelseisnecessarytogetthetableworkinginyourapplication:
AUITableViewtypicallyneedsaviewcontrollertohandleitsappearanceonthescreen.
AUITableViewneedsadatasource.AUITableViewasksitsdatasourceforthenumberofrowstodisplay,thedatatobeshowninthoserows,andothertidbitsthatmakeaUITableViewausefuluserinterface.Withoutadatasource,atableviewisjustanemptycontainer.ThedataSourceforaUITableViewcanbeanytypeofobjectaslongasitconformstotheUITableViewDataSourceprotocol.
AUITableViewtypicallyneedsadelegatethatcaninformotherobjectsofeventsinvolvingtheUITableView.ThedelegatecanbeanyobjectaslongasitconformstotheUITableViewDelegateprotocol.
AninstanceoftheclassUITableViewControllercanfillallthreeroles:viewcontroller,datasource,anddelegate.
UITableViewControllerisasubclassofUIViewController,soaUITableViewControllerhasaview.AUITableViewController’sviewisalwaysaninstanceofUITableView,andtheUITableViewControllerhandlesthepreparationandpresentationoftheUITableView.
WhenaUITableViewControllercreatesitsview,thedataSourceanddelegatepropertiesoftheUITableViewareautomaticallysettopointattheUITableViewController(Figure9.4).
WOW! eBook www.wowebook.org
Figure9.4UITableViewController-UITableViewrelationship
SubclassingUITableViewController
NowyouaregoingtoimplementasubclassofUITableViewControllerforHomepwner.CreateanewSwiftfilenamedItemsViewController.InItemsViewController.swift,defineaUITableViewControllersubclassnamedItemsViewController.importFoundationimportUIKit
classItemsViewController:UITableViewController{
}
NowopenMain.storyboard.Youwanttheinitialviewcontrollertobeatableviewcontroller.SelecttheexistingViewControlleronthecanvasandpressDelete.ThendragaTableViewControllerfromtheobjectlibraryontothecanvas.WiththeTableViewControllerselected,openitsidentityinspectorandchangetheclasstoItemsViewController.Finally,opentheattributesinspectorforItemsViewControllerandchecktheboxforIsInitialViewController.
Buildandrunyourapplication.Youshouldseeanemptytableview,asshowninFigure9.5.AsasubclassofUIViewController,aUITableViewControllerinheritstheviewproperty.Whenthispropertyisaccessedforthefirsttime,theloadView()methodiscalled,whichcreatesandloadsaviewobject.AUITableViewController’sviewisalwaysaninstanceofUITableView,soaskingfortheviewofaUITableViewControllergetsyouabright,shiny,andemptytableview.
WOW! eBook www.wowebook.org
Figure9.5EmptyUITableView
YounolongerneedtheViewController.swiftfilethatthetemplatecreatedforyou.SelectthisfileintheprojectnavigatorandpressDelete.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingtheItemClassYourtableviewneedsomerowstodisplay.Eachrowinthetableviewwilldisplayanitemwhichwillhaveinformationsuchasaname,serialnumber,andvalueindollars.
CreateanewSwiftfilenamedItem.InItem.swift,definetheItemclassandgiveitfourproperties.importFoundationimportUIKit
classItem:NSObject{varname:StringvarvalueInDollars:IntvarserialNumber:String?letdateCreated:NSDate}
IteminheritsfromNSObject.NSObjectisthebaseclassthatmostObjective-Cclassesinheritfrom.AlloftheUIKitclassesthatyouhaveworkedwith–UIView,UITextField,andUIViewController,tonameafew–inheriteitherdirectlyorindirectlyfromNSObject.YourownclasseswilloftenneedtoinheritfromNSObjectwhentheyneedtointerfacewiththeruntimesystem.
NoticethatserialNumberisanoptionalString,necessarybecauseanitemmaynothaveaserialnumber.Also,noticethatnoneofthepropertieshaveadefaultvalue.Youwillneedtogivethemvaluesinadesignatedinitializer.
Custominitializers
YoulearnedaboutstructinitializersinChapter2.Initializersonstructsarefairlystraightforwardbecausestructsdonotsupportinheritance.Classes,ontheotherhand,havesomerulesforinitializerstosupportinheritance.
Classescanhavetwokindsofinitializers:designatedinitializersandconvenienceinitializers.
Adesignatedinitializerisaprimaryinitializerfortheclass.Everyclasshasatleastonedesignatedinitializer.Adesignatedinitializerensuresthatallpropertiesintheclasshaveavalue.Onceitensuresthat,adesignatedinitializercallsadesignatedinitializeronitssuperclass(ifithasone).
ImplementanewdesignatedinitializerontheItemclassthatsetstheinitialvaluesforalloftheproperties.importUIKit
classItem:NSObject{varname:StringvarvalueInDollars:IntvarserialNumber:String?letdateCreated:NSDate
init(name:String,serialNumber:String?,valueInDollars:Int){self.name=nameself.valueInDollars=valueInDollarsself.serialNumber=serialNumber
WOW! eBook www.wowebook.org
self.dateCreated=NSDate()
super.init()}}
Thisinitializertakesinargumentsforthename,serialNumber,andvalueInDollars.Sincetheargumentnamesandthepropertynamesarethesame,youmustuseselftodistinguishthepropertyfromtheargument.
Nowthatyouhaveimplementedyourowncustominitializer,youlosethefreeinitializer–init()–thatclasseshave.Thefreeinitializerisusefulwhenallofyourclass’spropertieshavedefaultvaluesandyoudonotneedtodoadditionalworktocreatethenewinstance.TheItemclassdoesnotsatisfythiscriteria,soyouhavedeclaredacustominitializerfortheclass.
Everyclassmusthaveatleastonedesignatedinitializer,butconvenienceinitializersareoptional.Youcanthinkofconvenienceinitializersashelpers.Aconvenienceinitializeralwayscallsanotherinitializeronthesameclass.Convenienceinitializersareindicatedbytheconveniencekeywordbeforetheinitializername.
AddaconvenienceinitializertoItemthatcreatesarandomlygenerateditem.convenienceinit(random:Bool=false){ifrandom{letadjectives=["Fluffy","Rusty","Shiny"]letnouns=["Bear","Spork","Mac"]
varidx=arc4random_uniform(UInt32(adjectives.count))letrandomAdjective=adjectives[Int(idx)]
idx=arc4random_uniform(UInt32(nouns.count))letrandomNoun=nouns[Int(idx)]
letrandomName="\(randomAdjective)\(randomNoun)"letrandomValue=Int(arc4random_uniform(100))letrandomSerialNumber=NSUUID().UUIDString.componentsSeparatedByString("-").first!
self.init(name:randomName,serialNumber:randomSerialNumber,valueInDollars:randomValue)}else{self.init(name:"",serialNumber:nil,valueInDollars:0)}}
Ifrandomistrue,theinstanceisconfiguredwitharandomname,serialnumber,andvalue.(Thearc4random_uniformfunctionreturnsarandomvaluebetween0,inclusive,andthevaluepassedinastheargument,exclusive.)Noticethatattheendofbothbranchesoftheconditional,youarecallingthroughtothedesignatedinitializerforItem.Convenienceinitializersmustcallanotherinitializeronthesametype,whereasdesignatedinitializersmustcalladesignatedinitializeronitssuperclass.
TheItemclassisreadyforwork.InthenextsectionyouwilldisplayanarrayofIteminstancesinatableview.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UITableView’sDataSourceTheprocessofprovidingrowstoaUITableViewinCocoaTouch(thecollectionofframeworksusedtobuildiOSapps)isdifferentfromthetypicalproceduralprogrammingtask.Inaproceduraldesign,youtellthetableviewwhatitshoulddisplay.InCocoaTouch,thetableviewasksanotherobject–itsdataSource–whatitshoulddisplay.Inthiscase,theItemsViewControlleristhedatasource,soitneedsawaytostoreitemdata.
YouaregoingtouseanarraytostoretheIteminstances,butwithatwist.ThearraythatholdstheIteminstanceswillbeabstractedintoanotherobject–anItemStore(Figure9.6).
Figure9.6Homepwnerobjectdiagram
Ifanobjectwantstoseealloftheitems,itwillasktheItemStoreforthearraythatcontainsthem.Infuturechapters,thestorewillberesponsibleforperformingoperationsonthearray,likereordering,adding,andremovingitems.Itwillalsoberesponsibleforsavingandloadingtheitemsfromdisk.
CreateanewSwiftfilenamedItemStore.InItemStore.swift,definetheItemStoreclassanddeclareapropertytostorethelistofItems.importFoundationimportUIKit
WOW! eBook www.wowebook.org
classItemStore{
varallItems=[Item]()
}
ItemStoreisaSwiftbaseclass–itdoesnotinheritfromanyotherclass.UnliketheItemclassthatyoudefinedearlier,ItemStoredoesnotrequireanyofthebehaviorthatNSObjectaffords.
TheItemsViewControllerwillcallamethodonItemStorewhenitwantsanewItemtobecreated.TheItemStorewilloblige,creatingtheobjectandaddingittoanarrayofinstancesofItem.
InItemStore.swift,implementcreateItem()tocreateandreturnanewItem.funccreateItem()->Item{letnewItem=Item(random:true)
allItems.append(newItem)
returnnewItem}
Givingthecontrolleraccesstothestore
InItemsViewController.swift,addapropertyforanItemStore.classItemsViewController:UITableViewController{
varitemStore:ItemStore!
Now,whereshouldyousetthispropertyontheItemsViewControllerinstance?Whentheapplicationfirstlaunches,theAppDelegate’sapplication(_:didFinishLaunchingWithOptions:)methodiscalled.TheAppDelegateisdeclaredinAppDelegate.swiftand,asthenameimplies,servesasthedelegatefortheapplicationitself.Itisresponsibleforhandlingthechangesinstatethattheapplicationgoesthrough.YouwilllearnmoreabouttheAppDelegateandthestatesthattheapplicationgoesthroughinChapter15.
OpenAppDelegate.swift.AccesstheItemsViewController(whichwillbetherootViewControllerofthewindow)andsetitsitemStorepropertytobeanewinstanceofItemStore.funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{//Overridepointforcustomizationafterapplicationlaunch.
//CreateanItemStoreletitemStore=ItemStore()
//AccesstheItemsViewControllerandsetitsitemstoreletitemsController=window!.rootViewControlleras!ItemsViewControlleritemsController.itemStore=itemStore
returntrue}
Finally,inItemStore.swift,implementthedesignatedinitializertoaddfiverandomitems.
WOW! eBook www.wowebook.org
init(){for_in0..<5{createItem()}}
AtthispointyoumaybewonderingwhyitemStorewassetexternallyontheItemsViewController.Whydidn’ttheItemsViewControllerinstanceitselfjustcreateaninstanceofthestore?Thereasonforthisapproachisbasedonafairlycomplextopiccalledthedependencyinversionprinciple.Theessentialgoalofthisprincipleistodecoupleobjectsinanapplicationbyinvertingcertaindependenciesbetweenthem.Thisresultsinmorerobustandmaintainablecode.
Thedependencyinversionprinciplestatesthat:
1. High-levelobjectsshouldnotdependonlow-levelobjects.Bothshoulddependonabstractions.
2. Abstractionsshouldnotdependondetails.Detailsshoulddependonabstractions.
TheabstractionrequiredbythedependencyinversionprincipleinHomepwneristheconceptofa“store.”Astoreisalower-levelobjectthatretrievesandsavesIteminstancesthroughdetailsthatareonlyknowntothatclass.ItemsViewControllerisahigher-levelobjectthatonlyknowsthatitwillbeprovidedwithautilityobject(thestore)fromwhichitcanobtainalistofIteminstancesandtowhichitcanpassneworupdatedIteminstancestobestoredpersistently.ThisresultsinadecouplingbecauseItemsViewControllerisnotdependentonItemStore.Infact,aslongasthestoreabstractionisrespected,ItemStorecouldbereplacedbyanotherobjectthatfetchesIteminstancesdifferently(suchasbyusingawebservice)withoutanychangestoItemsViewController.
Acommonpatternusedwhenimplementingthedependencyinversionprincipleisdependencyinjection.Initssimplestform,higher-levelobjectsdonotassumewhichlower-levelobjectstheyneedtouse.Instead,thosearepassedtothemthroughaninitializerorproperty.InyourimplementationofItemsViewController,youusedinjectionthroughapropertytogiveitastore.
Implementingdatasourcemethods
Nowthattherearesomeitemsinthestore,youneedtoteachItemsViewControllerhowtoturnthoseitemsintorowsthatitsUITableViewcandisplay.WhenaUITableViewwantstoknowwhattodisplay,itcallsmethodsfromthesetofmethodsdeclaredintheUITableViewDataSourceprotocol.
OpenthedocumentationandsearchfortheUITableViewDataSourceprotocolreference.SelectTasksfromthelefthandpane(Figure9.7).
WOW! eBook www.wowebook.org
Figure9.7UITableViewDataSourceprotocoldocumentation
IntheConfiguringaTableViewtask,noticethattwoofthemethodsaremarkedRequired.ForItemsViewControllertoconformtoUITableViewDataSource,itmustimplementtableView(_:numberOfRowsInSection:)andtableView(_:cellForRowAtIndexPath:).Thesemethodstellthetableviewhowmanyrowsitshoulddisplayandwhatcontenttodisplayineachrow.
WheneveraUITableViewneedstodisplayitself,itcallsaseriesofmethods(therequiredmethodsplusanyoptionalonesthathavebeenimplemented)onitsdataSource.TherequiredmethodtableView(_:numberOfRowsInSection:)returnsanintegervalueforthenumberofrowsthattheUITableViewshoulddisplay.InthetableviewforHomepwner,thereshouldbearowforeachentryinthestore.
InItemsViewController.swift,implementtableView(_:numberOfRowsInSection:).overridefunctableView(tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{returnitemStore.allItems.count}
Wonderingaboutthesectionthatthismethodrefersto?Tableviewscanbebrokenupintosections,andeachsectionhasitsownsetofrows.Forexample,intheaddressbook,allnamesbeginningwith“C”aregroupedtogetherinasection.Bydefault,atableviewhasonesection,andinthischapter,youwillworkwithonlyone.Onceyouunderstandhowatableviewworks,itisnothardtousemultiplesections.Infact,usingsectionsisthefirstchallengeattheendofthischapter.
WOW! eBook www.wowebook.org
ThesecondrequiredmethodintheUITableViewDataSourceprotocolistableView(_:cellForRowAtIndexPath:).Toimplementthismethod,youneedtolearnaboutanotherclass–UITableViewCell.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UITableViewCellsEachrowofatableviewisaview.TheseviewsareinstancesofUITableViewCell.Inthissection,youwillcreatetheinstancesofUITableViewCelltofillthetableview.
Acellitselfhasonesubview–itscontentView(Figure9.8).ThecontentViewisthesuperviewforthecontentofthecell.Thecellmayalsohaveanaccessoryview.
Figure9.8UITableViewCelllayout
Theaccessoryviewshowsanaction-orientedicon,suchasacheckmark,adisclosureicon,oraninformationbutton.Theseiconsareaccessedthroughpredefinedconstantsfortheappearanceoftheaccessoryview.ThedefaultisUITableViewCellAccessoryType.None,andthatiswhatyouaregoingtouseinthischapter.ButyouwillseetheaccessoryviewagaininChapter22.(Curiousnow?SeethedocumentationforUITableViewCellformoredetails.)
TherealmeatofaUITableViewCellisthecontentView,whichhasthreesubviewsofitsown(Figure9.9).TwoofthosesubviewsareUILabelinstancesthatarepropertiesofUITableViewCellnamedtextLabelanddetailTextLabel.ThethirdsubviewisaUIImageViewcalledimageView.Inthischapter,youwillusetextLabelanddetailTextLabel.
WOW! eBook www.wowebook.org
Figure9.9UITableViewCellhierarchy
EachcellalsohasaUITableViewCellStylethatdetermineswhichsubviewsareusedandtheirpositionwithinthecontentView.ExamplesofthesestylesandtheirconstantsareshowninFigure9.10.
Figure9.10UITableViewCellStyle:stylesandconstants
CreatingandretrievingUITableViewCells
Fornow,eachcellwilldisplaythenameofItemasitstextLabelandtheserialNumberofItemasitsdetailTextLabel.Tomakethishappen,youneed
WOW! eBook www.wowebook.org
toimplementthesecondrequiredmethodfromtheUITableViewDataSourceprotocol,tableView(_:cellForRowAtIndexPath:).Thismethodwillcreateacell,setitstextLabeltothenameoftheItem,setitsdetailTextLabeltothevalueInDollarsoftheItem,andreturnittotheUITableView(Figure9.11).
Figure9.11UITableViewCellretrieval
HowdoyoudecidewhichcellanItemcorrespondsto?OneoftheparameterssenttotableView(_:cellForRowAtIndexPath:)isanNSIndexPath,whichhastwoproperties:sectionandrow.Whenthismethodiscalledonadatasource,thetableviewisasking,“CanIhaveacelltodisplayinsectionX,rowY?”Becausethereisonlyonesectioninthisexercise,yourimplementationwillonlybeconcernedwiththeindexpath’srow.
InItemsViewController.swift,implementtableView(_:cellForRowAtIndexPath:)sothatthenthrowdisplaysthenthentryintheallItemsarray.overridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{//CreateaninstanceofUITableViewCell,withdefaultappearanceletcell=UITableViewCell(style:.Value1,reuseIdentifier:"UITableViewCell")
//Setthetextonthecellwiththedescriptionoftheitem//thatisatthenthindexofitems,wheren=rowthiscell//willappearinonthetableviewletitem=itemStore.allItems[indexPath.row]
cell.textLabel?.text=item.namecell.detailTextLabel?.text="$\(item.valueInDollars)"
returncell}
BuildandruntheapplicationnowandyouwillseeaUITableViewpopulatedwithalistWOW! eBook
www.wowebook.org
ofrandomitems.
ReusingUITableViewCells
iOSdeviceshavealimitedamountofmemory.IfyouweredisplayingalistwiththousandsofentriesinaUITableView,youwouldhavethousandsofinstancesofUITableViewCell.Mostofthesecellswouldtakeupmemoryneedlessly.Afterall,iftheusercannotcurrentlyseeacellonscreen,thenthereisnoreasonforthatcelltohaveaclaimonmemory.
Toconservememoryandimproveperformance,youcanreusetableviewcells.Whentheuserscrollsthetable,somecellsmoveoffscreen.Offscreencellsareputintoapoolofcellsavailableforreuse.Then,insteadofcreatingabrandnewcellforeveryrequest,thedatasourcefirstchecksthepool.Ifthereisanunusedcell,thedatasourceconfiguresitwithnewdataandreturnsittothetableview(Figure9.12).
Figure9.12ReusableinstancesofUITableViewCell
Thereisoneproblem:sometimesaUITableViewhasdifferenttypesofcells.Occasionally,yousubclassUITableViewCelltocreateaspeciallookorbehavior.However,differentsubclassesfloatingaroundthepoolofreusablecellscreatethepossibilityofgettingbackacellofthewrongtype.Youmustbesureofthetypeofthecellreturnedsothatyoucanbesureofwhatpropertiesandmethodsithas.
Notethatyoudonotcareaboutgettinganyspecificcelloutofthepoolbecauseyouaregoingtochangethecellcontentanyway.Whatyouneedisacellofaspecifictype.The
WOW! eBook www.wowebook.org
goodnewsisthateverycellhasareuseIdentifierpropertyoftypeString.Whenadatasourceasksthetableviewforareusablecell,itpassesastringandsays,“Ineedacellwiththisreuseidentifier.”Byconvention,thereuseidentifieristypicallythenameofthecellclass.
Toreusecells,youneedtoregistereitheraprototypecelloraclasswiththetableviewforaspecificreuseidentifier.YouaregoingtoregisterthedefaultUITableViewCellclass.Youtellthetableview,“Hey,anytimeIaskforacellwiththisreuseidentifier,givemebackacellthatisthisspecificclass.”Thetableviewwilleithergiveyouacellfromthereusepoolorinstantiateanewcelliftherearenocellsofthattypeinthereusepool.
OpenMain.storyboard.NoticeinthetableviewthatthereisasectionforPrototypeCells(Figure9.13).Inthisarea,youcanconfigurethedifferentkindsofcellsthatyouneedfortheassociatedtableview.Ifyouarecreatingcustomcells,thisiswhereyouwillsetuptheinterfaceforthecells.ItemsViewControlleronlyneedsonekindofcell,andusingoneofthebuilt-instyleswillworkgreatfornow,soyouwillonlyneedtoconfiguresomeattributesonthecellthatisalreadyonthecanvas.
Figure9.13Prototypecells
Selecttheprototypecellandopenitsattributesinspector.ChangetheStyletoRightDetail(whichcorrespondstoUITableViewCellStyle.Value1)andgiveitanIdentifierofUITableViewCell(Figure9.14).
WOW! eBook www.wowebook.org
Figure9.14Tableviewcellattributes
Next,inItemsViewController.swift,updatetableView(_:cellForRowAtIndexPath:)toreusecells.overridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{//CreateaninstanceofUITableViewCell,withdefaultappearanceletcell=UITableViewCell(style:.Value1,reuseIdentifier:"UITableViewCell")
//Getaneworrecycledcellletcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",forIndexPath:indexPath)
...}
ThemethoddequeueReusableCellWithIdentifier(_:forIndexPath:)willcheckthepool,orqueue,ofcellstoseewhetheracellwiththecorrectreuseidentifieralreadyexists.Ifso,itwill“dequeue”thatcell.Ifthereisnotanexistingcell,anewcellwillbecreatedandreturned.
Buildandruntheapplication.Thebehavioroftheapplicationshouldremainthesame.Reusingcellsmeansthatyouonlyhavetocreateahandfulofcells,whichputsfewerdemandsonmemory.Yourapplication’susers(andtheirdevices)willthankyou.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ContentInsetsAsyouhavebeenrunningtheapplicationthroughoutthischapter,youmighthavenoticedthatthefirsttableviewcellunderlapsthestatusbar(Figure9.15).Theinterfacesfortheapplicationsyoucreatefilluptheentirewindowofthedevice.Thestatusbar,ifvisible,isplacedontopoftheinterface,soyourinterfacesmustaccountfortheplacementofthestatusbar.
Figure9.15Tableviewcellunderlappingstatusbar
Tohavethetableviewcellsnotunderlapthestatusbar,youwilladdsomepaddingtothetopofthetableview.AUITableViewisasubclassofUIScrollView,fromwhichitinheritsthecontentInsetproperty.Youcanthinkofthecontentinsetasapaddingforallfoursidesofthescrollview.
InItemsViewController.swift,overrideviewDidLoad()toupdatethetableviewcontentinset.overridefuncviewDidLoad(){super.viewDidLoad()
//GettheheightofthestatusbarletstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)tableView.contentInset=insetstableView.scrollIndicatorInsets=insets}
Thetopofthetableviewisgivenacontentinsetequaltotheheightofthestatusbar.Thiswillmakethecontentappearbelowthestatusbarwhenthetableviewisscrolledtothetop.Thescrollindicatorswillalsounderlapthestatusbar,soyougivethemthesameinsetstohavethemappearjustbelowthestatusbar.
NoticethatyouaccessthetableViewpropertyontheItemsViewControllertogetatthetableview.ThispropertyisinheritedfromUITableViewControllerandreturnsthecontroller’stableview.WhileyoucangetthesameobjectbyaccessingtheviewofaUITableViewController,usingtableViewtellsthecompilerthatthe
WOW! eBook www.wowebook.org
returnedobjectwillbeaninstanceofUITableView.Thus,callingamethodoraccessingapropertythatisspecifictoUITableViewwillnotgenerateanerror.
Buildandruntheapplication.Thetableviewcellcontentnolongerunderlapsthestatusbarwhenthetableviewisscrolledtothetop(Figure9.16).
Figure9.16Tableviewwithadjustedcontentinset
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:SectionsHavetheUITableViewdisplaytwosections–oneforitemsworthmorethan$50andonefortherest.Beforeyoustartthischallenge,copythefoldercontainingtheprojectandallofitssourcefilesinFinder.Thentacklethechallengeinthecopiedproject;youwillneedtheoriginaltobuildoninthefollowingchapters.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:ConstantRowsMakeitsothelastrowoftheUITableViewalwayshasthetextNomoreitems!.Makesurethisrowappearsregardlessofthenumberofitemsinthestore(including0items).
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:CustomizingtheTableMakeeachrow’sheight60points,exceptforthelastrowfromthesilverchallenge,whichshouldremain44points.Then,changethefontsizeofeveryrowexceptthelastto20points.Finally,makethebackgroundoftheUITableViewdisplayanimage.(Tomakethispixel-perfect,youwillneedanimageofthecorrectsizedependingonyourdevice.RefertothechartinChapter1.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
10EditingUITableView
Inthelastchapter,youcreatedanapplicationthatdisplaysalistofIteminstancesinaUITableView.Thenextstepisallowingtheusertointeractwiththetable–toadd,delete,andmoverows.Figure10.1showswhatHomepwnerwilllooklikebytheendofthischapter.
Figure10.1Homepwnerineditingmode
WOW! eBook www.wowebook.org
EditingModeUITableViewhasaneditingproperty,andwhenthispropertyissettotrue,theUITableViewenterseditingmode.Oncethetableviewisineditingmode,therowsofthetablecanbemanipulatedbytheuser.Dependingonhowthetableviewisconfigured,theusercanchangetheorderoftherows,addrows,orremoverows.Editingmodedoesnotallowtheusertoeditthecontentofarow.
Butfirst,theuserneedsawaytoputtheUITableViewineditingmode.Fornow,youaregoingtoincludeabuttonintheheaderviewofthetable.Aheaderviewappearsatthetopofatableandisusefulforaddingsection-wideortable-widetitlesandcontrols.ItcanbeanyUIViewinstance.
Notethatthetableviewusestheword“header”intwodifferentways:therecanbeatableheaderandtherecanbesectionheaders.Likewise,therecanbeatablefooterandsectionfooters(Figure10.2).
Figure10.2Sectionheadersandfooters
Youarecreatingatableheaderview.Itwillhavetwosubviewsthatareinstancesof
WOW! eBook www.wowebook.org
UIButton:onetotoggleeditingmodeandtheothertoaddanewItemtothetable.Youcouldcreatethisviewprogrammatically,butinthiscaseyouwillcreatetheviewanditssubviewsinthestoryboardfile.
First,let’ssetupthenecessarycode.ReopenHomepwner.xcodeproj.InItemsViewController.swift,stubouttwomethodsintheimplementation.classItemsViewController:UITableViewController{
varitemStore:ItemStore!
@IBActionfuncaddNewItem(sender:AnyObject){
}
@IBActionfunctoggleEditingMode(sender:AnyObject){
}
NowopenMain.storyboard.Fromtheobjectlibrary,dragaViewtotheverytopofthetableview.Thiswilladdtheviewasaheaderviewforthetableview.Resizetheheightofthisviewtobeabout60points.(Youcanusethesizeinspectorifyouwanttomakeitexact.)
NowdragtwoButtonsfromtheobjectlibrarytotheheaderview.ChangetheirtextandpositionthemasshowninFigure10.3.Youdonotneedtobeexact–youwilladdconstraintssoontopositionthebuttons.
Figure10.3Addingbuttonstotheheaderview
SelectbothofthebuttonsandopentheAutoLayoutAlignmenu.SelectVerticallyinContainerwithaconstantof0.MakesureUpdateFramesissettoNone,andthenclickAdd2Constraints(Figure10.4).
WOW! eBook www.wowebook.org
Figure10.4Alignmenuconstraints
OpenthePinmenuandconfigureitexactlylikeFigure10.5.Makesurethevaluesfortheleadingandtrailingconstraintssaveafteryouhavetypedthem;sometimesthevaluesdonotsave,soitcanbeabittricky.Whenyouhavedonethat,clickAdd4Constraints.
WOW! eBook www.wowebook.org
Figure10.5Pinmenuconstraints
Finally,connecttheactionsforthetwobuttonsasshowninFigure10.6.
Figure10.6Connectingthetwoactions
Buildandruntheapplicationtoseetheinterface.
Nowlet’simplementthetoggleEditingMode(_:)method.YoucouldtoggletheeditingpropertyofUITableViewdirectly.However,UIViewControlleralso
WOW! eBook www.wowebook.org
hasaneditingproperty.AUITableViewControllerinstanceautomaticallysetstheeditingpropertyofitstableviewtomatchitsowneditingproperty.Bysettingtheeditingpropertyontheviewcontrolleritself,itcanensurethatotheraspectsoftheinterfacealsoenterandleaveeditingmode.YouwillseeanexampleofthisinChapter13withUIViewController’seditButtonItem().
Tosettheeditingpropertyforaviewcontroller,youcallthemethodsetEditing(_:animated:).InItemsViewController.swift,implementtoggleEditingMode(_:).@IBActionfunctoggleEditingMode(sender:AnyObject){//Ifyouarecurrentlyineditingmode...ifediting{//Changetextofbuttontoinformuserofstatesender.setTitle("Edit",forState:.Normal)
//TurnoffeditingmodesetEditing(false,animated:true)}else{//Changetextofbuttontoinformuserofstatesender.setTitle("Done",forState:.Normal)
//EntereditingmodesetEditing(true,animated:true)}}
Buildandrunyourapplication.TaptheEditbuttonandtheUITableViewwillentereditingmode(Figure10.7).
Figure10.7UITableViewineditingmode
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AddingRowsTherearetwocommoninterfacesforaddingrowstoatableviewatruntime.
Abuttonabovethecellsofthetableview:usuallyforaddingarecordforwhichthereisadetailview.Forexample,intheContactsapp,youtapabuttonwhenyoumeetanewpersonandwanttotakedownhisorherinformation.
Acellwithagreenplussign:usuallyforaddinganewfieldtoarecord,suchaswhenyouwanttoaddabirthdaytoaperson’srecordintheContactsapp.Ineditingmode,youtapthegreenplussignnextto“addbirthday.”
Inthisexercise,youwillusethefirstoptionandcreateaNewbuttonintheheaderview.Whenthisbuttonistapped,anewrowwillbeaddedtotheUITableView.
InItemsViewController.swift,implementaddNewItem(_:).@IBActionfuncaddNewItem(sender:AnyObject){//Makeanewindexpathforthe0thsection,lastrowletlastRow=tableView.numberOfRowsInSection(0)letindexPath=NSIndexPath(forRow:lastRow,inSection:0)
//InsertthisnewrowintothetabletableView.insertRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}
Buildandruntheapplication.TaptheAddbuttonand…theapplicationcrashes.Theconsoletellsyouthatthetableviewhasaninternalinconsistencyexception.
Rememberthat,ultimately,itisthedataSourceoftheUITableViewthatdeterminesthenumberofrowsthetableviewshoulddisplay.Afterinsertinganewrow,thetableviewhassixrows(theoriginalfiveplusthenewone).WhentheUITableViewasksitsdataSourceforthenumberofrows,theItemsViewControllerconsultsthestoreandreturnsthatthereshouldbefiverows.TheUITableViewcannotresolvethisinconsistencyandthrowsanexception.
YoumustmakesurethattheUITableViewanditsdataSourceagreeonthenumberofrows.Thus,youmustaddanewItemtotheItemStorebeforeyouinsertthenewrow.
InItemsViewController.swift,updateaddNewItem(_:).@IBActionfuncaddNewItem(sender:AnyObject){//Makeanewindexpathforthe0thsection,lastrowletlastRow=tableView.numberOfRowsInSection(0)letindexPath=NSIndexPath(forRow:lastRow,inSection:0)
//InsertthisnewrowintothetabletableView.insertRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)
//CreateanewitemandaddittothestoreletnewItem=itemStore.createItem()
//Figureoutwherethatitemisinthearrayifletindex=itemStore.allItems.indexOf(newItem){letindexPath=NSIndexPath(forRow:index,inSection:0)
//InsertthisnewrowintothetabletableView.insertRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}
WOW! eBook www.wowebook.org
}
Buildandruntheapplication.TaptheAddbutton,andthenewrowwillslideintothebottompositionofthetable.Rememberthattheroleofaviewobjectistopresentmodelobjectstotheuser;updatingviewswithoutupdatingthemodelobjectsisnotveryuseful.
Nowthatyouhavetheabilitytoaddrowsanditems,younolongerneedthecodethatputsfiverandomitemsintothestore.
OpenItemStore.swiftandremovetheinitializercode.init(){for_in0..<5{createItem()}}
Buildandruntheapplication.Therewillnolongerbeanyrowswhenyoufirstlaunchtheapplication,butyoucanaddsomebytappingtheAddbutton.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DeletingRowsIneditingmode,theredcircleswiththeminussign(showninFigure10.7)aredeletioncontrols,andtappingoneshoulddeletethatrow.However,atthispoint,youcannotactuallydeletetherow.(Tryitandsee.)Beforethetableviewwilldeletearow,itcallsamethodonitsdatasourceabouttheproposeddeletionandwaitsforconfirmation.
Whendeletingacell,youmustdotwothings:removetherowfromtheUITableViewandremovetheItemassociatedwithitfromtheItemStore.Topullthisoff,theItemStoremustknowhowtoremoveobjectsfromitself.
InItemStore.swift,implementanewmethodtoremoveaspecificitem.funcremoveItem(item:Item){ifletindex=allItems.indexOf(item){allItems.removeAtIndex(index)}}
NowyouwillimplementtableView(_:commitEditingStyle:forRowAtIndexPath:),amethodfromtheUITableViewDataSourceprotocol.(ThismethodiscalledontheItemsViewController.KeepinmindthatwhiletheItemStoreisthewherethedataiskept,theItemsViewControlleristhetableview’sdataSource.)
WhentableView(_:commitEditingStyle:forRowAtIndexPath:)iscalledonthedatasource,twoextraargumentsarepassedalongwithit.ThefirstistheUITableViewCellEditingStyle,which,inthiscase,is.Delete.TheotherargumentistheNSIndexPathoftherowinthetable.
InItemsViewController.swift,implementthismethodtohavetheItemStoreremovetherightobjectandconfirmtherowdeletionbycallingthemethoddeleteRowsAtIndexPaths(_:withRowAnimation:)onthetableview.overridefunctableView(tableView:UITableView,commitEditingStyleeditingStyle:UITableViewCellEditingStyle,forRowAtIndexPathindexPath:NSIndexPath){//Ifthetableviewisaskingtocommitadeletecommand...ifeditingStyle==.Delete{letitem=itemStore.allItems[indexPath.row]//RemovetheitemfromthestoreitemStore.removeItem(item)
//AlsoremovethatrowfromthetableviewwithananimationtableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}}
Buildandrunyourapplication,createsomerows,andthendeletearow.Itwilldisappear.Noticethatswipe-to-deleteworksalso.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
MovingRowsTochangetheorderofrowsinaUITableView,youwilluseanothermethodfromtheUITableViewDataSourceprotocol–tableView(_:moveRowAtIndexPath:toIndexPath:).
Todeletearow,youhadtocallthemethoddeleteRowsAtIndexPaths(_:withRowAnimation:)ontheUITableViewtoconfirmthedeletion.Movingarow,however,doesnotrequireconfirmation:ThetableviewmovestherowonitsownauthorityandreportsthemovetoitsdatasourcebycallingthemethodtableView(_:moveRowAtIndexPath:toIndexPath:).Youimplementthismethodtoupdateyourdatasourcetomatchtheneworder.
Butbeforeyoucanimplementthismethod,youneedtogivetheItemStoreamethodtochangetheorderofitemsinitsallItemsarray.
InItemStore.swift,implementthisnewmethod.funcmoveItemAtIndex(fromIndex:Int,toIndex:Int){iffromIndex==toIndex{return}
//GetreferencetoobjectbeingmovedsoyoucanreinsertitletmovedItem=allItems[fromIndex]
//RemoveitemfromarrayallItems.removeAtIndex(fromIndex)
//InsertiteminarrayatnewlocationallItems.insert(movedItem,atIndex:toIndex)}
InItemsViewController.swift,implementtableView(_:moveRowAtIndexPath:toIndexPath:)toupdatethestore.overridefunctableView(tableView:UITableView,moveRowAtIndexPathsourceIndexPath:NSIndexPath,toIndexPathdestinationIndexPath:NSIndexPath){//UpdatethemodelitemStore.moveItemAtIndex(sourceIndexPath.row,toIndex:destinationIndexPath.row)}
Buildandrunyourapplication.TapEditandcheckoutthenewreorderingcontrols(thethreehorizontallines)onthesideofeachrow.Touchandholdareorderingcontrolandmovetherowtoanewposition(Figure10.8).
WOW! eBook www.wowebook.org
Figure10.8Movingarow
NotethatsimplyimplementingtableView(_:moveRowAtIndexPath:toIndexPath:)causedthereorderingcontrolstoappear.TheUITableViewcanaskitsdatasourceatruntimewhetheritimplementstableView(_:moveRowAtIndexPath:toIndexPath:).Ifitdoes,thenthetableviewaddsthereorderingcontrolswheneverthetableviewenterseditingmode.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DisplayingUserAlertsInthissection,youaregoingtolearnaboutuseralertsandthedifferentwaysofconfiguringanddisplayingthem.Useralertscanprovideyourapplicationwithabetteruserexperience,soyouwillusethemfairlyoften.
Alertsareoftenusedtowarnusersthatanimportantactionisabouttohappenandperhapsgivethemtheopportunitytocancelthataction.Whenyouwanttodisplayanalert,youcreateaninstanceofUIAlertControllerwithapreferredstyle.ThetwoavailablestylesareUIAlertControllerStyle.ActionSheetandUIAlertControllerStyle.Alert(Figure10.9).The.ActionSheetstyleisusedtopresenttheuserwithalistofactionsfromwhichtochoose.The.Alerttypeisusedtodisplaycriticalinformationtorequiretheusertodecidehowtoproceed.Thedistinctionmayseemsubtle,butiftheusercanbackoutofadecisionoriftheactionisnotcritical,thenan.ActionSheetisprobablythebestchoice.
Figure10.9UIAlertControllerstyles
YouaregoingtouseaUIAlertControllertoconfirmthedeletionofitems.Youwillusethe.ActionSheetstylesincethepurposeofthealertistoconfirmorcancelapossiblydestructiveaction.
WOW! eBook www.wowebook.org
OpenItemsViewController.swiftandmodifytableView(_:commitEditingStyle:forRowAtIndexPath:)toasktheusertoconfirmorcancelthedeletionofanitem.overridefunctableView(tableView:UITableView,commitEditingStyleeditingStyle:UITableViewCellEditingStyle,forRowAtIndexPathindexPath:NSIndexPath){//Ifthetableviewisaskingtocommitadeletecommand...ifeditingStyle==.Delete{letitem=itemStore.allItems[indexPath.row]
lettitle="Delete\(item.name)?"letmessage="Areyousureyouwanttodeletethisitem?"
letac=UIAlertController(title:title,message:message,preferredStyle:.ActionSheet)
//RemovetheitemfromthestoreitemStore.removeItem(item)
//AlsoremovethatrowfromthetableviewwithananimationtableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}}
Afterdeterminingthattheuserwantstodeleteanitem,youcreateaninstanceofUIAlertControllerwithanappropriatetitleandmessagedescribingwhatactionisabouttotakeplace.Also,youspecifythe.ActionSheetstyleforthealert.
TheactionsthattheusercanchoosefromwhenshownanalertareinstancesofUIAlertAction,andyoucanaddmultipleonesregardlessofthealert’sstyle.ActionsareaddedtotheUIAlertControllerusingtheaddAction(_:)method.
AddthenecessaryactionstotheactionsheetintableView(_:commitEditingStyle:forRowAtIndexPath:)....
letac=UIAlertController(title:title,message:message,preferredStyle:.ActionSheet)
letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel,handler:nil)ac.addAction(cancelAction)
letdeleteAction=UIAlertAction(title:"Delete",style:.Destructive,handler:{(action)->Voidin//Removetheitemfromthestoreself.itemStore.removeItem(item)
//Alsoremovethatrowfromthetableviewwithananimationself.tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)})ac.addAction(deleteAction)
...
Thefirstactionhasatitleof“Cancel”andiscreatedusingthe.Cancelstyle.The.Cancelstyleresultsintextinastandardbluefont.ThisactionwillallowtheusertobackoutofdeletinganItem.Thehandlerparameterallowsaclosuretobeexecutedwhenthatactionoccurs.Sincenootheractionisneeded,nilispassedastheargument.
Thesecondactionhasatitleof“Delete”andiscreatedusingthe.Destructivestyle.Sincedestructiveactionsshouldbeclearlymarkedandnoticed,the.Destructive
WOW! eBook www.wowebook.org
styleresultsinbrightredtext.Iftheuserselectsthisaction,thentheitemandthetableviewcellneedtoberemoved.Thisisalldonewithinthehandlerclosurethatispassedtotheaction’sinitializer.
Nowthattheactionshavebeenadded,thealertcontrollercanbedisplayedtotheuser.SinceUIAlertControllerisasubclassofUIViewController,youcanpresentittotheusermodally.Amodalviewcontrollertakesovertheentirescreenuntilithasfinisheditswork.
Topresentaviewcontrollermodally,youcallpresentViewController(_:animated:completion:)ontheviewcontrollerwhoseviewisonthescreen.Theviewcontrollertobepresentedispassedtoit,andthisviewcontroller’sviewtakesoverthescreen....
letdeleteAction=UIAlertAction(title:"Delete",style:.Destructive,handler:{(action)->Voidin//Removetheitemfromthestoreself.itemStore.removeItem(item)
//Alsoremovethatrowfromthetableviewwithananimationself.tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)})ac.addAction(deleteAction)
//PresentthealertcontrollerpresentViewController(ac,animated:true,completion:nil)
...
Buildandruntheapplicationanddeleteanitem.Anactionsheetwillbepresentedforyoutoconfirmthedeletion(Figure10.10).
WOW! eBook www.wowebook.org
Figure10.10Deletinganitem
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DesignPatternsAdesignpatternsolvesacommonsoftwareengineeringproblem.Designpatternsarenotactualsnippetsofcode,butinsteadareabstractideasorapproachesthatyoucanuseinyourapplications.Gooddesignpatternsarevaluableandpowerfultoolsforanydeveloper.
Theconsistentuseofdesignpatternsthroughoutthedevelopmentprocessreducesthementaloverheadinsolvingaproblemsoyoucancreatecomplexapplicationsmoreeasilyandrapidly.Herearesomeofthedesignpatternsthatyouhavealreadyused:
Delegation:Oneobjectdelegatescertainresponsibilitiestoanotherobject.YouuseddelegationwiththeUITextFieldtobeinformedwhenthecontentsofthetextfieldchange.
Datasource:Adatasourceissimilartoadelegate,butinsteadofreactingtoanotherobject,adatasourceisresponsibleforprovidingdatatoanotherobjectwhenrequested.Youusedthedatasourcepatternwithtableviews:Eachtableviewhasadatasourcethatisresponsiblefor,ataminimum,tellingthetableviewhowmanyrowstodisplayandwhichcellitshoulddisplayateachindexpath.
Model-View-Controller:Eachobjectinyourapplicationsfulfillsoneofthreeroles.Modelobjectsarethedata.Viewsdisplaytheuserinterface.Controllersprovidethegluethattiesthemodelsandviewstogether.
Target-actionpairs:Oneobjectcallsamethodonanotherobjectwhenaspecificeventoccurs.Thetargetistheobjectthathasamethodcalledonit,andtheactionisthemethodbeingcalled.Forexample,youusedtarget-actionpairswithbuttons:whenatoucheventoccurs,amethodwillbecalledonanotherobject(oftenaviewcontroller).
Appleisveryconsistentinitsuseofthesedesignpatterns,andsoitisimportanttounderstandandrecognizethem.Keepaneyeoutforthesepatternsasyoucontinuethroughthisbook!Recognizingthemwillhelpyoulearnnewclassesandframeworksmuchmoreeasily.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:RenamingtheDeleteButtonWhendeletingarow,aconfirmationbuttonappearslabeledDelete.ChangethelabelofthisbuttontoRemove.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:PreventingReorderingMakeitsothetableviewalwaysshowsafinalrowthatsaysNomoreitems!.(Thispartofthechallengeisthesameasachallengefromthelastchapter.Ifyouhavealreadydoneit,youcancopyyourcodefrombefore.)Now,makeitsothatthefinalrowcannotbemoved.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:ReallyPreventingReorderingAftercompletingthesilverchallenge,youmaynoticethateventhoughyoucannotmovetheNomoreitems!rowitself,youcanstilldragotherrowsunderneathit.Makeitsothatnomatterwhat,theNomoreitems!rowcanneverbeknockedoutofthelastposition.Finally,makeitundeletable.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
11SubclassingUITableViewCell
AUITableViewdisplaysalistofUITableViewCellobjects.Formanyapplications,thebasiccellwithitstextLabel,detailTextLabel,andimageViewissufficient.However,whenyouneedacellwithmoredetailoradifferentlayout,yousubclassUITableViewCell.
Inthischapter,youwillcreateasubclassofUITableViewCellnamedItemCellthatwilldisplayIteminstancesmoreeffectively.EachofthesecellswillshowanItem’sname,itsvalueindollars,anditsserialnumber(Figure11.1).
Figure11.1Homepwnerwithsubclassedtableviewcells
YoucustomizetheappearanceofUITableViewCellsubclassesbyaddingsubviewstoitscontentView.AddingsubviewstothecontentViewinsteadofdirectlytothecellitselfisimportantbecausethecellwillresizeitscontentViewatcertaintimes.Forexample,whenatableviewenterseditingmode,thecontentViewresizesitselftomakeroomfortheeditingcontrols(Figure11.2).IfyouaddedsubviewsdirectlytotheUITableViewCell,theseeditingcontrolswouldobscurethesubviews.Thecellcannotadjustitssizewhenenteringeditmode(itmustremainthewidthofthetableview),butthecontentViewcanresize,anditdoes.
WOW! eBook www.wowebook.org
Figure11.2Tableviewcelllayoutinstandardandeditingmode
WOW! eBook www.wowebook.org
CreatingItemCellCreateanewSwiftfilenamedItemCell.InItemCell.swift,defineItemCellasaUITableViewCellsubclass.importFoundationimportUIKit
classItemCell:UITableViewCell{
}
TheeasiestwaytoconfigureaUITableViewCellsubclassisthroughastoryboard.InChapter9,yousawthatstoryboardsfortableviewcontrollershaveaPrototypeCellssection.ThisiswhereyouwilllayoutthecontentfortheItemCell.
OpenMain.storyboardandselecttheUITableViewCellinthedocumentoutline.Openitsattributesinspector,changetheStyletoCustom,andchangetheIdentifiertoItemCell.
Nowopenitsidentityinspector(the tab).IntheClassfield,enterItemCell(Figure11.3).
Figure11.3Changingthecellclass
Changetheheightoftheprototypecelltobeabout65pointstall.YoucanchangeiteitheronthecanvasorbyselectingthetableviewcellandchangingtheRowHeightfromitssizeinspector.
AnItemCellwilldisplaythreetextelements,sodragthreeUILabelobjectsontothecell.ConfigurethemasshowninFigure11.4.Makethetextofthebottomlabelaslightlysmallerfontinalightshadeofgray.
Figure11.4ItemCell’slayout
Addconstraintstothesethreelabelsasfollows.
1. Selectthetop-leftlabelandopentheAutoLayoutPinmenu.SelectthetopandleftstrutandthenclickAdd2Constraints.
WOW! eBook www.wowebook.org
2. Youwantthebottom-leftlabeltoalwaysbealignedwiththetop-leftlabel.Control-dragfromthebottom-leftlabeltothetop-leftlabelandselectLeading.
3. Withthebottom-leftlabelstillselected,openthePinmenu,selectthebottomstrut,andthenclickAdd1Constraint.
4. SelecttherightlabelandControl-dragfromthislabeltoitssuperviewonitsrightside.SelectbothTrailingSpacetoContainerMarginandCenterVerticallyinContainer.
5. Selectthebottom-leftlabelandopenitssizeinspector.FindtheVerticalContentHuggingPriorityandloweritto250.LowertheVerticalContentCompressionResistancePriorityto749.YouwilllearnwhattheseAutoLayoutpropertiesdoinChapter12.
6. Yourframesmightbemisplaced,soopentheResolveAutoLayoutIssuesmenuandupdatetheframesforthethreelabels.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ExposingthePropertiesofItemCellForItemsViewControllertoconfigurethecontentofanItemCellintableView(_:cellForRowAtIndexPath:),thecellmusthavepropertiesthatexposethethreelabels.ThesepropertieswillbesetthroughoutletconnectionsinMain.storyboard.
Thenextstep,then,istocreateandconnectoutletsonItemCellforeachofitssubviews.
OpenItemCell.swiftandaddthreepropertiesfortheoutlets.importUIKit
classItemCell:UITableViewCell{
@IBOutletvarnameLabel:UILabel!@IBOutletvarserialNumberLabel:UILabel!@IBOutletvarvalueLabel:UILabel!
}
YouaregoingtoconnecttheoutletsforthethreeviewstotheItemCell.Whenconnectingoutletsearlierinthebook,youControl-draggedfromviewcontrollerinthestoryboardtotheappropriateview.ButtheoutletsforItemCellarenotoutletsonacontroller.Theyareoutletsonaview:thecustomUITableViewCellsubclass.
Therefore,toconnecttheoutletsforItemCell,youwillconnectthemtotheItemCell.
OpenMain.storyboard.Control-clickontheItemViewCellinthedocumentoutlineandmakethethreeoutletconnectionsshowninFigure11.5.
Figure11.5Connectingtheoutlets
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UsingItemCellLet’sgetyourcustomcellsonscreen.InItemsViewController’stableView(_:cellForRowAtIndexPath:)method,youwilldequeueaninstanceofItemCellforeveryrowinthetable.
NowthatyouareusingacustomUITableViewCellsubclass,thetableviewneedstoknowhowtalleachrowis.Thereareafewwaystoaccomplishthis,butthesimplestwayistosettherowHeightpropertyofthetableviewtoaconstantvalue.Youwillseeanotherwaylaterinthischapter.
OpenItemsViewController.swiftandupdateviewDidLoad()tosettheheightofthetableviewcells.overridefuncviewDidLoad(){super.viewDidLoad()
//GettheheightofthestatusbarletstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)tableView.contentInset=insetstableView.scrollIndicatorInsets=insets
tableView.rowHeight=65}
NowthatyouhaveregisteredtheItemCellwiththetableview(usingtheprototypecellsinthestoryboard),youcanaskthetableviewtodequeueacellwiththeidentifier“ItemCell.”
InItemsViewController.swift,modifytableView(_:cellForRowAtIndexPath:).overridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{//Getaneworrecycledcellletcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",forIndexPath:indexPath)
letcell=tableView.dequeueReusableCellWithIdentifier("ItemCell",forIndexPath:indexPath)as!ItemCell
//Setthetextonthecellwiththedescriptionoftheitem//thatisatthenthindexofitems,wheren=rowthiscell//willappearinonthetableviewletitem=itemStore.allItems[indexPath.row]
cell.textLabel?.text=item.namecell.detailTextLabel?.text="$\(item.valueInDollars)"
//ConfigurethecellwiththeItemcell.nameLabel.text=item.namecell.serialNumberLabel.text=item.serialNumbercell.valueLabel.text="$\(item.valueInDollars)"
returncell}
First,thereuseidentifierisupdatedtoreflectyournewsubclass.Thecodeattheendofthismethodisfairlyobvious–foreachlabelonthecell,setitstexttosomepropertyfromtheappropriateItem.
WOW! eBook www.wowebook.org
Buildandruntheapplication.ThenewcellsnowloadwiththeirlabelspopulatedwiththevaluesfromeachItem.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DynamicCellHeightsCurrently,thecellshaveafixedheightof65points.Itismuchbettertoallowthecontentofthecelltodriveitsheight.Thatway,ifthecontenteverchanges,thetableviewcell’sheightcanchangeautomatically.
Youcanachievethisgoal,asyouhaveprobablyguessed,withAutoLayout.TheUITableViewCellneedstohaveverticalconstraintsthatwillexactlydeterminetheheightofthecell.Currently,ItemCelldoesnothavesufficientconstraintsforthis.Youneedtoaddaconstraintbetweenthetwoleftlabelsthatfixestheverticalspacingbetweenthem.
OpenMain.storyboard.Control-dragfromthenameLabeltotheserialNumberLabelandselectVerticalSpacing.
NowopenItemsViewController.swiftandupdateviewDidLoad(_:)totellthetableviewthatitshouldcomputethecellheightsbasedontheconstraints.overridefuncviewDidLoad(){super.viewDidLoad()
//GettheheightofthestatusbarletstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)tableView.contentInset=insetstableView.scrollIndicatorInsets=insets
tableView.rowHeight=65tableView.rowHeight=UITableViewAutomaticDimensiontableView.estimatedRowHeight=65}
UITableViewAutomaticDimensionisthedefaultvalueforrowHeight,sowhileitisnotnecessarytoadd,itisusefulforunderstandingwhatisgoingon.SettingtheestimatedRowHeightpropertyonthetableviewcanimproveperformance.Insteadofaskingeachcellforitsheightwhenthetableviewloads,settingthispropertyallowssomeofthatperformancecosttobedeferreduntiltheuserstartsscrolling.
Buildandruntheapplication.Theapplicationwilllookthesameasitdidbefore.Inthenextsection,youwilllearnaboutatechnologycalledDynamicTypethatwilltakeadvantageoftheautomaticallyresizingtableviewcells.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DynamicTypeCreatinganinterfacethatappealstoeveryonecanbedaunting.Somepeopleprefermorecompactinterfacessotheycanseemoreinformationatatime.Othersmightwanttobeabletoeasilyseeinformationataglance,orperhapstheyhavepooreyesight.Inshort:peoplehavedifferentneeds.Gooddevelopersstrivetomakeappsthatmeetthoseneeds.
DynamicTypeisatechnologythathelpsrealizethisgoalbyprovidingspecificallydesignedtextstylesthatareoptimizedforlegibility.UserscanselectoneofsevenpreferredtextsizesfromwithinApple’sSettingsapplication(plusafewadditionallargersizesfromwithintheAccessibilitysection),andappsthatsupportDynamicTypewillhavetheirfontsscaledappropriately.Inthissection,youwillupdateItemCelltosupportDynamicType.Figure11.6showstheapplicationrenderedatthesmallestandlargestuser-selectableDynamicTypesizes.
Figure11.6ItemCellwithDynamicTypesupported
TheDynamicTypesystemiscenteredaroundtextstyles.Whenafontisrequestedforagiventextstyle,thesystemwillconsidertheuser’spreferredtextsizeinassociationwiththetextstyletoreturnanappropriatelyconfiguredfont.Figure11.7showsthesixdifferenttextstyles.
WOW! eBook www.wowebook.org
Figure11.7Differenttextstyles
OpenMain.storyboard.Let’supdatethelabelstousethetextstylesinsteadoffixedfonts.SelectthenameLabelandvalueLabelandopentheattributesinspector.ClickonthetexticontotherightofFont.ForFont,chooseTextStyles-Body(Figure11.8).RepeatthesamestepsfortheserialNumberLabel,choosingtheCaption1textstyle.
Figure11.8Changingthetextstyle
Nowlet’schangethepreferredfontsize.YoudothisthroughtheSettingsapplication.
Buildandruntheapplication.PresstheHomebutton(oruseHomefromtheHardware
WOW! eBook www.wowebook.org
menu)andopenApple’sSettingsapplication.UnderGeneral,selectAccessibilityandthenLargerText.(Onanactualdevice,thismenuisaccessedinSettingsunderDisplay&BrightnessandthenTextSize.)Dragthesliderallthewaytothelefttosetthefontsizetothesmallestvalue(Figure11.9).PresstheHomebuttonagaintosavethesechanges.
Figure11.9Textsizesettings
Buildandruntheapplication.(Ifyouswitchbacktotheapplication,eitherusingthetaskswitcherorthroughtheHomescreen,youwillnotseethechanges.Youwillfixthatinthenextsection.)Addsomeitemstothetableviewandyouwillseethenewsmallerfontsizesinaction.
Respondingtouserchanges
Whentheuserchangesthepreferredtextsizeandreturnstotheapplication,thetableviewwillgetreloaded.Unfortunately,thelabelswillnotknowaboutthenewpreferredtext
WOW! eBook www.wowebook.org
size.Tofixthis,youneedtoupdatethelabelsmanually.
OpenItemCell.swiftandaddanewmethodthatupdatesallthreelabels.funcupdateLabels(){letbodyFont=UIFont.preferredFontForTextStyle(UIFontTextStyleBody)nameLabel.font=bodyFontvalueLabel.font=bodyFont
letcaption1Font=UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)serialNumberLabel.font=caption1Font}
NowopenItemsViewController.swiftandcallthismethodintableView(_:cellForRowAtIndexPath:).overridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{//Getaneworrecycledcellletcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",forIndexPath:indexPath)as!ItemCell
//Updatethelabelsforthenewpreferredtextsizecell.updateLabels()
letitem=itemStore.allItems[indexPath.row]
cell.nameLabel.text=item.namecell.serialNumberLabel.text=item.serialNumbercell.valueLabel.text="$\(item.valueInDollars)"
returncell}
Buildandruntheapplication.GointoSettingsandchangethepreferredreadingsizetothelargestsize.Unlikebefore,youcannowswitchbacktoHomepwner,eitherbyopeningthetaskswitcherorthroughtheHomescreen,andthetableviewwillupdatetoreflectthenewpreferredtextsize.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:CellColorsUpdatetheItemCelltodisplaythevalueInDollarsingreenifthevalueislessthan50andredifthevalueisgreaterthanorequalto50.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
12StackViews
YouhavebeenusingAutoLayoutthroughoutthisbooktocreateflexibleinterfacesthatscaleacrossdevicetypesandsizes.AutoLayoutisaverypowerfultechnology,butwiththatpowercomescomplexity.Layingoutaninterfacewelloftenneedsalotofconstraints,anditcanbedifficulttocreatedynamicinterfacesduetotheneedtoconstantlyaddandremoveconstraints.
Often,aninterface(orasubsectionoftheinterface)canbelaidoutinalinearfashion.Let’sthinkaboutsomeoftheotherapplicationsyouhavewritteninthisbook.TheQuizapplicationthatyouwroteinChapter1consistedoffoursubviewsthatwerelaidoutvertically.ThesameistruefortheWorldTrotterapplicationthatyouwrote:theConversionViewControllerhadaverticalinterfaceconsistingofatextfieldandafewlabels.
Interfacesthathavealinearlayoutaregreatcandidatesforusingastackview.AstackviewisaninstanceofUIStackViewthatallowsyoutocreateaverticalorhorizontallayoutthatiseasytolayoutandmanagesmostoftheconstraintsthatyouwouldtypicallyhavetomanageyourself.Perhapsbestofall,youareabletoneststackviewswithinotherstackviews,whichallowsyoutocreatetrulyamazinginterfacesinafractionofthetime.
Inthischapter,youaregoingtocontinueworkingonHomepwnertocreateaninterfacefordisplayingthedetailsofaspecificItem.Theinterfacethatyoucreatewillconsistofmultiplenestedstackviews,bothverticalandhorizontal(Figure12.1).
WOW! eBook www.wowebook.org
Figure12.1Homepwnerwithstackviews
WOW! eBook www.wowebook.org
UsingUIStackViewYouaregoingtocreateaninterfaceforeditingthedetailsforanItem.Youwillgetthebasicinterfaceworkinginthischapter,andthenyouwillfinishimplementingthedetailsinChapter13.
Atthetoplevel,youwillhaveaverticalstackviewwithfourelementsdisplayingtheitem’sname,serialnumber,value,anddatecreated(Figure12.2).
Figure12.2Verticalstackviewlayout
OpenyourHomepwnerprojectandthenopenMain.storyboard.DraganewViewControllerfromtheobjectlibraryontothecanvas.DragaVerticalStackViewfromtheobjectlibraryontotheviewfortheViewController.Addconstraintstothestackviewtopinittotheleadingandtrailingmargins,andpinthetopandbottomedgestobe8pointsfromthetopandbottomlayoutguides.
NowdragfourinstancesofUILabelfromtheobjectlibraryontothestackview.Fromtoptobottom,givetheselabelsthetext“Name,”“Serial,”“Value,”and“DateCreated.”(Figure12.3).
WOW! eBook www.wowebook.org
Figure12.3Labelsaddedtothestackview
Youcanseeaproblemrightaway:thelabelsallhavearedborder(indicatinganAutoLayoutproblem)andthereisawarningthatsomeviewsareverticallyambiguous.Therearetwowaysyoucanfixthisissue:byusingAutoLayout,orbyusingapropertyonthestackview.Let’sworkthroughtheAutoLayoutsolutionfirstbecauseithighlightsanimportantaspectofAutoLayout.
Implicitconstraints
YoulearnedinChapter3thateveryviewhasanintrinsiccontentsize.Youalsolearnedthatifyoudonotspecifyconstraintsthatexplicitlydeterminethewidthorheight,theviewwillderiveitswidthorheightfromitsintrinsiccontentsize.Howdoesthiswork?
Itdoesthisusingimplicitconstraintsderivedfromaview’scontenthuggingprioritiesanditscontentcompressionresistancepriorities.Aviewhasoneoftheseprioritiesforeachaxis:
horizontalcontenthuggingpriority
WOW! eBook www.wowebook.org
verticalcontenthuggingpriority
horizontalcontentcompressionresistancepriority
verticalcontentcompressionresistancepriority
Contenthuggingpriorities
Thecontenthuggingpriorityislikearubberbandthatisplacedaroundaview.Therubberbandmakestheviewnotwanttobebiggerthanitsintrinsiccontentsizeinthatdimension.Eachpriorityisassociatedwithavaluefrom0to1000.Avalueof1000meansthataviewcannotgetbiggerthanitsintrinsiccontentsizeonthatdimension.
Let’slookatanexamplewithjustthehorizontaldimension.Sayyouhavetwolabelsnexttooneanotherwithconstraintsbothbetweenthetwoviewsandbetweeneachviewanditssuperview,asshowninFigure12.4.
Figure12.4Twolabelssidebyside
Thisworksgreatuntilthesuperviewbecomeswider.Atthatpoint,whichlabelshouldbecomewider?Thefirstlabel,thesecondlabel,orboth?AsFigure12.5shows,theinterfaceiscurrentlyambiguous.
Figure12.5Ambiguouslayout
Thisiswherethecontenthuggingprioritybecomesrelevant.Theviewwiththehighercontenthuggingpriorityistheonethatdoesnotstretch.Youcanthinkaboutthepriorityvalueasthe“strength”oftherubberband.Thehigherthepriorityvalue,thestrongerthe
WOW! eBook www.wowebook.org
rubberband,andthemoreitwantstohugtoitsintrinsiccontentsize.
Contentcompressionresistancepriorities
Thecontentcompressionresistanceprioritiesdeterminehowmuchaviewresistsgettingsmallerthanitsintrinsiccontentsize.ConsiderthesametwolabelsfromFigure12.4.Whatwouldhappenifthesuperview’swidthdecreased?Oneofthelabelswouldneedtotruncateitstext(Figure12.6).Butwhichone?
Figure12.6Compressedambiguouslayout
Theviewwiththegreatercontentcompressionresistancepriorityistheonethatwillresistcompressionand,therefore,nottruncateitstext.
Withthisknowledge,youcannowfixtheproblemwiththestackview.
SelecttheDateCreatedlabelandopenitssizeinspector.FindtheVerticalContentHuggingPriorityandloweritto249.Nowtheotherthreelabelshaveahighercontenthuggingpriority,sotheywillallhugtotheirintrinsiccontentheight.TheDateCreatedlabelwillstretchtofillintheremainingspace.
Stackviewdistribution
Let’stakealookatanotherwayofsolvingtheproblem.Stackviewshaveanumberofpropertiesthatdeterminehowtheircontentislaidout.
Selectthestackview,eitheronthecanvasorusingthedocumentoutline.OpenitsattributesinspectorandfindthesectionatthetoplabeledStackView.OneofthepropertiesthatdetermineshowthecontentislaidoutistheDistributionproperty.CurrentlyitissettoFill,whichletstheviewslayouttheircontentbasedontheirintrinsiccontentsize.ChangethevaluetoFillEqually.Thiswillresizethelabelssothattheyallhavethesameheight,ignoringtheintrinsiccontentsize(Figure12.7).Besuretoreadthedocumentationfortheotherdistributionvaluesthatastackviewcanhave.
WOW! eBook www.wowebook.org
Figure12.7Stackviewsettofillequally
ChangetheDistributionofthestackviewbacktoFill;thisisthevalueyouwillwantgoingforwardinthischapter.
Nestedstackviews
Oneofthemostpowerfulfeaturesofstackviewsisthattheycanbenestedwithinoneanother.Youwillusethistonesthorizontalstackviewswithinthelargerverticalstackview.ThetopthreelabelswillhaveatextfieldnexttothemthatdisplaysthecorrespondingvaluefortheItemandwillalsoallowtheusertoeditthatvalue.
SelecttheNamelabelonthecanvas.Clicktheleft-mosticonintheAutoLayoutconstraintsmenu: .Thiswillembedtheselectedviewinastackview.
Selectthenewstackviewandopenitsattributesinspector.Thestackviewiscurrentlyaverticalstackview,butyouwantittobeahorizontalstackview.ChangetheAxistoHorizontal.
NowdragaTextFieldfromtheobjectlibrarytotherightoftheNamelabel.Sincelabels,by
WOW! eBook www.wowebook.org
default,haveagreatercontenthuggingprioritythantextfields,thelabelhugstoitsintrinsiccontentwidthandthetextfieldstretches.Thelabelandthetextfieldcurrentlyhavethesamecontentcompressionresistancepriorities,whichwouldresultinanambiguouslayoutifthetextfield’stextwastoolong.OpenthesizeinspectorforthetextfieldandsetitsHorizontalContentCompressionResistancePriorityto749.
Stackviewspacing
Thelabelandtextfieldlookalittlesquishedsincethereisnospacingbetweenthem.Stackviewsallowyoutocustomizethespacingbetweenitems.
Selectthehorizontalstackviewandopenitsattributesinspector.ChangetheSpacingtobe8points.Noticethatthetextfieldshrinkstoaccommodatethespacing,becauseitislessresistanttocompressionthanthelabel.
RepeatthesesamestepsfortheSerialandValuelabels.
1. Selectthelabelandclickthe icon.
2. Changethestackviewtobeahorizontalstackview.
3. Dragatextfieldontothehorizontalstackviewandchangeitshorizontalcontentcompressionresistanceprioritytobe749.
4. Updatethestackviewtohaveaspacingof8points.
Thereareacoupleofothertweaksyouwillwanttomaketotheinterface.Theverticalstackviewneedssomespacing.TheDateCreatedlabelshouldhaveacentertextalignment.AndtheName,Serial,andValuelabelsshouldbethesamewidth.
Selecttheverticalstack,openitsattributesinspector,andupdatetheSpacingtobe8points.ThenselecttheDateCreatedlabel,openitsattributesinspector,andchangetheAlignmenttobecentered.Thatsolvesthefirsttwoissues.
Althoughstackviewssubstantiallyreducethenumberofconstraintsthatyouneedtoaddtoyourinterface,someconstraintsarestillimportant.Withtheinterfaceasis,thetextfieldsdonotalignontheirleadingedgeduetothedifferenceinthewidthsofthelabels.(ThedifferenceisnotverynoticeableinEnglish,butitbecomesmorepronouncedwhenlocalizedintootherlanguages.)Tosolvethis,youwilladdleadingedgeconstraintsbetweenthethreetextfields.
Control-dragfromtheNametextfieldtotheSerialtextfieldandselectLeading.ThendothesamefortheSerialtextfieldandtheValuetextfield.ThecompletedinterfacewilllooklikeFigure12.8.
WOW! eBook www.wowebook.org
Figure12.8Finalstackviewinterface
Stackviewsallowyoutocreateveryrichinterfacesinafractionofthetimeitwouldtaketoconfigurethemmanuallyusingconstraints.Constraintsarestilladded,buttheyarebeingmanagedbythestackviewitselfinsteadofbyyou.Stackviewsallowyoutohaveverydynamicinterfacesatruntime.YoucanaddandremoveviewsfromstackviewsbyusingaddArrangedSubview(_:),insertArrangedSubview(_:atIndex:),andremoveArrangedSubview(_:).Youcanalsotogglethehiddenpropertyonaviewinastackview.Thestackviewwillautomaticallylayoutitscontenttoreflectthatvalue.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SeguesMostiOSapplicationshaveanumberofviewcontrollersthatusersnavigatebetween.Storyboardsallowyoutosetuptheseinteractionsassegueswithouthavingtowritecode.
Aseguemovesanotherviewcontroller’sviewontothescreenandisrepresentedbyaninstanceofUIStoryboardSegue.Eachseguehasastyle,anactionitem,andanidentifier.Thestyleofaseguedetermineshowtheviewcontrollerwillbepresented.Theactionitemistheviewobjectinthestoryboardfilethattriggersthesegue,likeabutton,atableviewcell,orsomeotherUIControl.Theidentifierisusedtoprogrammaticallyaccessthesegue.Thisisusefulwhenyouwanttotriggeraseguethatdoesnotcomefromanactionitem,likeashakeorsomeotherinterfaceelementthatcannotbesetupinthestoryboardfile.
Let’sstartwithashowsegue.Ashowseguedisplaysaviewcontrollerdependingonthecontextinwhichitisdisplayed.Theseguewillbebetweenthetableviewcontrollerandthenewviewcontroller.Theactionitemswillbethetableview’scells;tappingacellwillshowtheviewcontrollermodally.
InMain.storyboard,selecttheItemCellprototypecellontheItemsViewController.Control-dragfromthecelltothenewviewcontrollerthatyousetupintheprevioussection.(MakesureyouareControl-draggingfromthecellandnotthetableview!)Ablackpanelwillappearthatliststhepossiblestylesforthissegue.SelectShowfromtheSelectionSeguesection(Figure12.9).
Figure12.9Settingupashowsegue
Noticethearrowthatgoesfromthetableviewcontrollertothenewviewcontroller.Thisisasegue.Theiconinthecircletellsyouthatthissegueisashowsegue–eachseguehas
WOW! eBook www.wowebook.org
auniqueicon.
Buildandruntheapplication.Tapacellandthenewviewcontrollerwillslideupfromthebottomofthescreen.(Slidingupfromthebottomisthedefaultbehaviorwhenpresentingaviewcontrollermodally.Thiscanbecustomized,however,asyousawinChapter10whenyoupresentedtheUIAlertControllermodally.)
Sofar,sogood!Buttherearetwoproblemsatthemoment:theviewcontrollerisnotdisplayingtheinformationfortheItemthatwasselected,andthereisnowaytodismisstheviewcontrollertoreturntotheItemsViewController.Youwillfixthefirstissueinthenextsection,andyouwillfixthesecondissueinChapter13.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
HookingUptheContentTodisplaytheinformationfortheselectedItem,youwillneedtocreateanewUIViewControllersubclass.
CreateanewSwiftfileandnameitDetailViewController.OpenDetailViewController.swiftanddeclareanewUIViewControllersubclassnamedDetailViewController.importFoundationimportUIKit
classDetailViewController:UIViewController{
}
Becauseyouneedtobeabletoaccessthesesubviewsduringruntime,DetailViewControllerneedsoutletsforthem.TheplanistoaddfournewoutletstoDetailViewControllerandthenmaketheconnections.Inpreviousexercises,youdidthisintwodistinctsteps:first,youaddedtheoutletsintheSwiftfile,andsecond,youmadeconnectionsinthestoryboardfile.Youcandobothatonceusingtheassistanteditor.
WithDetailViewController.swiftopen,Option-clickonMain.storyboardintheprojectnavigator.ThiswillopenthefileintheassistanteditorrightnexttoDetailViewController.swift.(YoucantoggletheassistanteditorbyclickingthemiddlebuttonfromtheEditorcontrolatthetopoftheworkspace.TheshortcuttodisplaytheassistanteditorisCommand-Option-Return,andtheshortcuttoreturntothestandardeditorisCommand-Return.)
Yourwindowhasbecomealittlecluttered.Let’smakesometemporaryspace.HidethenavigatorareabyclickingtheleftbuttonintheViewcontrolatthetopoftheworkspace(theshortcutforthisisCommand-0).Then,hidethedocumentoutlineinInterfaceBuilderbyclickingthetogglebuttoninthelowerleftcorneroftheeditor.YourworkspaceshouldnowlooklikeFigure12.10.
WOW! eBook www.wowebook.org
Figure12.10Layingouttheworkspace
Beforeyouconnecttheoutlets,youneedtotellthedetailinterfacethatitshouldbeassociatedwiththeDetailViewController.SelecttheViewControlleronthecanvasandopenitsidentityinspector.ChangetheClasstobeDetailViewController(Figure12.11).
Figure12.11Settingtheviewcontrollerclass
ThethreeinstancesofUITextFieldandbottominstanceofUILabelwillbeoutletsinDetailViewController.Control-dragfromtheUITextFieldnexttotheNamelabeltothetopofDetailViewController.swift,asshowninFigure12.12.
WOW! eBook www.wowebook.org
Figure12.12Draggingfromstoryboardtosourcefile
Letgoandapop-upwindowwillappear.EnternameFieldintotheNamefield,selectStrongfromtheStoragepop-upmenu,andclickConnect(Figure12.13).
Figure12.13Autogeneratinganoutletandmakingaconnection
Thiswillcreatean@IBOutletpropertyoftypeUITextFieldnamednameFieldinDetailViewController.
Inaddition,thisUITextFieldisnowconnectedtothenameFieldoutletoftheDetailViewController.YoucanverifythisbyControl-clickingontheDetailViewControllertoseetheconnections.AlsonoticethathoveringyourmouseabovethenameFieldconnectioninthepanelthatappearswillrevealtheUITextFieldthatyouconnected.Twobirds,onestone.
CreatetheotherthreeoutletsthesamewayandnamethemasshowninFigure12.14.
WOW! eBook www.wowebook.org
Figure12.14Connectiondiagram
Aftermakingtheconnections,DetailViewController.swiftshouldlooklikethis:importUIKit
classDetailViewController:UIViewController{
@IBOutletvarnameField:UITextField!@IBOutletvarserialNumberField:UITextField!@IBOutletvarvalueField:UITextField!@IBOutletvardateLabel:UILabel!
}
Ifyourfilelooksdifferent,thenyouroutletsarenotconnectedcorrectly.Fixanydisparitiesbetweenyourfileandthecodeshownaboveinthreesteps:First,gothroughtheControl-dragprocessandmakeconnectionsagainuntilyouhavethefourlinesshownaboveinyourDetailViewController.swift.Second,removeanywrongcode(likenon-propertymethoddeclarationsorproperties)thatgotcreated.Finally,checkforanybadconnectionsinthestoryboardfile.InMain.storyboard,Control-clickontheDetailViewController.Ifthereareyellowwarningsignsnexttoanyconnection,clickthexiconnexttothoseconnectionstodisconnectthem.
Itisimportanttoensurethattherearenobadconnectionsinaninterfacefile.Abadconnectiontypicallyhappenswhenyouchangethenameofapropertybutdonotupdatetheconnectionintheinterfacefile.Or,youcompletelyremoveapropertybutdonotremoveitfromtheinterfacefile.Eitherway,abadconnectionwillcauseyourapplicationtocrashwhentheinterfacefileisloaded.
WOW! eBook www.wowebook.org
Withtheconnectionsmade,youcanclosetheassistanteditorandreturntoviewingjustDetailViewController.swift.
DetailViewControllerwillholdontoareferencetotheItemthatisbeingdisplayed.Whenitsviewisloaded,youwillsetthetextoneachtextfieldtotheappropriatevaluefromtheIteminstance.
InDetailViewController.swift,addapropertyforanIteminstanceandoverrideviewWillAppear(_:)tosetuptheinterface.classDetailViewController:UIViewController{
@IBOutletvarnameField:UITextField!@IBOutletvarserialNumberField:UITextField!@IBOutletvarvalueField:UITextField!@IBOutletvardateLabel:UILabel!
varitem:Item!
overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
nameField.text=item.nameserialNumberField.text=item.serialNumbervalueField.text="\(item.valueInDollars)"dateLabel.text="\(item.dateCreated)"}}
InsteadofusingstringinterpolationtoprintoutthevalueInDollarsanddateCreated,itwouldbebettertouseaformatter.YouusedaninstanceofNSNumberFormatterinChapter4.Youwilluseanotheronehere,aswellasaninstanceofNSDateFormattertoformatthedateCreated.
AddaninstanceofNSNumberFormatterandNSDateFormattertotheDetailViewController.UsetheseformattersinviewWillAppear(_:)toformatthevalueInDollarsanddateCreated.varitem:Item!
letnumberFormatter:NSNumberFormatter={letformatter=NSNumberFormatter()formatter.numberStyle=.DecimalStyleformatter.minimumFractionDigits=2formatter.maximumFractionDigits=2returnformatter}()
letdateFormatter:NSDateFormatter={letformatter=NSDateFormatter()formatter.dateStyle=.MediumStyleformatter.timeStyle=.NoStylereturnformatter}()
overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
nameField.text=item.nameserialNumberField.text=item.serialNumbervalueField.text="\(item.valueInDollars)"dateLabel.text="\(item.dateCreated)"valueField.text=numberFormatter.stringFromNumber(item.valueInDollars)dateLabel.text=dateFormatter.stringFromDate(item.dateCreated)}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
PassingDataAroundWhenarowinthetableviewistapped,youneedawayoftellingtheDetailViewControllerwhichitemwasselected.Wheneverasegueistriggered,theprepareForSegue(_:sender:)methodiscalledontheviewcontrollerinitiatingthesegue.Thismethodhastwoarguments:theUIStoryboardSegue,whichgivesyouinformationaboutwhichsegueishappening,andthesender,whichistheobjectthattriggeredthesegue(aUITableViewCelloraUIButton,forexample).
TheUIStoryboardSeguegivesyouthreepiecesofinformation:thesourceviewcontroller(wherethesegueoriginates),thedestinationviewcontroller(wherethesegueends),andtheidentifierofthesegue.Theidentifierletsyoudifferentiatesegues.Let’sgivethesegueausefulidentifier.
OpenMain.storyboardagain.Selecttheshowseguebyclickingonthearrowbetweenthetwoviewcontrollersandopentheattributesinspector.Fortheidentifier,enterShowItem(Figure12.15).
Figure12.15Segueidentifier
Withyoursegueidentified,youcannowpassyourIteminstancesaround.OpenItemsViewController.swiftandimplementprepareForSegue(_:sender:).overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){//Ifthetriggeredsegueisthe"ShowItem"segueifsegue.identifier=="ShowItem"{
//Figureoutwhichrowwasjusttappedifletrow=tableView.indexPathForSelectedRow?.row{
//Gettheitemassociatedwiththisrowandpassitalongletitem=itemStore.allItems[row]letdetailViewController=segue.destinationViewControlleras!DetailViewControllerdetailViewController.item=item}}}
Buildandruntheapplication.TaponarowandtheDetailViewControllerwill
WOW! eBook www.wowebook.org
slideonscreen,displayingthedetailsforthatitem.YouwillfixtheinabilitytogobacktotheItemsViewControllerinChapter13.
ManyprogrammersnewtoiOSstrugglewithhowdataispassedbetweenviewcontrollers.HavingallofthedataintherootviewcontrollerandpassingsubsetsofthatdatatothenextUIViewController(likeyoudid)isacleanandefficientwayofperformingthistask.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:MoreStackViewsQuizandWorldTrotteraregoodcandidatesforusingstackviews.UpdatebothoftheseapplicationstouseUIStackView.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
13UINavigationController
InChapter5,youlearnedaboutUITabBarControllerandhowitallowsausertoaccessdifferentscreens.Atabbarcontrollerisgreatforscreensthatareindependentofeachother,butwhatifyouhavescreensthatproviderelatedinformation?
Forexample,theSettingsapplicationhasmultiplerelatedscreensofinformation:alistofsettings(likeSounds),adetailedpageforeachsetting,andaselectionpageforeachdetail(Figure13.1).Thistypeofinterfaceiscalledadrill-downinterface.
Figure13.1Drill-downinterfaceinSettings
Inthischapter,youwilluseaUINavigationControllertoaddadrill-downinterfacetoHomepwnerthatletstheuserseeandeditthedetailsofanItem.ThesedetailswillbepresentedbytheDetailViewControllerthatyoucreatedinChapter12(Figure13.2).
WOW! eBook www.wowebook.org
Figure13.2HomepwnerwithUINavigationController
WOW! eBook www.wowebook.org
UINavigationControllerAUINavigationControllermaintainsanarrayofviewcontrollerspresentingrelatedinformationinastack.WhenaUIViewControllerisontopofthestack,itsviewisvisible.
WhenyouinitializeaninstanceofUINavigationController,yougiveitaUIViewController.ThisUIViewControllerisaddedtothenavigationcontroller’sviewControllersarrayandbecomesthenavigationcontroller’srootviewcontroller.Therootviewcontrollerisalwaysonthebottomofthestack.(Notethatwhilethisviewcontrollerisreferredtoasthenavigationcontroller’s“rootviewcontroller,”UINavigationControllerdoesnothavearootViewControllerproperty.)
MoreviewcontrollerscanbepushedontopoftheUINavigationController’sstackwhiletheapplicationisrunning.TheseviewcontrollersareaddedtotheendoftheviewControllersarraythatcorrespondstothetopofthestack.UINavigationController’stopViewControllerpropertykeepsareferencetotheviewcontrolleratthetopofthestack.
Whenaviewcontrollerispushedontothestack,itsviewslidesontothescreenfromtheright.Whenthestackispopped(i.e.,thelastitemisremoved),thetopviewcontrollerisremovedfromthestackanditsviewslidesofftotheright,exposingtheviewofnextviewcontrolleronthestack,whichbecomesthetopviewcontroller.Figure13.3showsanavigationcontrollerwithtwoviewcontrollers.TheviewofthetopViewControlleriswhattheusersees.
Figure13.3UINavigationController’sstack
WOW! eBook www.wowebook.org
UINavigationControllerisasubclassofUIViewController,soithasaviewofitsown.Itsviewalwayshastwosubviews:aUINavigationBarandtheviewoftopViewController(Figure13.4).
Figure13.4AUINavigationController’sview
Inthischapter,youwilladdaUINavigationControllertotheHomepwnerapplicationandmaketheItemsViewControllertheUINavigationController’srootviewcontroller.TheDetailViewControllerwillbepushedontotheUINavigationController’sstackwhenanItemisselected.ThisviewcontrollerwillallowtheusertoviewandeditthepropertiesofanItemselectedfromthetableviewofItemsViewController.TheobjectdiagramfortheupdatedHomepwnerapplicationisshowninFigure13.5.
WOW! eBook www.wowebook.org
Figure13.5Homepwnerobjectdiagram
Thisapplicationisgettingfairlylarge,asyoucansee.Fortunately,viewcontrollersandUINavigationControllerknowhowtodealwiththistypeofcomplicatedobjectdiagram.WhenwritingiOSapplications,itisimportanttotreateachUIViewControllerasitsownlittleworld.ThestuffthathasalreadybeenimplementedinCocoaTouchwilldotheheavylifting.
BeginbygivingHomepwneranavigationcontroller.ReopentheHomepwnerproject.TheonlyrequirementsforusingaUINavigationControllerarethatyougiveitarootviewcontrollerandadditsviewtothewindow.
OpenMain.storyboardandselecttheItemsViewController.Then,fromtheEditormenu,chooseEmbedIn→NavigationController.ThiswillsettheItemsViewControllertobetherootviewcontrollerofaUINavigationController.ItwillalsoupdatethestoryboardtosettheNavigationControllerastheinitialviewcontroller.
SelecttheDetailViewController,andyouwillseethatitsinterfacehasmisplacedviewsnow.Thestackviewhasaconstrainttothetoplayoutguide,butwhenaviewcontrollerisembeddedwithinanavigationcontroller,thebottomofthetoplayoutguidecorrespondstothebottomofthenavigationbar(notthebottomofthestatusbar).WiththeDetailView
WOW! eBook www.wowebook.org
Controllerselected,opentheResolveAutoLayoutIssuespop-up.SelectUpdateFramesfromthesectionAllViewsinDetailViewControllerandthestackviewwillrepositionitself.
Buildandruntheapplicationand…theapplicationcrashes.Whatishappening?YoupreviouslycreatedacontractwiththeAppDelegatethataninstanceofItemsViewControllerwouldbetherootViewControllerofthewindow:letitemsController=window!.rootViewControlleras!ItemsViewController
YouhavenowbrokenthiscontractbyembeddingtheItemsViewControllerwithinaUINavigationController.Youneedtoupdatethecontract.
OpenAppDelegate.swiftandupdateapplication(_:didFinishLaunchingWithOptions:)toreflectthenewviewcontrollerhierarchy.funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{//Overridepointforcustomizationafterapplicationlaunch
letitemStore=ItemStore()
//AccesstheItemsViewControllerandsetitsitemstoreletitemsController=window!.rootViewControlleras!ItemsViewControllerletnavController=window!.rootViewControlleras!UINavigationControllerletitemsController=navController.topViewControlleras!ItemsViewControlleritemsController.itemStore=itemStore
returntrue}
Buildandruntheapplicationagain.Homepwnerworksagainandhasaverynice,iftotallyempty,UINavigationBaratthetopofthescreen(Figure13.6).NoticehowthescreenadjustedtofitItemsViewController’sviewaswellasthenewnavigationbar.UINavigationControllerdidthisforyou.
Figure13.6Homepwnerwithanemptynavigationbar
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
NavigatingwithUINavigationControllerWiththeapplicationstillrunning,createanewitemandselectthatrowfromtheUITableView.NotonlyareyoutakentoDetailViewController’sview,butyoualsogetafreeanimationandaBackbuttonintheUINavigationBar.TapthisbuttontogetbacktoItemsViewController.
NoticethatyoudidnothavetochangetheshowseguethatyoucreatedinChapter12togetthisbehavior.Asmentionedinthatchapter,theshowseguepresentsthedestinationviewcontrollerinawaythatmakessensegiventhesurroundingcontext.Whenashowsegueistriggeredfromaviewcontrollerembeddedwithinanavigationcontroller,thedestinationviewcontrollerispushedontothenavigationcontroller’sviewcontrollerstack.
SincetheUINavigationController’sstackisanarray,itwilltakeownershipofanyviewcontrolleraddedtoit.Thus,theDetailViewControllerisownedonlybytheUINavigationControlleraftertheseguefinishes.Whenthestackispopped,theDetailViewControllerisdestroyed.Thenexttimearowistapped,anewinstanceofDetailViewControlleriscreated.
Havingaviewcontrollerpushthenextviewcontrollerisacommonpattern.Therootviewcontrollertypicallycreatesthenextviewcontroller,andthenextviewcontrollercreatestheoneafterthat,andsoon.Someapplicationsmayhaveviewcontrollersthatcanpushdifferentviewcontrollersdependingonuserinput.Forexample,thePhotosapppushesavideoviewcontrolleroranimageviewcontrollerontothenavigationstackdependingonwhattypeofmediaisselected.
Buildandrunyourapplication.CreateanewitemandselectthatrowintheUITableView.TheviewthatappearswillcontaintheinformationfortheselectedItem.Whileyoucaneditthisdata,theUITableViewwillnotreflectthosechangeswhenyoureturntoit.Tofixthisproblem,youneedtoimplementcodetoupdatethepropertiesoftheItembeingedited.Inthenextsection,youwillseewhentodothis.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AppearingandDisappearingViewsWheneveraUINavigationControllerisabouttoswapviews,itcallstwomethods:viewWillDisappear(_:)andviewWillAppear(_:).TheUIViewControllerthatisabouttobepoppedoffthestackhasviewWillDisappear(_:)called.TheUIViewControllerthatwillthenbeontopofthestackhasviewWillAppear(_:)calledonit.
Toholdontochangesinthedata,whenaDetailViewControllerispoppedoffthestackyouwillsetthepropertiesofitsitemtothecontentsofthetextfields.Whenimplementingthesemethodsforviewsappearinganddisappearing,itisimportanttocallthesuperclass’simplementation–itmighthavesomeworktodoandneedstobegiventhechancetodoit.InDetailViewController.swift,implementviewWillDisappear(_:).overridefuncviewWillDisappear(animated:Bool){super.viewWillDisappear(animated)
//"Save"changestoitemitem.name=nameField.text??""item.serialNumber=serialNumberField.text
ifletvalueText=valueField.text,value=numberFormatter.numberFromString(valueText){item.valueInDollars=value.integerValue}else{item.valueInDollars=0}}
NowthevaluesoftheItemwillbeupdatedwhentheusertapstheBackbuttonontheUINavigationBar.WhenItemsViewControllerappearsbackonthescreen,themethodviewWillAppear(_:)iscalled.TakethisopportunitytoreloadtheUITableViewsotheusercanimmediatelyseethechanges.
InItemsViewController.swift,overrideviewWillAppear(_:)toreloadthetableview.overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
tableView.reloadData()}
Buildandrunyourapplicationonceagain.Nowyoucanmovebackandforthbetweentheviewcontrollersthatyoucreatedandchangethedatawithease.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DismissingtheKeyboardRuntheapplication,addanitem,andtouchthetextfieldwiththeitem’sname.Whenyoutouchthetextfield,akeyboardappearsonscreen,asyousawinyourWorldTrotterappinChapter4(Figure13.7).(Ifyouareusingthesimulatorandthekeyboarddoesnotappear,rememberthatyoucanpressCommand-Ktotogglethedevicekeyboard.)
Figure13.7Keyboardappearswhenatextfieldistouched
TheappearanceofthekeyboardinresponsetoatouchisbuiltintotheUITextFieldclassaswellasUITextView,soyoudonothavetodoanythingextraforthekeyboardtoappear.However,attimesyouwillwanttomakesurethekeyboardbehavesasyouwantitto.
Forexample,thekeyboardcoversmorethanathirdofthescreen.Rightnow,itdoesnotobscureanything,butsoonyouwilladdmoredetailsthatextendtothebottomofthe
WOW! eBook www.wowebook.org
screen,anduserswillwantawaytohidethekeyboardwhenitisnotneeded.Inthissection,youaregoingtogivetheusertwowaystodismissthekeyboard:pressingthekeyboard’sReturnkeyandtappinganywhereelseonthedetailviewcontroller’sview.Butfirst,let’slookatthecombinationofeventsthatmaketexteditingpossible.
Eventhandlingbasics
Whenyoutouchaview,aneventiscreated.Thisevent(knownasa“touchevent”)istiedtoaspecificlocationintheviewcontroller’sview.Thatlocationdetermineswhichviewinthehierarchythetoucheventisdeliveredto.
Forexample,whenyoutapaUIButtonwithinitsbounds,itwillreceivethetoucheventandrespondinbutton-likefashion–bycallingtheactionmethodonitstarget.Itisperfectlyreasonabletoexpectthatwhenaviewinyourapplicationistouched,thatviewreceivesatouchevent,anditmaychoosetoreacttothateventorignoreit.However,viewsinyourapplicationcanalsorespondtoeventswithoutbeingtouched.Agoodexampleofthisisashake.Ifyoushakethedevicewithyourapplicationrunning,oneofyourviewsonthescreencanrespond.Butwhichone?Anotherinterestingcaseisrespondingtothekeyboard.DetailViewController’sviewcontainsthreeUITextFields.Whichonewillreceivethetextwhentheusertypes?
Forboththeshakeandkeyboardevents,thereisnoeventlocationwithinyourviewhierarchytodeterminewhichviewwillreceivetheevent,soanothermechanismmustbeused.Thismechanismisthefirstresponderstatus.Manyviewsandcontrolscanbeafirstresponderwithinyourviewhierarchy–butonlyoneatatime.Thinkofitasaflagthatcanbepassedamongviews.Whicheverviewholdstheflagwillreceivetheshakeorkeyboardevent.
InstancesofUITextFieldandUITextViewhaveanuncommonresponsetotouchevents.Whentouched,atextfieldoratextviewbecomesthefirstresponder,whichinturntriggersthesystemtoputthekeyboardonscreenandsendthekeyboardeventstothetextfieldorview.Thekeyboardandthetextfieldorviewhavenodirectconnection,buttheyworktogetherthroughthefirstresponderstatus.
Thisisaneatwaytoensurethatthekeyboardinputisdeliveredtothecorrecttextfield.TheconceptofafirstresponderispartofthebroadertopicofeventhandlinginCocoaTouchprogramming,andthisincludestheUIResponderclassandtheresponderchain.YouwilllearnmoreaboutthemwhenyouhandletoucheventsinChapter17,andyoucanalsovisitApple’sEventHandlingGuideforiOSformoreinformation.
DismissingbypressingtheReturnkey
Nowlet’sgetbacktoallowinguserstodismissthekeyboard.Ifyoutouchanothertextfieldintheapplication,thattextfieldwillbecomethefirstresponder,andthekeyboardwillstayonscreen.Thekeyboardwillonlygiveupandgoawaywhennotextfield(ortextview)isthefirstresponder.Todismissthekeyboardthen,youcallresignFirstResponder()onthetextfieldthatisthefirstresponder.
WOW! eBook www.wowebook.org
TohavethetextfieldresigninresponsetotheReturnkeybeingpressed,youaregoingtoimplementtheUITextFieldDelegatemethodtextFieldShouldReturn(_:).ThismethodiscalledwhenevertheReturnkeyispressed.
First,inDetailViewController.swift,haveDetailViewControllerconformtotheUITextFieldDelegateprotocol.classDetailViewController:UIViewController,UITextFieldDelegate{
Next,implementtextFieldShouldReturn(_:)tocallresignFirstResponder()onthetextfieldthatispassedin.functextFieldShouldReturn(textField:UITextField)->Bool{textField.resignFirstResponder()returntrue}
Finally,openMain.storyboardandconnectthedelegatepropertyofeachtextfieldtotheDetailViewController(Figure13.8).(Control-dragfromeachUITextFieldtotheDetailViewControllerandchoosedelegate.)
Figure13.8Connectingthedelegatepropertyofatextfield
Buildandruntheapplication.TapatextfieldandthenpresstheReturnkeyonthekeyboard.Thekeyboardwilldisappear.Togetthekeyboardback,touchanytextfield.
Dismissingbytappingelsewhere
ItwouldbestylishtoalsodismissthekeyboardiftheusertapsanywhereelseonDetailViewController’sview.Todothis,youaregoingtouseagesturerecognizerwhentheviewistapped,justasyoudidintheWorldTrotterapp.Intheactionmethod,youwillcallresignFirstResponder()onthetextfield.
OpenMain.storyboardandfindTapGestureRecognizerintheobjectlibrary.DragthisobjectontothebackgroundviewfortheDetailViewController.Youwillseeareferencetothisgesturerecognizerinthescenedock.
WOW! eBook www.wowebook.org
Intheprojectnavigator,Option-clickDetailViewController.swifttoopenitintheassistanteditor.Control-dragfromthetapgesturerecognizerinthestoryboardtotheimplementationofDetailViewController.
Inthepop-upthatappears,selectActionfromtheConnectionmenu.NametheactionbackgroundTapped.FortheType,chooseUITapGestureRecognizer(Figure13.9).
Figure13.9ConfiguringaUITapGestureRecognizeraction
ClickConnectandthestubfortheactionmethodwillappearinDetailViewController.swift.UpdatethemethodtocallendEditing(_:)ontheviewofDetailViewController.@IBActionfuncbackgroundTapped(sender:AnyObject){view.endEditing(true)}
CallingendEditing(_:)isaconvenientwaytodismissthekeyboardwithouthavingtoknow(orcare)whichtextfieldisthefirstresponder.Whentheviewgetsthiscall,itcheckstoseeifanytextfieldinitshierarchyisthefirstresponder.Ifso,thenresignFirstResponder()iscalledonthatparticularview.
Buildandrunyourapplication.Taponatextfieldtoshowthekeyboard.Tapontheviewoutsideofatextfieldandthekeyboardwilldisappear.
Thereisonefinalcasewhereyouneedtodismissthekeyboard.WhentheusertapstheBackbutton,viewWillDisappear(_:)iscalledontheDetailViewControllerbeforeitispoppedoffthestack.InDetailViewController.swift,updatetheimplementationofviewWillDisappear(_:)tocallendEditing(_:).overridefuncviewWillDisappear(animated:Bool){super.viewWillDisappear(animated)
//Clearfirstresponderview.endEditing(true)
//"Save"changestoitemitem.name=nameField.text??""item.serialNumber=serialNumberField.text
ifletvalueText=valueField.text,letvalue=numberFormatter.numberFromString(valueText){item.valueInDollars=value.integerValue}else{item.valueInDollars=0}}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UINavigationBarInthissection,youaregoingtogivetheUINavigationBaradescriptivetitlefortheUIViewControllerthatiscurrentlyontopoftheUINavigationController’sstack.
EveryUIViewControllerhasanavigationItempropertyoftypeUINavigationItem.However,unlikeUINavigationBar,UINavigationItemisnotasubclassofUIView,soitcannotappearonthescreen.Instead,thenavigationitemsuppliesthenavigationbarwiththecontentitneedstodraw.WhenaUIViewControllercomestothetopofaUINavigationController’sstack,theUINavigationBarusestheUIViewController’snavigationItemtoconfigureitself,asshowninFigure13.10.
Figure13.10UINavigationItem
Bydefault,aUINavigationItemisempty.Atthemostbasiclevel,aUINavigationItemhasasimpletitlestring.WhenaUIViewControllerismovedtothetopofthenavigationstackanditsnavigationItemhasavalidstringforitstitleproperty,thenavigationbarwilldisplaythatstring(Figure13.11).
Figure13.11UINavigationItemwithtitle
ThetitlefortheItemsViewControllerwillalwaysremainthesame,soyoucansetthetitleofitsnavigationitemwithinthestoryboarditself.
WOW! eBook www.wowebook.org
OpenMain.storyboard.Double-clickonthecenterofthenavigationbarabovetheItemsViewControllertoedititstitle.Giveitatitleof“Homepwner”(Figure13.12).
Figure13.12Settingthetitleinastoryboard
Buildandruntheapplication.NoticethestringHomepwneronthenavigationbar.Createandtaponarowandnoticethatthenavigationbarnolongerhasatitle.ItwouldbenicetohavetheDetailViewController’snavigationitemtitlebethenameoftheItemitisdisplaying.SincethetitlewilldependontheItemthatisbeingdisplayed,youneedtosetthetitleofthenavigationItemdynamicallyincode.
InDetailViewController.swift,addapropertyobservertotheitempropertythatupdatesthetitleofthenavigationItem.varitem:Item!{didSet{navigationItem.title=item.name}}
Buildandruntheapplication.CreateandtaparowandyouwillseethatthetitleofthenavigationbaristhenameoftheItemyouselected.
Anavigationitemcanholdmorethanjustatitlestring,asshowninFigure13.13.TherearethreecustomizableareasforeachUINavigationItem:aleftBarButtonItem,arightBarButtonItem,andatitleView.TheleftandrightbarbuttonitemsarereferencestoinstancesofUIBarButtonItem,whichcontaintheinformationforabuttonthatcanonlybedisplayedonaUINavigationBaroraUIToolbar.
Figure13.13UINavigationItemwitheverything
RecallthatUINavigationItemisnotasubclassofUIView.Instead,
WOW! eBook www.wowebook.org
UINavigationItemencapsulatesinformationthatUINavigationBarusestoconfigureitself.Similarly,UIBarButtonItemisnotaview,butholdstheinformationabouthowasinglebuttonontheUINavigationBarshouldbedisplayed.(AUIToolbaralsousesinstancesofUIBarButtonItemtoconfigureitself.)
ThethirdcustomizableareaofaUINavigationItemisitstitleView.YoucaneitheruseabasicstringasthetitleorhaveasubclassofUIViewsitinthecenterofthenavigationitem.Youcannothaveboth.Ifitsuitsthecontextofaspecificviewcontrollertohaveacustomview(likeasegmentedcontroloratextfield,forexample),youwouldsetthetitleViewofthenavigationitemtothatcustomview.Figure13.13showsanexamplefromthebuilt-inMapsapplicationofaUINavigationItemwithacustomviewasitstitleView.Typically,however,atitlestringissufficient.
Addingbuttonstothenavigationbar
Inthissection,youaregoingtoreplacethetwobuttonsthatareinthetable’sheaderviewwithtwobarbuttonitemsthatwillappearintheUINavigationBarwhentheItemsViewControllerisontopofthestack.
First,let’sworkonabarbuttonitemforaddingnewitems.ThisbuttonwillsitontherightsideofthenavigationbarwhentheItemsViewControllerisontopofthestack.Whentapped,itwilladdanewItem.
Abarbuttonitemhasatarget-actionpairthatworkslikeUIControl’starget-actionmechanism:whentapped,itsendstheactionmessagetothetarget.
InMain.storyboard,opentheobjectlibraryanddragaBarButtonItemtotherightsideofItemsViewController’snavigationbar.Selectthisbarbuttonitemandopenitsattributesinspector.ChangetheSystemItemtoAdd(Figure13.14).
Figure13.14Systembarbuttonitem
Control-dragfromthisbarbuttonitemtotheItemsViewControllerandselectaddNewItem:WOW! eBook
www.wowebook.org
(Figure13.15).
Figure13.15ConnectingtheaddNewItem:action
Buildandruntheapplication.Tapthe+buttonandanewrowwillappearinthetable.
Nowlet’sreplacetheEditbutton.Viewcontrollersexposeabarbuttonitemthatwillautomaticallytoggletheireditingmode.ThereisnowaytoaccessthisthroughInterfaceBuilder,soyouwillneedtoaddthisbarbuttonitemprogrammatically.
InItemsViewController.swift,overridetheinit(coder:)methodtosettheleftbarbuttonitem.requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)
navigationItem.leftBarButtonItem=editButtonItem()}
Buildandruntheapplication,addsomeitems,andtaptheEditbutton.TheUITableViewenterseditingmode!TheeditButtonItem()methodcreatesaUIBarButtonItemwiththetitleEdit.Evenbetter,thisbuttoncomeswithatarget-actionpair:itcallsthemethodsetEditing(_:animated:)toitsUIViewControllerwhentapped.
OpenMain.storyboard.NowthatHomepwnerhasafullyfunctionalnavigationbar,youcangetridoftheheaderviewandtheassociatedcode.SelecttheheaderviewonthetableviewandpressDelete.
Also,theUINavigationControllerwillhandleupdatingtheinsetsforthetableview.InItemsViewController.swift,deletethefollowingcode.overridefuncviewDidLoad(){super.viewDidLoad()
//GettheheightofthestatusbarletstatusBarHeight=UIApplication.sharedApplication().statusBarFrame.height
letinsets=UIEdgeInsets(top:statusBarHeight,left:0,bottom:0,right:0)tableView.contentInset=insetstableView.scrollIndicatorInsets=insets
tableView.rowHeight=UITableViewAutomaticDimension
WOW! eBook www.wowebook.org
tableView.estimatedRowHeight=65}
Finally,removethetoggleEditingMode(_:)method.@IBActionfunctoggleEditingMode(sender:AnyObject){ifediting{//Changetextofbuttontoinformuserofstatesender.setTitle("Edit",forState:.Normal)
//TurnoffeditingmodesetEditing(false,animated:true)}else{//Changetextofbuttontoinformuserofstatesender.setTitle("Done",forState:.Normal)
//EntereditingmodesetEditing(true,animated:true)}}
Buildandrunagain.TheoldEditandAddbuttonsaregone,leavingyouwithalovelyUINavigationBar(Figure13.16).
Figure13.16Homepwnerwithnavigationbar
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:DisplayingaNumberPadThekeyboardfortheUITextFieldthatdisplaysaItem’svalueInDollarsisaQWERTYkeyboard.Itwouldbebetterifitwereanumberpad.ChangetheKeyboardTypeofthatUITextFieldtotheNumberPad.(Hint:youcandothisinthestoryboardfileusingtheattributesinspector.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:ACustomUITextFieldMakeasubclassofUITextFieldandoverridethebecomeFirstResponder()andresignFirstResponder()methods(inheritedfromUIResponder)sothatitsborderstylechangeswhenitisthefirstresponder.YoucanusetheborderStylepropertyofUITextFieldtoaccomplishthis.UseyoursubclassforthetextfieldsinDetailViewController.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:PushingMoreViewControllersCurrently,instancesofItemcannothavetheirdateCreatedpropertychanged.ChangeItemsothattheycan,andthenaddabuttonunderneaththedateLabelinDetailViewControllerwiththetitleChangeDate.Whenthisbuttonistapped,pushanotherviewcontrollerinstanceontothenavigationstack.ThisviewcontrollershouldhaveaUIDatePickerinstancethatmodifiesthedateCreatedpropertyoftheselectedItem.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
14Camera
Inthischapter,youaregoingtoaddphotostotheHomepwnerapplication.YouwillpresentaUIImagePickerControllersothattheusercantakeandsaveapictureofeachitem.TheimagewillthenbeassociatedwithanIteminstanceandviewableintheitem’sdetailview(Figure14.1).
Figure14.1Homepwnerwithcameraaddition
Imagestendtobeverylarge,soitisagoodideatostoreimagesseparatelyfromotherdata.Thus,youaregoingtocreateasecondstoreforimages.ImageStorewillfetchandcacheimagesastheyareneeded.
WOW! eBook www.wowebook.org
DisplayingImagesandUIImageViewYourfirststepistohavetheDetailViewControllergetanddisplayanimage.AneasywaytodisplayanimageistoputaninstanceofUIImageViewonthescreen.
OpenHomepwner.xcodeprojandMain.storyboard.ThendraganinstanceofUIImageViewontotheviewatthebottomofthestackview(Figure14.2).Selecttheimageviewandopenitssizeinspector.Youwanttheverticalcontenthuggingandcontentcompressionresistanceprioritiesfortheimageviewtobelowerthanthoseoftheotherviews.ChangetheVerticalContentHuggingPrioritytobe248andtheVerticalContentCompressionResistancePrioritytobe749.
Figure14.2UIImageViewonDetailViewController’sview
AUIImageViewdisplaysanimageaccordingtotheimageview’scontentModeproperty.Thispropertydetermineswheretopositionandhowtoresizethecontentwithintheimageview’sframe.UIImageView’sdefaultvalueforcontentModeisUIViewContentMode.ScaleToFill,whichadjuststheimagetoexactlymatchtheboundsoftheimageview.Ifyoukeepthedefault,animagetakenbythecamerawillbe
WOW! eBook www.wowebook.org
scaledtofitintothesquareUIImageView.Tomaintaintheimage’saspectratio,youhavetoupdatecontentMode.
WiththeUIImageViewselected,opentheattributesinspector.FindtheModeattributeandchangeittoAspectFit(Figure14.3).Youwillnotseeachangeonthestoryboard,butnowimageswillberesizedtofitwithintheboundsoftheUIImageView.
Figure14.3ChangeUIImageView’smodetoAspectFit
Next,Option-clickDetailViewController.swiftintheprojectnavigatortoopenitintheassistanteditor.Control-dragfromtheUIImageViewtothetopofDetailViewController.swift.NametheoutletimageViewandchooseStrongasthestoragetype.ClickConnect(Figure14.4).
Figure14.4CreatingtheimageViewoutlet
ThetopofDetailViewController.swiftshouldnowlooklikethis:classDetailViewController:UIViewController{
@IBOutletvarnameField:UITextField!@IBOutletvarserialNumberField:UITextField!@IBOutletvarvalueField:UITextField!@IBOutletvardateLabel:UILabel!@IBOutletvarimageView:UIImageView!
AddingacamerabuttonWOW! eBook
www.wowebook.org
Nowyouneedabuttontoinitiatethephoto-takingprocess.YouwillcreateaninstanceofUIToolbarandplaceitatthebottomofDetailViewController’sview.
InMain.storyboard,pressCommand-Returntoclosetheassistanteditorandgiveyourselfmoreroomtoworkinthestoryboard.Youaregoingtoneedtotemporarilybreakyourinterfaceinordertoaddthetoolbartotheinterface.
SelectthebottomconstraintforthestackviewandpressDeletetoremoveit.Youneedtomakeroomforthetoolbaronthebottom.AsofXcode7.1,itisdifficulttoresizethestackview.Soinstead,dragthestackviewupabit(Figure14.5).Theviewwillbemisplacedfornow,butyouwillfixthisshortly.
Figure14.5Movingthestackviewoutoftheway
Nowdragatoolbarfromtheobjectlibraryontothebottomoftheview.SelectthetoolbarandopentheAutoLayoutPinmenu.ConfiguretheconstraintsexactlyasshowninFigure14.6andthenclickAdd5Constraints.Sinceyouchosetheoptiontoupdateframes,thestackviewisrepositionedtoitscorrectlocation.
WOW! eBook www.wowebook.org
Figure14.6Toolbarconstraints
AUIToolbarworksalotlikeaUINavigationBar–youcanaddinstancesofUIBarButtonItemtoit.However,whereanavigationbarhastwoslotsforbarbuttonitems,atoolbarhasanarrayofbarbuttonitems.Youcanplaceasmanybarbuttonitemsinatoolbarascanfitonthescreen.
Bydefault,anewinstanceofUIToolbarthatiscreatedinaninterfacefilecomeswithoneUIBarButtonItem.Selectthisbarbuttonitemandopentheattributesinspector.ChangetheSystemItemtoCamera,andtheitemwillshowacameraicon(Figure14.7).
WOW! eBook www.wowebook.org
Figure14.7UIToolbarwithbarbuttonitem
Buildandruntheapplicationandnavigatetoanitem’sdetailstoseethetoolbarwithitscamerabarbuttonitem.Youhavenotconnectedthecamerabuttontoanactionyet,sotappingonitwillnotdoanything.
Thecamerabuttonneedsatargetandanaction.WithMain.storyboardstillopen,Option-clickDetailViewController.swiftintheprojectnavigatortoreopenitintheassistanteditor.
InMain.storyboard,selectthecamerabuttonbyfirstclickingonthetoolbarandthenthebuttonitself.Control-dragfromtheselectedbuttontoDetailViewController.swift.
IntheConnectionpop-upmenu,selectActionastheconnectiontype,nameittakePicture,selectUIBarButtonItemasthetype,andclickConnect(Figure14.8).
Figure14.8Creatinganaction
WOW! eBook www.wowebook.org
Ifyoumadeanymistakeswhilemakingthisconnection,youwillneedtoopenMain.storyboardanddisconnectanybadconnections.(Lookforyellowwarningsignsintheconnectionsinspector.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TakingPicturesandUIImagePickerControllerInthetakePicture(_:)method,youwillinstantiateaUIImagePickerControllerandpresentitonthescreen.WhencreatinganinstanceofUIImagePickerController,youmustsetitssourceTypepropertyandassignitadelegate.Sincethereissetupworkneededfortheimagepickercontroller,youneedtocreateandpresentitprogrammaticallyinsteadofthroughthestoryboard.
Settingtheimagepicker’ssourceType
ThesourceTypeconstanttellstheimagepickerwheretogetimages.Ithasthreepossiblevalues(Figure14.9):
UIImagePickerControllerSourceType.Camera
Theuserwilltakeanewpicture.
UIImagePickerControllerSourceType.PhotoLibrary
Theuserwillbepromptedtoselectanalbumandthenaphotofromthatalbum.
UIImagePickerControllerSourceType.SavedPhotosAlbum
Theuserpicksfromthemostrecentlytakenphotos.
Figure14.9Examplesofthreesourcetypes
Thefirstsourcetype,.Camera,willnotworkonadevicethatdoesnothaveacamera.Sobeforeusingthistype,youhavetocheckforacamerabycallingthemethod
WOW! eBook www.wowebook.org
isSourceTypeAvailable(_:)ontheUIImagePickerControllerclass:classfuncisSourceTypeAvailable(type:UIImagePickerControllerSourceType)->Bool
CallingthismethodreturnsaBooleanvalueforwhetherthedevicesupportsthepassed-insourcetype.
InDetailViewController.swift,findthestubfortakePicture(_:).AddthefollowingcodetocreatetheimagepickerandsetitssourceType.@IBActionfunctakePicture(sender:UIBarButtonItem){
letimagePicker=UIImagePickerController()
//Ifthedevicehasacamera,takeapicture;otherwise,//justpickfromphotolibraryifUIImagePickerController.isSourceTypeAvailable(.Camera){imagePicker.sourceType=.Camera}else{imagePicker.sourceType=.PhotoLibrary}}
Settingtheimagepicker’sdelegate
Inadditiontoasourcetype,theUIImagePickerControllerinstanceneedsadelegate.WhentheuserselectsanimagefromtheUIImagePickerController’sinterface,thedelegateissentthemessageimagePickerController(_:didFinishPickingMediaWithInfo:).(Iftheusertapsthecancelbutton,thenthedelegatereceivesthemessageimagePickerControllerDidCancel(_:).)
Theimagepicker’sdelegatewillbetheinstanceofDetailViewController.AtthetopofDetailViewController.swift,declarethatDetailViewControllerconformstotheUINavigationControllerDelegateandtheUIImagePickerControllerDelegateprotocols.classDetailViewController:UIViewController,UITextFieldDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate{
WhyUINavigationControllerDelegate?UIImagePickerController’sdelegatepropertyisactuallyinheritedfromitssuperclass,UINavigationController,andwhileUIImagePickerControllerhasitsowndelegateprotocol,itsinheriteddelegatepropertyisdeclaredtoreferenceanobjectthatconformstoUINavigationControllerDelegate.
InDetailViewController.swift,addthefollowingcodetotakePicture(_:)tosettheinstanceofDetailViewControllertobetheimagepicker’sdelegate.@IBActionfunctakePicture(sender:AnyObject){
letimagePicker=UIImagePickerController()
//Ifthedevicehasacamera,takeapicture;otherwise,//justpickfromphotolibraryifUIImagePickerController.isSourceTypeAvailable(.Camera){imagePicker.sourceType=.Camera
WOW! eBook www.wowebook.org
}else{imagePicker.sourceType=.PhotoLibrary}
imagePicker.delegate=self}
Presentingtheimagepickermodally
OncetheUIImagePickerControllerhasasourcetypeandadelegate,youcandisplayitbypresentingtheviewcontrollermodally.
InDetailViewController.swift,addcodetotheendoftakePicture(_:)topresenttheUIImagePickerController.imagePicker.delegate=self
//PlaceimagepickeronthescreenpresentViewController(imagePicker,animated:true,completion:nil)}
Buildandruntheapplication.SelectanItemtoseeitsdetailsandthentapthecamerabuttonontheUIToolbar.UIImagePickerController’sinterfacewillappearonthescreen(Figure14.10),andyoucantakeapictureorchooseanexistingimageifyourdevicedoesnothaveacamera.
(Ifyouareworkingonthesimulator,therearesomedefaultimagesalreadyinthephotolibrary.Ifyouwouldliketoaddyourown,youcandraganimagefromyourcomputerontothesimulator,anditwillbeaddedtothesimulator’sphotolibrary.Alternatively,youcanopenSafariinthesimulatorandnavigatetoapagewithanimage.ClickandholdtheimageandchooseSaveImagetosaveitinthesimulator’sphotolibrary.)
WOW! eBook www.wowebook.org
Figure14.10UIImagePickerController’spreviewinterface
Savingtheimage
SelectinganimagedismissestheUIImagePickerControllerandreturnsyoutothedetailview.However,youdonothaveareferencetothephotooncetheimagepickerisdismissed.Tofixthis,youaregoingtoimplementthedelegatemethodimagePickerController(_:didFinishPickingMediaWithInfo:).Thismethodiscalledontheimagepicker’sdelegatewhenaphotohasbeenselected.
InDetailViewController.swift,implementthismethodtoputtheimageintotheUIImageViewandthencallthemethodtodismisstheimagepicker.funcimagePickerController(picker:UIImagePickerController,didFinishPickingMediaWithInfoinfo:[String:AnyObject]){
//Getpickedimagefrominfodictionaryletimage=info[UIImagePickerControllerOriginalImage]as!UIImage
WOW! eBook www.wowebook.org
//PutthatimageonthescreenintheimageviewimageView.image=image
//Takeimagepickeroffthescreen-//youmustcallthisdismissmethoddismissViewControllerAnimated(true,completion:nil)}
Buildandruntheapplicationagain.Take(orselect)aphoto.Theimagepickerisdismissed,andyouarereturnedtotheDetailViewController’sview,whereyouwillseetheselectedphoto.
Homepwner’suserscouldhavehundredsofitemstocatalog,andeachonecouldhavealargeimageassociatedwithit.KeepinghundredsofinstancesofIteminmemoryisnotabigdeal.Keepinghundredsofimagesinmemorywouldbebad:First,youwillgetalowmemorywarning.Then,ifyourapp’smemoryfootprintcontinuestogrow,theOSwillterminateit.Thesolution,whichyouaregoingtoimplementinthenextsection,istostoreimagestodiskandonlyfetchthemintoRAMwhentheyareneeded.Thisfetchingwillbedonebyanewclass,ImageStore.Whentheapplicationreceivesalow-memorynotification,theImageStore’scachewillbeflushedtofreethememorythatthefetchedimageswereoccupying.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingImageStoreInChapter15,youwillhaveinstancesofItemwriteouttheirpropertiestoafile,whichwillthenbereadinwhentheapplicationstarts.However,becauseimagestendtobeverylarge,itisagoodideatokeepthemseparatefromotherdata.YouaregoingtostorethepicturestheusertakesinaninstanceofaclassnamedImageStore.Theimagestorewillfetchandcachetheimagesastheyareneeded.Itwillalsobeabletoflushthecacheifthedevicerunslowonmemory.
CreateanewSwiftfilenamedImageStore.InImageStore.swift,definetheImageStoreclassandaddapropertythatisaninstanceofNSCache.importFoundationimportUIKit
classImageStore{
letcache=NSCache()
}
Thecacheworksverymuchlikeadictionary(whichyousawinChapter2).Youareabletoadd,remove,andupdatevaluesassociatedwithagivenkey.Unlikeadictionary,thecachewillautomaticallyremoveobjectsifthesystemgetslowonmemory.Whilethiscouldbeaprobleminthischapter(sinceimageswillonlyexistwithinthecache),youwillfixtheprobleminChapter15whenyouwillalsowritetheimagestothefilesystem.
Nowimplementthreemethodsforadding,retrieving,anddeletinganimagefromthedictionary.classImageStore:NSObject{
letcache=NSCache()
funcsetImage(image:UIImage,forKeykey:String){cache.setObject(image,forKey:key)}
funcimageForKey(key:String)->UIImage?{returncache.objectForKey(key)as?UIImage}
funcdeleteImageForKey(key:String){cache.removeObjectForKey(key)}
}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GivingViewControllersAccesstotheImageStoreTheDetailViewControllerneedsaninstanceofImageStoretofetchandstoreimages.YouwillinjectthisdependencyintotheDetailViewController’sdesignatedinitializer,justasyoudidforItemsViewControllerandItemStoreinChapter9.
InDetailViewController.swift,addapropertyforanImageStore.varitem:Item!{didSet{navigationItem.title=item.name}}varimageStore:ImageStore!
NowdothesameinItemsViewController.swift.letitemStore:ItemStorevarimageStore:ImageStore!
Next,stillinItemsViewController.swift,updateprepareForSegue(_:sender:)tosettheimageStorepropertyonDetailViewController.overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){ifsegue.identifier=="ShowItem"{ifletrow=tableView.indexPathForSelectedRow?.row{letitem=itemStore.allItems[row]letdetailViewController=segue.destinationViewControlleras!DetailViewControllerdetailViewController.item=itemdetailViewController.imageStore=imageStore}}}
Finally,updateAppDelegate.swifttocreateandinjecttheImageStore.funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{//Overridepointforcustomizationafterapplicationlaunch.
//CreateanItemStoreletitemStore=ItemStore()
//CreateanImageStoreletimageStore=ImageStore()
//GettheItemsViewControllerletnavController=window!.rootViewControlleras!UINavigationControllerletitemsController=navController.topViewControlleras!ItemsViewController
//AccesstheItemsViewControllerandsetitsitemstoreandimagestoreitemsController.itemStore=itemStoreitemsController.imageStore=imageStore
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingandUsingKeysWhenanimageisaddedtothestore,itwillbeputintothecacheunderauniquekey,andtheassociatedItemobjectwillbegiventhatkey.WhentheDetailViewControllerwantsanimagefromthestore,itwillaskitsitemforthekeyandsearchthecachefortheimage.
AddapropertytoItem.swifttostorethekey.letdateCreated:NSDateletitemKey:String
Theimagekeysneedtobeuniqueforyourcachetowork.Whiletherearemanywaystohacktogetherauniquestring,youaregoingtousetheCocoaTouchmechanismforcreatinguniversallyuniqueidentifiers(UUIDs),alsoknownasgloballyuniqueidentifiers(GUIDs).ObjectsoftypeNSUUIDrepresentaUUIDandaregeneratedusingthetime,acounter,andahardwareidentifier,whichisusuallytheMACaddressoftheWiFicard.Whenrepresentedasastring,UUIDslooksomethinglikethis:4A73B5D2-A6F4-4B40-9F82-EA1E34C1DC04
InItem.swift,generateaUUIDandsetitastheitemKey.init(name:String,serialNumber:String?,valueInDollars:Int){
self.name=nameself.serialNumber=serialNumberself.valueInDollars=valueInDollarsself.dateCreated=NSDate()self.itemKey=NSUUID().UUIDString
super.init()}
Then,inDetailViewController.swift,updateimagePickerController(_:didFinishPickingMediaWithInfo:)tostoretheimageintheImageStore.funcimagePickerController(picker:UIImagePickerController,didFinishPickingMediaWithInfoinfo:[String:AnyObject]){
//Getpickedimagefrominfodictionaryletimage=info[UIImagePickerControllerOriginalImage]as!UIImage
//StoretheimageintheImageStorefortheitem'skeyimageStore.setImage(image,forKey:item.itemKey)
//PutthatimageonthescreenintheimageviewimageView.image=image
//Takeimagepickeroffthescreen-//youmustcallthisdismissmethoddismissViewControllerAnimated(true,completion:nil)}
Eachtimeanimageiscaptured,itwillbeaddedtothestore.BoththeImageStoreandtheItemwillknowthekeyfortheimage,sobothwillbeabletoaccessitasneeded(Figure14.11).
WOW! eBook www.wowebook.org
Figure14.11Accessingimagesfromthecache
Similarly,whenanitemisdeleted,youneedtodeleteitsimagefromtheimagestore.InItemsViewController.swift,updatetableView(_:commitEditingStyle:forRowAtIndexPath:)toremovetheitem’simagefromtheimagestore.overridefunctableView(tableView:UITableView,commitEditingStyleeditingStyle:UITableViewCellEditingStyle,forRowAtIndexPathindexPath:NSIndexPath){
//Ifthetableviewisaskingtocommitadeletecommand...ifeditingStyle==.Delete{letitem=itemStore.allItems[indexPath.row]
lettitle="Delete\(item.name)?"letmessage="Areyousureyouwanttodeletethisitem?"
letac=UIAlertController(title:title,message:message,preferredStyle:.ActionSheet)
letdeleteAction=UIAlertAction(title:"Delete",style:.Destructive,handler:{(action)->Voidin//Removetheitemfromthestoreself.itemStore.removeItem(item)
//Removetheitem'simagefromtheimagestoreself.imageStore.deleteImageForKey(item.itemKey)
//Alsoremovethatrowfromthetableviewwithananimationself.tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)})ac.addAction(deleteAction)
//PresentthealertcontrollerpresentViewController(ac,animated:true,completion:nil)
WOW! eBook www.wowebook.org
}}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
WrappingUpImageStoreNowthattheImageStorecanstoreimagesandinstancesofItemhaveakeytogetanimage(Figure14.11),youneedtoteachDetailViewControllerhowtograbtheimagefortheselectedItemandplaceitinitsimageView.
TheDetailViewController’sviewwillappearwhentheusertapsarowinItemsViewControllerandwhentheUIImagePickerControllerisdismissed.Inbothofthesesituations,theimageViewshouldbepopulatedwiththeimageoftheItembeingdisplayed.
InDetailViewController.swift,addcodetoviewWillAppear(_:)todothis.overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)
nameField.text=item.nameserialNumberField.text=item.serialNumbervalueField.text=currencyFormatter.stringFromNumber(item.valueInDollars)dateLabel.text=dateFormatter.stringFromDate(item.dateCreated)
//Gettheitemkeyletkey=item.itemKey
//Ifthereisanassociatedimagewiththeitem//displayitontheimageviewletimageToDisplay=imageStore.imageForKey(key)imageView.image=imageToDisplay}
Buildandruntheapplication.CreateanItemandselectitfromtheUITableView.Then,tapthecamerabuttonandtakeapicture.Theimagewillappearasitshould.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:EditinganImageUIImagePickerControllerhasabuilt-ininterfaceforeditinganimageonceithasbeenselected.AllowtheusertoedittheimageandusetheeditedimageinsteadoftheoriginalimageinDetailViewController.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:RemovinganImageAddabuttonthatclearstheimageforanitem.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:CameraOverlayAUIImagePickerControllerhasacameraOverlayViewproperty.MakeitsothatpresentingtheUIImagePickerControllershowsacrosshairinthemiddleoftheimagecapturearea.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:NavigatingImplementationFilesBothofyourviewcontrollershavequiteafewmethodsintheirimplementationfiles.TobeaneffectiveiOSdeveloper,youmustbeabletogotothecodeyouarelookingforquicklyandeasily.ThesourceeditorjumpbarinXcodeisonetoolatyourdisposal(Figure14.12).
Figure14.12Sourceeditorjumpbar
Thejumpbarshowsyouwhereexactlyyouarewithintheproject(andalsowherethecursoriswithinagivenfile).Figure14.13breaksdownthejumpbardetails.
Figure14.13Jumpbardetails
Thebreadcrumbtrailnavigationofthejumpbarmirrorstheprojectnavigationhierarchy.Ifyouclickonanyofthesections,youwillbepresentedwithapopoverofthatsectionintheprojecthierarchy.Fromthere,youcaneasilynavigatetootherpartsoftheproject.
Figure14.14showsthefilepopoverfortheHomepwnerfolder.
Figure14.14Filepopover
WOW! eBook www.wowebook.org
Perhapsmostusefulistheabilitytonavigateeasilywithinanimplementationfile.Ifyouclickonthelastelementinthebreadcrumbtrail,youwillgetapopoverwiththecontentsofthefile,includingallofthemethodsimplementedwithinthatfile.
Whilethepopoverisstillvisible,youcanstarttypingtofiltertheitemsinthelist.Atanypoint,youcanusetheupanddownarrowkeysandthenpresstheReturnkeytojumptothatmethodinthecode.Figure14.15showswhatyougetwhenyousearchfor“indexpath”inItemsViewController.swift.
Figure14.15Filepopoverwith“indexpath”search
//MARK:
Asyourclassesgetlonger,itcangetmoredifficulttofindamethodburiedinalonglistofmethods.Agoodwaytoorganizeyourmethodsistouse//MARK:comments.
Twouseful//MARK:commentsarethedividerandthelabel://Thisisadivider//MARK:-
//Thisisalabel//MARK:MyAwesomeMethods
Thedividerandlabelcanbecombined://MARK:-ViewlifecycleoverridefuncviewDidLoad(){...}overridefuncviewWillAppear(animated:Bool){...}
//MARK:-ActionsfuncaddNewItem(sender:AnyObject){...}
Adding//MARK:commentstoyourcodedoesnotchangethecodeitself;itjusttellsXcodehowtovisuallyorganizeyourmethods.Youcanseetheresultsbyopeningthecurrentfileiteminthejumpbar.Figure14.16presentsawell-organizedItemsViewController.swift.
WOW! eBook www.wowebook.org
Figure14.16Filepopoverwith//MARK:s
Ifyoumakeahabitofusing//MARK:comments,youwillforceyourselftoorganizeyourcode.Ifdonethoughtfully,thiswillmakeyourcodemorereadableandeasiertoworkwith.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
15Saving,Loading,andApplication
StatesTherearemanywaystosaveandloaddatainaniOSapplication.ThischapterwilltakeyouthroughsomeofthemostcommonmechanismsaswellastheconceptsyouneedforwritingtoorreadingfromthefilesysteminiOS.Alongtheway,youwillbeupdatingHomepwnersothatitsdatapersistsbetweenruns(Figure15.1).
Figure15.1Homepwnerinthetaskswitcher
WOW! eBook www.wowebook.org
ArchivingMostiOSapplicationsare,atbase,doingthesamething:providinganinterfacefortheusertomanipulatedata.Everyobjectinanapplicationhasaroleinthisprocess.Modelobjectsareresponsibleforholdingontothedatathattheusermanipulates.Viewobjectsreflectthatdata,andcontrollersareresponsibleforkeepingtheviewsandthemodelobjectsinsync.Therefore,savingandloading“data”almostalwaysmeanssavingandloadingmodelobjects.
InHomepwner,themodelobjectsthatausermanipulatesareinstancesofItem.ForHomepwnertobeausefulapplication,instancesofItemmustpersistbetweenrunsoftheapplication.YouwillbeusingarchivingtosaveandloadItemobjects.
ArchivingisoneofthemostcommonwaysofpersistingmodelobjectsiniOS.Archivinganobjectinvolvesrecordingallofitspropertiesandsavingthemtothefilesystem.Unarchivingrecreatestheobjectfromthatdata.
ClasseswhoseinstancesneedtobearchivedandunarchivedmustconformtotheNSCodingprotocolandimplementitstworequiredmethods,encodeWithCoder(_:)andinit(coder:).protocolNSCoding{funcencodeWithCoder(aCoder:NSCoder)init?(coderaDecoder:NSCoder)}
Whenobjectsareaddedtoaninterfacefile,suchasastoryboardfile,theyarearchived.Atruntime,theobjectsareloadedintomemorybybeingunarchivedfromtheinterfacefile.UIViewandUIViewControllerbothconformtotheNSCodingprotocol,sobothcanbearchivedandunarchivedwithoutanyextraeffortfromyou.
YourItemclass,ontheotherhand,doesnotcurrentlyconformtoNSCoding.OpenHomepwner.xcodeprojandaddthisprotocoldeclarationinItem.swift.classItem:NSObject,NSCoding{
Thenextstepistoimplementtherequiredmethods.Let’sstartwithencodeWithCoder(_:).WhenanItemissentthemessageencodeWithCoder(_:),itwillencodeallofitspropertiesintotheNSCoderobjectthatispassedasanargument.Whilesaving,youwilluseNSCodertowriteoutastreamofdata.Thatstreamwillbestoredonthefilesystemandisorganizedaskey-valuepairs.
InItem.swift,implementencodeWithCoder(_:)toaddthenamesandvaluesoftheitem’spropertiestothestream.funcencodeWithCoder(aCoder:NSCoder){aCoder.encodeObject(name,forKey:"name")aCoder.encodeObject(dateCreated,forKey:"dateCreated")aCoder.encodeObject(itemKey,forKey:"itemKey")aCoder.encodeObject(serialNumber,forKey:"serialNumber")
aCoder.encodeInteger(valueInDollars,forKey:"valueInDollars")}
NoticethatreferencestoobjectsareencodedwithencodeObject(_:forKey:),but
WOW! eBook www.wowebook.org
valueInDollarsisencodedwithencodeInteger(_:forKey:).BecausevalueInDollarsisanIntandnotanobject,youcannotencodeitwithencodeObject(_:forKey:).
Why,then,canyouuseencodeObject(_:forKey:)forname,whichisaString?Stringisnotaclass,andthusinstancesofStringarenotobjects.WhenyoupassaStringtoamethodthatexpectsanobject,likeencodeObject(_:forKey:),SwiftconvertsittoanNSString–thestringclassfromtheFoundationframework–thankstothespecialrelationshipbetweenStringandNSString.
TofindoutwhichencodingmethodstouseforotherSwifttypes,youcancheckthedocumentationforNSCoder.Regardlessofthetypeoftheencodedvalue,thereisalwaysakey,whichisastringthatidentifieswhichpropertyisbeingencoded.Byconvention,thiskeyisthenameofthepropertybeingencoded.
Encodingisarecursiveprocess.Whenanobjectisencoded(thatis,whenitisthefirstargumentinencodeObject(_:forKey:)),thatobjectissentencodeWithCoder(_:).DuringtheexecutionofitsencodeWithCoder(_:)method,itencodesitsobject(orString)propertiesusingencodeObject(_:forKey:)(Figure15.2).Thus,eachobjectencodesanyobjectsthatitreferences,whichencodeanyobjectsthattheyreference,andsoon.
Figure15.2Encodinganobject
Tobeencoded,theseobjectsmustalsoconformtoNSCoding.Let’sconfirmthisforNSDate.Theprotocolsthatatypeconformstoarelistedinitsreference.Insteadofopeningupthedocumentationbrowserasyouhavedonebefore,youcantakeashortcuttogettothereferencedirectlyfromyourcode.
InItem.swift,holddowntheOptionkey,mouseoveranoccurrenceofNSDateinyourcode,andclick.Apop-upwindowwillappearwithabriefdescriptionofthetypeand
WOW! eBook www.wowebook.org
linkstoitsreference,asshowninFigure15.3.
Figure15.3Option-clickingNSDate
Atthetop,noticetheDeclarationlinewheretheclassdeclarationforNSDateisgiven.HereyoucanseethatNSDatedoesconformtotheNSCodingprotocol.
YoucanalsoclicktheReferencelinkatthebottomtobetakentotheNSDatereference.Atthetopofthereferenceyouwillseealistofprotocolsthattheclassconformsto.
(YouarenotdoingthesamecheckforStringbecauseStringdoesnotconformtoNSCoding.RecallthatwhenyoupassaStringtoencodeObject(_:forKey:),thestringisconvertedtoanNSString,andNSStringdoesconformtoNSCoding.)
Option-clickingisnotjustforclassesandtypes.Youcanusethesameshortcutformethods,protocols,andmore.Keepthisinmindasyourunacrossitemsinyourcodethatyouwanttoknowmoreabout.
Nowbacktokeysandencoding.ThepurposeofthekeyistoretrievetheencodedvaluewhenthisItemisloadedfromthefilesystemlater.Objectsbeingloadedfromanarchivearesentthemessageinit(coder:).ThismethodshouldgraballoftheobjectsthatwereencodedinencodeWithCoder(_:)andassignthemtotheappropriateproperty.
InItem.swift,implementinit(coder:).requiredinit(coderaDecoder:NSCoder){name=aDecoder.decodeObjectForKey("name")as!StringdateCreated=aDecoder.decodeObjectForKey("dateCreated")as!NSDateitemKey=aDecoder.decodeObjectForKey("itemKey")as!StringserialNumber=aDecoder.decodeObjectForKey("serialNumber")as!String?
valueInDollars=aDecoder.decodeIntegerForKey("valueInDollars")
super.init()}
NoticethatthismethodhasanNSCoderargument,too.Ininit(coder:),theNSCoderisfullofdatatobeconsumedbytheItembeinginitialized.AlsonoticethatyoucalldecodeObjectForKey:tothecontainertogetobjects(includinginstancesofStringasNSStringobjects)anddecodeIntegerForKey(_:)togetthe
WOW! eBook www.wowebook.org
valueInDollars.
InChapter9,wetalkedabouttheinitializerchainanddesignatedinitializers.Theinit(coder:)methodisnotpartofthisdesignpattern.YouwillkeepItem’sdesignatedinitializerthesame,andinit(coder:)willnotcallit.
InstancesofItemarenowNSCoding-compliantandcanbesavedtoandloadedfromthefilesystemusingarchiving.Youcanbuildtheapplicationtomakesuretherearenosyntaxerrors,butyoustillneedawaytokickoffthesavingandloading.Youalsoneedaplaceonthefilesystemtostorethesaveditems.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ApplicationSandboxEveryiOSapplicationhasitsownapplicationsandbox.Anapplicationsandboxisadirectoryonthefilesystemthatisbarricadedfromtherestofthefilesystem.Yourapplicationmuststayinitssandbox,andnootherapplicationcanaccessyoursandbox.Figure15.4showstheapplicationsandboxforiTunes/iCloud.
Figure15.4Applicationsandbox
Theapplicationsandboxcontainsanumberofdirectories:
Documents/
Thisdirectoryiswhereyouwritedatathattheapplicationgeneratesduringruntimeandthatyouwanttopersistbetweenrunsoftheapplication.ItisbackedupwhenthedeviceissynchronizedwithiTunesoriCloud.Ifsomethinggoeswrongwiththedevice,filesinthisdirectorycanberestoredfromiTunesoriCloud.InHomepwner,thefilethatholdsthedataforallyouritemswillbestoredhere.
Library/Caches/
Thisdirectoryiswhereyouwritedatathattheapplicationgeneratesduringruntimeandthatyouwanttopersistbetweenrunsoftheapplication.However,unliketheDocumentsdirectory,itdoesnotgetbackedupwhenthedeviceissynchronizedwithiTunesoriCloud.Amajorreasonfornotbackingupcacheddataisthatthedatacanbeverylargeandextendthetimeittakestosynchronizeyourdevice.Datastoredsomewhereelse–likeawebserver–canbeplacedinthisdirectory.Iftheuserneedstorestorethedevice,thisdatacanbedownloadedfromthewebserveragain.Ifthedeviceisverylowondiskspace,thesystemmaydeletethecontentsofthisdirectory.
Library/Preferences/
ThisdirectoryiswhereanypreferencesarestoredandwheretheSettingsapplicationlooksforapplicationpreferences.Library/PreferencesishandledautomaticallybytheclassNSUserDefaultsandisbackedupwhen
WOW! eBook www.wowebook.org
thedeviceissynchronizedwithiTunesoriCloud.
tmp/
Thisdirectoryiswhereyouwritedatathatyouwillusetemporarilyduringanapplication’sruntime.TheOSmaypurgefilesinthisdirectorywhenyourapplicationisnotrunning.However,tobetidyyoushouldstillexplicitlyremovefilesfromthisdirectorywhenyounolongerneedthem.ThisdirectorydoesnotgetbackedupwhenthedeviceissynchronizedwithiTunesoriCloud.
ConstructingafileURL
TheinstancesofItemfromHomepwnerwillbesavedtoasinglefileintheDocumentsdirectory.TheItemStorewillhandlewritingtoandreadingfromthatfile.Todothis,theItemStoreneedstoconstructaURLtothisfile.
ImplementanewpropertyinItemStore.swifttostorethisURL.varallItems=[Item]()letitemArchiveURL:NSURL={letdocumentsDirectories=NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory,inDomains:.UserDomainMask)letdocumentDirectory=documentsDirectories.first!returndocumentDirectory.URLByAppendingPathComponent("items.archive")}()
Insteadofassigningavaluetothepropertydirectly,thevalueisbeingsetusingaclosure.YoumayrecallthatyoudidthiswiththenumberFormatterpropertyinChapter4.Noticethattheclosureherehasasignatureof()->NSURL,meaningitdoesnottakeinanyargumentsanditreturnsaninstanceofNSURL.WhentheItemStoreclassisinstantiated,thisclosurewillberunandthereturnvaluewillbeassignedtotheitemArchiveURLproperty.Usingaclosurelikethisallowsyoutosetthevalueforavariableorconstantthatrequiresmultiplelinesofcode,whichcanbeveryusefulwhenconfiguringobjects.Thismakesyourcodemoremaintainablebecauseitkeepsthepropertyandthecodeneededtogeneratethepropertytogether.
ThemethodURLsForDirectory(_:inDomains:)searchesthefilesystemforaURLthatmeetsthecriteriagivenbythearguments.(Double-checkthatyourfirstargumentis.DocumentDirectoryandnot.DocumentationDirectory.Autocomplete’sfirstsuggestionis.DocumentationDirectory,soitiseasytointroducethiserrorandendupwiththewrongURL.)
IniOS,thelastargumentisalwaysthesame.(ThismethodisborrowedfromOSX,wheretherearesignificantlymoreoptions.)ThefirstargumentisanNSSearchPathDirectoryenumerationthatspecifiesthedirectoryinthesandboxyouwanttheURLto.Forexample,searchingfor.CachesDirectorywillreturntheCachesdirectoryintheapplication’ssandbox.
YoucansearchthedocumentationforNSSearchPathDirectorytolocatetheotheroptions.RememberthattheseenumerationvaluesaresharedbyiOSandOSX,sonotallofthemwillworkoniOS.
WOW! eBook www.wowebook.org
ThereturnvalueofURLsForDirectory(_:inDomains:)isanarrayofURLs.Itisanarraybecause,inOSX,theremaybemultipleURLsthatmeetthesearchcriteria.IniOS,however,therewillonlybeone(ifthedirectoryyousearchedforisanappropriatesandboxdirectory).Therefore,thenameofthearchivefileisappendedtothefirstandonlyURLinthearray.ThiswillbewherethearchiveofIteminstanceswilllive.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
NSKeyedArchiverandNSKeyedUnarchiverYounowhaveaplacetosavedataonthefilesystemandamodelobjectthatcanbesavedtothefilesystem.Thefinaltwoquestionsare:howdoyoukickoffthesavingandloadingprocesses,andwhendoyoudoit?TosaveinstancesofItem,youwillusetheclassNSKeyedArchiverwhentheapplication“exits.”
InItemStore.swift,implementanewmethodthatcallsarchiveRootObject(_:toFile:)ontheNSKeyedArchiverclass.funcsaveChanges()->Bool{print("Savingitemsto:\(itemArchiveURL.path!)")returnNSKeyedArchiver.archiveRootObject(allItems,toFile:itemArchiveURL.path!)}
ThearchiveRootObject(_:toFile:)methodtakescareofsavingeverysingleIteminallItemstotheitemArchiveURL.Yes,itisthatsimple.HereishowarchiverRootObject(_:toFile:)works:
ThemethodbeginsbycreatinganinstanceofNSKeyedArchiver.(NSKeyedArchiverisaconcretesubclassoftheabstractclassNSCoder.)
ThemethodencodeWithCoder:iscalledonallItemsandispassedtheinstanceofNSKeyedArchiverasanargument.
TheallItemsarraythencallsencodeWithCoder(_:)toalloftheobjectsitcontains,passingthesameNSKeyedArchiver.Thus,allyourinstancesofItemencodetheirinstancevariablesintotheverysameNSKeyedArchiver(Figure15.5).
TheNSKeyedArchiverwritesthedataitcollectedtothepath.
Figure15.5ArchivingtheallItemsarray
WOW! eBook www.wowebook.org
WhentheuserpressestheHomebuttononthedevice,themessageapplicationDidEnterBackground(_:)issenttotheAppDelegate.ThatiswhenyouwanttosendsaveChangestotheItemStore.
OpenAppDelegate.swiftandaddapropertytotheclasstostoretheItemStoreinstance.YouwillneedapropertyinordertoreferencetheinstanceinapplicationDidEnterBackground(_:).classAppDelegate:UIResponder,UIApplicationDelegate{
varwindow:UIWindow?letitemStore=ItemStore()
Thenupdateapplication(_:didFinishLaunchingWithOptions:)tousethispropertyinsteadofthelocalconstant.funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{//Overridepointforcustomizationafterapplicationlaunch.
//CreateanItemStoreletitemStore=ItemStore()
//CreateanImageStoreletimageStore=ImageStore()
//GettheItemsViewControllerletnavController=window!.rootViewControlleras!UINavigationControllerletitemsController=navController.topViewControlleras!ItemsViewController
//SettheitemstoreaswellastheimagestoreitemsController.itemStore=itemStoreitemsController.imageStore=imageStore
returntrue}
Sincethepropertyandthelocalconstantwerenamedthesame,youonlyneededtoremovethecodethatcreatedthelocalconstant.
InAppDelegate.swift,implementapplicationDidEnterBackground(_:)tokickoffsavingtheIteminstances.(Thismethodmayhavealreadybeenimplementedbythetemplate.Ifso,makesuretoaddcodetotheexistingmethodinsteadofwritingabrandnewone.)funcapplicationDidEnterBackground(application:UIApplication){letsuccess=itemStore.saveChanges()if(success){print("SavedalloftheItems")}else{print("CouldnotsaveanyoftheItems")}}
Buildandruntheapplicationonthesimulator.CreateafewinstancesofItem,thenpresstheHomebuttontoleavetheapplication.Checktheconsoleandyoushouldseealogstatementindicatingthattheitemsweresaved.
WhileyoucannotyetloadtheseinstancesofItembackintotheapplication,youcanstillverifythatsomethingwassaved.
Intheconsole’slogstatements,findonethatlogsouttheitemArchiveURLlocationandanotherthatindicateswhethersavingwassuccessful.Ifsavingwasnotsuccessful,
WOW! eBook www.wowebook.org
confirmthatyouritemArchiveURLisbeingcreatedcorrectly.Iftheitemsweresavedsuccessfully,copythepaththatisprintedtotheconsole.
OpenFinderandpressCommand-Shift-G.PastethefilepaththatyoucopiedfromtheconsoleandpressReturn.Youwillbetakentothedirectorythatcontainstheitems.archivefile.PressCommand-Uptonavigatetotheparentdirectoryofitems.archive.Thisistheapplication’ssandboxdirectory.Here,youcanseetheDocuments,Library,andtmpdirectoriesalongsidetheapplicationitself(Figure15.6).
Figure15.6Homepwner’ssandbox
Thelocationofthesandboxdirectorycanchangebetweenrunsoftheapplication;however,thecontentsofthatsandboxwillremainunchanged.Duetothis,youmayneedtocopyandpastethedirectoryintoFinderfrequentlywhileworkingonanapplication.
Loadingfiles
Nowlet’sturntoloadingthesefiles.ToloadinstancesofItemwhentheapplicationlaunches,youwillusetheclassNSKeyedUnarchiverwhentheItemStoreiscreated.
InItemStore.swift,overrideinit()toaddthefollowingcode.init(){ifletarchivedItems=NSKeyedUnarchiver.unarchiveObjectWithFile(itemArchiveURL.path!)as?[Item]{allItems+=archivedItems}}
TheunarchiveObjectWithFile(_:)methodwillcreateaninstanceofNSKeyedUnarchiverandloadthearchivelocatedattheitemArchiveURLintothatinstance.TheNSKeyedUnarchiverwilltheninspectthetypeoftherootobjectinthearchiveandcreateaninstanceofthattype.Inthiscase,thetypewillbeanarrayofItemsbecauseyoucreatedthisarchivewitharootobjectoftype[Item].(IftherootobjectwasaninstanceofIteminstead,thenunarchiveObjectWithFile(_:)wouldreturnanItem.)
Thenewlycreatedarrayisthensentinit(coder:)and,asyoumayhaveguessed,theNSKeyedUnarchiverispassedastheargument.Thearraystartsdecodingitscontents(instancesofItem)fromtheNSKeyedUnarchiverandsendseachoftheseobjectsthemessageinit(coder:),passingthesameNSKeyedUnarchiver.
Buildandruntheapplication.Youritemswillbeavailableuntilyouexplicitlydelete
WOW! eBook www.wowebook.org
them.Onethingtonoteabouttestingyoursavingandloadingcode:ifyoukillHomepwnerfromXcode,themethodapplicationDidEnterBackground(_:)willnotgetachancetobecalledandtheitemarraywillnotbesaved.YoumustpresstheHomebuttonfirstandthenkillitfromXcodebyclickingtheStopbutton.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ApplicationStatesandTransitionsInHomepwner,theitemsarearchivedwhentheapplicationentersthebackgroundstate.Itisusefultounderstandthestatesanapplicationcanbein,whatcausesapplicationstotransitionbetweenstates,andhowyourcodecanbenotifiedofthesetransitions.ThisinformationissummarizedinFigure15.7.
Figure15.7Statesofatypicalapplication
Whenanapplicationisnotrunning,itisinthenotrunningstateanditdoesnotexecuteanycodeorhaveanymemoryreservedinRAM.
Aftertheuserlaunchesanapplication,itenterstheactivestate.Whenintheactivestate,anapplication’sinterfaceisonthescreen,itisacceptingevents,anditscodeishandlingthoseevents.
Whileintheactivestate,anapplicationcanbetemporarilyinterruptedbyasystemeventlikeanSMSmessage,pushnotification,phonecall,oralarm.Anoverlaywillappearontopofyourapplicationtohandlethisevent,andtheapplicationenterstheinactivestate.Intheinactivestate,anapplicationisvisiblebehindtheoverlayandisexecutingcode,butitisnotreceivingevents.Applicationstypicallyspendverylittletimeintheinactivestate.YoucanforceanactiveapplicationintotheinactivestatebypressingtheLockbuttonatthetopofthedevice.Theapplicationwillstayinactiveuntilthedeviceisunlocked.
WhentheuserpressestheHomebuttonorswitchestoanotherapplicationinsomeother
WOW! eBook www.wowebook.org
way,theapplicationentersthebackgroundstate.(Actually,itspendsabriefmomentintheinactivestatebeforetransitioningtothebackgroundstate.)Inthebackgroundstate,anapplication’sinterfaceisnotvisibleorreceivingevents,butitcanstillexecutecode.Bydefault,anapplicationthatentersthebackgroundstatehasabout10secondsbeforeitentersthesuspendedstate.Yourapplicationshouldnotrelyonthisnumber;instead,itshouldsaveuserdataandreleaseanysharedresourcesasquicklyaspossible.
Anapplicationinthesuspendedstatecannotexecutecode.Youcannotseeitsinterface,andanyresourcesitdoesnotneedwhilesuspendedaredestroyed.Asuspendedapplicationisessentiallyflash-frozenandcanbequicklythawedwhentheuserrelaunchesit.Table15.1summarizesthecharacteristicsofthedifferentapplicationstates.
Table15.1Applicationstates
State Visible ReceivesEvents ExecutesCode
NotRunning No No No
Active Yes Yes Yes
Inactive Mostly No Yes
Background No No Yes
Suspended No No No
Youcanseewhatapplicationsareinthebackgroundorsuspendedbydouble-tappingtheHomebuttontogettothetaskswitcher(Figure15.8).YoucandothisinthesimulatorbypressingCommand-Shift-Htwice.(Recentlyrunapplicationsthathavebeenterminatedmayalsoappearinthisdisplay.)
WOW! eBook www.wowebook.org
Figure15.8Backgroundandsuspendedapplicationsinthetaskswitcher
Anapplicationinthesuspendedstatewillremaininthatstateaslongasthereisadequatesystemmemory.WhentheOSdecidesmemoryisgettinglow,itterminatessuspendedapplicationsasneeded.Asuspendedapplicationgetsnoindicationthatitisabouttobeterminated.Itissimplyremovedfrommemory.(Anapplicationmayremaininthetaskswitcherafterithasbeenterminated,butitwillhavetorelaunchwhentapped.)
Whenanapplicationchangesitsstate,amethodiscalledontheapplicationdelegate.HerearesomeofthemethodsfromtheUIApplicationDelegateprotocolthatannounceapplicationstatetransitions.(ThesearealsoshowninFigure15.7.)optionalfuncapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->BooloptionalfuncapplicationDidBecomeActive(application:UIApplication)optionalfuncapplicationWillResignActive(application:UIApplication)optionalfuncapplicationDidEnterBackground(application:UIApplication)optionalfuncapplicationWillEnterForeground(application:UIApplication)
Youcanimplementthesemethodstotaketheappropriateactionsforyourapplication.WOW! eBook
www.wowebook.org
Transitioningtothebackgroundstateisagoodplacetosaveanyoutstandingchangesbecauseitisthelasttimeyourapplicationcanexecutecodebeforeitentersthesuspendedstate.Onceinthesuspendedstate,anapplicationcanbeterminatedatthewhimoftheOS.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
WritingtotheFilesystemwithNSDataYourarchivinginHomepwnersavesandloadstheitemKeyforeachItem,butwhatabouttheimages?Atthemoment,theyarelostwhentheappentersthebackgroundstate.Inthissection,youwillextendtheimagestoretosaveimagesastheyareaddedandfetchthemastheyareneeded.
TheimagesforIteminstancesshouldalsobestoredintheDocumentsdirectory.Youcanusetheimagekeygeneratedwhentheusertakesapicturetonametheimageinthefilesystem.
ImplementanewmethodinImageStore.swiftnamedimageURLForKey(_:)tocreateaURLinthedocumentsdirectoryusingagivenkey.funcimageURLForKey(key:String)->NSURL{
letdocumentsDirectories=NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory,inDomains:.UserDomainMask)letdocumentDirectory=documentsDirectories.first!
returndocumentDirectory.URLByAppendingPathComponent(key)}
Tosaveandloadanimage,youaregoingtocopytheJPEGrepresentationoftheimageintoabufferinmemory.Insteadofjustcreatingabuffer,Swiftprogrammershaveahandyclasstocreate,maintain,anddestroythesesortsofbuffers–NSData.AnNSDatainstanceholdssomenumberofbytesofbinarydata,andyouwilluseNSDatatostoreimagedata.
InImageStore.swift,modifysetImage(_:forKey:)togetaURLandsavetheimage.funcsetImage(image:UIImage,forKeykey:String){cache.setObject(image,forKey:key)
//CreatefullURLforimageletimageURL=imageURLForKey(key)
//TurnimageintoJPEGdataifletdata=UIImageJPEGRepresentation(image,0.5){//WriteittofullURLdata.writeToURL(imageURL,atomically:true)}}
Let’sexaminethiscodemoreclosely.ThefunctionUIImageJPEGRepresentationtakestwoparameters:aUIImageandacompressionquality.ThecompressionqualityisaFloatfrom0to1,where1isthehighestquality(leastcompression).ThefunctionreturnsaninstanceofNSDataifthecompressionsucceedsandnilifitdoesnot.
ThisNSDatainstancecanbewrittentothefilesystembycallingwriteToURL(_:atomically:).ThebytesheldinthisNSDataarethenwrittentotheURLspecifiedbythefirstparameter.Thesecondparameter,atomically,isaBooleanvalue.Ifitistrue,thefileiswrittentoatemporaryplaceonthefilesystem,and,oncethewritingoperationiscomplete,thatfileisrenamedtotheURLofthefirstparameter,replacinganypreviouslyexistingfile.Writingatomicallypreventsdata
WOW! eBook www.wowebook.org
corruptionshouldyourapplicationcrashduringthewriteprocedure.
Itisworthnotingthatthiswayofwritingdatatothefilesystemisnotarchiving.WhileNSDatainstancescanbearchived,usingthemethodwriteToURL(_:atomically:)copiesthebytesintheNSDatadirectlytothefilesystem.
Nowthattheimageisstoredinthefilesystem,theImageStorewillneedtoloadthatimagewhenitisrequested.TheclassmethodimageWithContentsOfFile(_:)ofUIImagewillreadinanimagefromafile,givenaURL.
InImageStore.swift,updatethemethodimageForKey(_:)sothattheImageStorewillloadtheimagefromthefilesystemifitdoesnotalreadyhaveit.funcimageForKey(key:String)->UIImage?{
returncache.objectForKey(key)as?UIImage
ifletexistingImage=cache.objectForKey(key)as?UIImage{returnexistingImage}
letimageURL=imageURLForKey(key)guardletimageFromDisk=UIImage(contentsOfFile:imageURL.path!)else{returnnil}
cache.setObject(imageFromDisk,forKey:key)returnimageFromDisk}
Whatisthatguardstatement?guardisaconditionalstatement,likeanifstatement.Thecompilerwillonlycontinuepasttheguardstatementiftheconditionwithintheguardistrue.Here,theconditioniswhethertheUIImageinitializationissuccessful.Iftheinitializationfails,theelseblockisexecuted,whichallowsyoutohaveanearlyreturn.Iftheinitializationsucceeds,anyvariablesorconstantsboundintheguardstatement(here,imageFromDisk)areusableaftertheguardstatement.
Thecodeaboveisfunctionallyequivalenttothefollowingcode:ifletimageFromDisk=UIImage(contentsOfFile:imageURL.path!){cache.setObject(imageFromDisk,forKey:key)returnimageFromDisk}
returnnil
Whileyoucoulddothis,guardprovidesbothacleaner–and,moreimportantly,asafer–waytoensurethatyouexitifdonothavewhatyouneed.
Usingguardalsoforcesthefailurecasetobedirectlytiedtotheconditionbeingchecked.Thismakesthecodemorereadableandeasiertoreasonabout.
Youareabletosaveanimagetodiskandretrieveanimagefromdisk,sothelastthingyouneedtodoisaddfunctionalitytoremoveanimagefromdisk.
InImageStore.swift,makesurethatwhenanimageisdeletedfromthestore,itisalsodeletedfromthefilesystem.(Youwillseeanerrorwhenyoutypeinthiscode,whichwewilldiscussnext.)funcdeleteImageForKey(key:String){
WOW! eBook www.wowebook.org
cache.removeObjectForKey(key)
letimageURL=imageURLForKey(key)NSFileManager.defaultManager().removeItemAtURL(imageURL)}
Let’stakealookattheerrormessagethatthiscodegenerated,showninFigure15.9.
Figure15.9Errorwhenremovingtheimagefromdisk
ThiserrormessageislettingyouknowthatthemethodremoveItemAtURL(_:)canfail,butyouarenothandlingtheerror.Let’sfixthis.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ErrorHandlingItisoftenusefultohaveawayofrepresentingthepossibilityoffailurewhencreatingmethods.Youhaveseenonewayofrepresentingfailurethroughoutthisbookwiththeuseofoptionals.Optionalsprovideasimplewaytorepresentfailurewhenyoudonotcareaboutthereasonforfailure.ConsiderthecreationofanIntfromaString.lettheMeaningOfLife="42"letnumberFromString=Int(theMeaningOfLife)
ThisinitializeronInttakesaStringparameterandreturnsanoptionalInt(anInt?).ThisisbecausethestringmaynotbeabletoberepresentedasanInt.ThecodeabovewillsuccessfullycreateanInt,butthefollowingcodewillnot:letpi="ApplePie"letnumberFromString=Int(pi)
Thestring“ApplePie”cannotberepresentedasanInt,sonumberFromStringwillcontainnil.Anoptionalworkswellforrepresentingfailureherebecauseyoudonotcarewhyitfailed.Youjustwanttoknowwhetheritwassuccessful.
Whenyouneedtoknowwhysomethingfailed,anoptionalwillnotprovideenoughinformation.Thinkaboutremovingtheimagefromthefilesystem–whymightitfail?PerhapsthereisnofileatthespecifiedURL,ortheURLismalformed,oryoudonothavepermissiontoremovethatfile.Thereareanumberofreasonsthismethodcouldfail,andyoumightwanttohandleeachcasedifferently.
Swiftprovidesaricherrorhandlingsystemwithcompilersupporttoensurethatyourecognizewhensomethingbadcouldhappen.YoualreadysawthiswhentheSwiftcompilertoldyouthatyouwerenothandlingapossibleerrorwhenremovingthefilefromdisk.
Ifamethodcouldgenerateanerror,itsmethodsignatureneedstoindicatethisusingthethrowskeyword.HereisthemethoddefinitionforremoveItemAtURL(_:):funcremoveItemAtURL(URL:NSURL)throws
Thethrowskeywordindicatesthatthismethodcouldthrowanerror.(Ifyouarefamiliarwiththrowingexceptioninotherlanguages,Swift’serrorhandlingisnotthesameasthrowingexception.)Byusingthiskeyword,thecompilerensuresthatanyonewhousesthismethodknowsthatthismethodcanthrowanerror–and,moreimportantly,thatthecalleralsohandlesanypotentialerrors.Thisishowthecompilerwasabletoletyouknowthatyouarenothandlingerrorswhenattemptingtoremoveafilefromdisk.
Tocallamethodthatcanthrow,youuseado-catchstatement.Withinthedoblock,youannotateanymethodsthatmightthrowanerrorusingthetrykeywordtoreinforcetheideathatthecallmightfail.
InImageStore.swift,updatedeleteImageForKey(_:)tocallremoveItemAtURL(_:)usingado-catchstatement.funcdeleteImageForKey(key:String){cache.removeObjectForKey(key)
letimageURL=imageURLForKey(key)
WOW! eBook www.wowebook.org
NSFileManager.defaultManager().removeItemAtURL(imageURL)do{tryNSFileManager.defaultManager().removeItemAtURL(imageURL)}catch{
}}
Ifamethoddoesthrowanerror,thentheprogramimmediatelyexitsthedoblock;nofurthercodeinthedoblockisexecuted.Atthatpoint,theerrorispassedtothecatchblockforittobehandledinsomeway.
UpdatedeleteImageForKey(_:)toprintouttheerrortotheconsole.funcdeleteImageForKey(key:String){cache.removeObjectForKey(key)
letimageURL=imageURLForKey(key)do{tryNSFileManager.defaultManager().removeItemAtURL(imageURL)}catch{print("Errorremovingtheimagefromdisk:\(error)")}}
Withinthecatchblock,thereisanimpliciterrorconstantthatcontainsinformationdescribingtheerror.Youcanoptionallygivethisconstantanexplicitname.
UpdatedeleteImageForKey(_:)touseanexplicitnamefortheerrorbeingcaught.funcdeleteImageForKey(key:String){cache.removeObjectForKey(key)
letimageURL=imageURLForKey(key)do{tryNSFileManager.defaultManager().removeItemAtURL(imageURL)}catch{print("Errorremovingtheimagefromdisk:\(error)")}catchletdeleteError{print("Errorremovingtheimagefromdisk:\(deleteError)")}}
Thereisalotmorethatyoucandowitherrorhandling,butthisisthebasicknowledgethatyouneedfornow.Wewillcovermoredetailsasyouprogressthroughthisbook.
BuildandruntheapplicationnowthattheImageStoreiscomplete.TakeaphotoforanitemandexittheapplicationtotheHomescreen(onthesimulator,selectHardware→HomeorpressShift-Command-H;onahardwaredevicesimplypresstheHomebutton).Launchtheapplicationagain.Selectingthatsameitemwillshowallitssaveddetails–includingthephotoyoujusttook.
Noticethattheimagesaresavedimmediatelyafterbeingtaken,whiletheinstancesofItemaresavedonlywhentheapplicationentersthebackground.Yousavetheimagesrightawaybecausetheyarejusttoobigtokeepinmemoryforlong.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:PNGInsteadofsavingeachimageasaJPEG,saveitasaPNG.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:ApplicationStateTransitionsLet’swritesomequickcodetogetabetterunderstandingofthedifferentapplicationstatetransitions.
InAppDelegate.swift,implementtheapplicationstatetransitiondelegatemethodssothattheyprintoutthenameofthemethod.Youwillneedtoaddfourmoremethods.(Checktomakesurethetemplatehasnotalreadycreatedthesemethodsbeforewritingbrandnewones.)Ratherthanhardcodingthenameofthemethodintheprint,usethe__FUNCTION__expression.Atcompiletime,the__FUNCTION__expressionwillevaluatetoaStringrepresentingthenameofthemethod.funcapplicationWillResignActive(application:UIApplication){print(__FUNCTION__)}
funcapplicationDidEnterBackground(application:UIApplication){print(__FUNCTION__)
letsuccess=itemStore.saveChanges()if(success){print("SavedalloftheItems")}else{print("CouldnotsaveanyoftheItems")}}
funcapplicationWillEnterForeground(application:UIApplication){print(__FUNCTION__)}
funcapplicationDidBecomeActive(application:UIApplication){print(__FUNCTION__)}
funcapplicationWillTerminate(application:UIApplication){print(__FUNCTION__)}
Now,addthesameprint()statementtothetopofapplication(_:didFinishLaunchingWithOptions:).funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{print(__FUNCTION__)...}
Buildandruntheapplication.Youwillseethattheapplicationgetssentapplication(_:didFinishLaunchingWithOptions:)andthenapplicationDidBecomeActive(_:).Playaroundtoseewhatactionscausewhattransitions.
PresstheHomebuttonandtheconsolewillreportthattheapplicationbrieflyinactivatedandthenwentintothebackgroundstate.RelaunchtheapplicationbytappingitsiconontheHomescreenorinthetaskswitcher.Theconsolewillreportthattheapplicationenteredtheforegroundandthenbecameactive.
PresstheHomebuttontoexittheapplicationagain.Then,double-presstheHomebuttontoopenthetaskswitcher.SwipetheHomepwnerapplicationupandoffthisdisplaytoquit
WOW! eBook www.wowebook.org
theapplication.Notethatnomethodiscalledonyourapplicationdelegateatthispoint–itissimplyterminated.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:ReadingandWritingtotheFilesystemInadditiontoarchivingandNSData’sbinaryreadandwritemethods,thereareafewmoremethodsfortransferringdatatoandfromthefilesystem.Oneofthem,CoreData,iscomingupinChapter21.Acoupleothersareworthmentioninghere.
UsingNSDataworkswellforbinarydata.Fortextdata,Stringhastwoinstancemethods:writeToURL(_:atomically:encoding:)andinit(contentsOfURL:encoding:).Theyareusedasfollows://SavesomeStringtothefilesystemdo{trysomeString.writeToURL(someURL,atomically:true,encoding:NSUTF8StringEncoding)}catch{print("ErrorwritingtoURL:\(error)")}
//LoadsomeStringfromthefilesystemdo{letmyEssay=tryString(contentsOfURL:someURL,encoding:NSUTF8StringEncoding)print(myEssay)}catch{print("ErrorreadingfromURL:\(error)")}
Notethatinmanylanguages,anythingunexpectedresultsinanexceptionbeingthrown.InSwift,exceptionsarenearlyalwaysusedtoindicateprogrammererror.Whenanexceptionisthrown,theinformationaboutwhatwentwrongisinanNSExceptionobject.Thatinformationisusuallyjustahinttotheprogrammer,like,“Youtriedtoaccessthe7thobjectinthisarray,butthereareonlytwo.”Thesymbolsforthecallstack(asitappearedwhentheexceptionwasthrown)arealsointheNSException.
Whendoyouuseexceptions,andwhendoyouuseerrorhandling?Ifyouarewritingamethodthatshouldonlybecalledwithanoddnumberasanargument,throwanexceptionifitiscalledwithanevennumber–thecallerismakinganerrorandyouwanttohelpthatprogrammerfindtheerror.Ifyouarewritingamethodthatwantstoreadthecontentsofaparticulardirectorybutdoesnothavethenecessaryprivileges,useSwift’serrorhandlingandthrowanerrortothecallertoindicatewhyyouwereunabletofulfillthisveryreasonablerequest.
Propertylistserializabletypescanalsobewrittentothefilesystem.TheonlytypesthatarepropertylistserializableareString,NSNumber(includingprimitiveslikeInt,Double,andBool),NSDate,NSData,Array<T>,andDictionary<K,V>.WhenanArray<T>orDictionary<K,V>iswrittentothefilesystemwiththesemethods,anXMLpropertylistiscreated.AnXMLpropertylistisacollectionoftaggedvalues:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEplistPUBLIC"-//Apple//DTDPLIST1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plistversion="1.0"><array><dict>
WOW! eBook www.wowebook.org
<key>firstName</key><string>Christian</string><key>lastName</key><string>Keur</string></dict><dict><key>firstName</key><string>Aaron</string><key>lastName</key><string>Hillegass</string></dict></array></plist>
XMLpropertylistsareaconvenientwaytostoredatabecausetheycanbereadonnearlyanysystem.Manywebserviceapplicationsusepropertylistsasinputandoutput.Thecodeforwritingandreadingapropertylistlookslikethis:letauthors:AnyObject=[["firstName":"Christian","lastName":"Keur"],["firstName":"Aaron","lastName":"Hillegass"]]
//WritearraytodiskifNSPropertyListSerialization.propertyList(authors,isValidForFormat:.XMLFormat_v1_0){do{letdata=tryNSPropertyListSerialization.dataWithPropertyList(authors,format:.XMLFormat_v1_0,options:0)data.writeToURL(url,atomically:true)}catch{print("Errorwritingplist:\(error)")}}
//Readarrayfromdiskdo{letdata=tryNSData(contentsOfURL:url,options:[])letauthors=tryNSPropertyListSerialization.propertyListWithData(data,options:[],format:nil)print("Readinauthors:\(authors)")}catch{print("Errorreadingplist:\(error)")}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:TheApplicationBundleWhenyoubuildaniOSapplicationprojectinXcode,youcreateanapplicationbundle.Theapplicationbundlecontainstheapplicationexecutableandanyresourcesyouhavebundledwithyourapplication.Resourcesarethingslikestoryboardfiles,images,andaudiofiles–anyfilesthatwillbeusedatruntime.Whenyouaddaresourcefiletoaproject,Xcodeissmartenoughtorealizethatitshouldbebundledwithyourapplication.
Howcanyoutellwhichfilesarebeingbundledwithyourapplication?SelecttheHomepwnerprojectfromtheprojectnavigator.CheckouttheBuildPhasespaneintheHomepwnertarget.EverythingunderCopyBundleResourceswillbeaddedtotheapplicationbundlewhenitisbuilt.
EachitemintheHomepwnertargetgroupisoneofthephasesthatoccurswhenyoubuildaproject.TheCopyBundleResourcesphaseiswherealloftheresourcesinyourprojectgetcopiedintotheapplicationbundle.
Youcancheckoutwhatanapplicationbundlelookslikeonthefilesystemafteryouinstallanapplicationonthesimulator.Printtheapplicationbundlepathtotheconsoleandthennavigatetothatdirectory.print(NSBundle.mainBundle().bundlePath)
Control-clicktheapplicationbundleandchooseShowPackageContentsfromthecontextualmenu(Figure15.10).
Figure15.10Viewinganapplicationbundle
AFinderwindowwillappearshowingyouthecontentsoftheapplicationbundle
WOW! eBook www.wowebook.org
(Figure15.11).WhenusersdownloadyourapplicationfromtheAppStore,thesefilesarecopiedtotheirdevices.
Figure15.11Theapplicationbundle
Youcanloadfilesfromtheapplication’sbundleatruntime.TogetthefullURLforfilesintheapplicationbundle,youneedtogetareferencetotheapplicationbundleandthenaskitfortheURLofaresource.//GetareferencetotheapplicationbundleletapplicationBundle=NSBundle.mainBundle()
//AskfortheURLtoaresourcenamedmyImage.pnginthebundleifleturl=applicationBundle.URLForResource("myImage",ofType:"png"){//DosomethingwithURL}
IfyouaskfortheURLtoafilethatisnotintheapplication’sbundle,thismethodwillreturnnil.Ifthefiledoesexist,thenthefullURLisreturned,andyoucanusethisURLtoloadthefilewiththeappropriateclass.
Bearinmindthatfileswithintheapplicationbundleareread-only.Youcannotmodifythem,norcanyoudynamicallyaddfilestotheapplicationbundleatruntime.Filesintheapplicationbundlearetypicallythingslikebuttonimages,interfacesoundeffects,ortheinitialstateofadatabaseyoushipwithyourapplication.Youwillusethismethodinlaterchapterstoloadthesetypesofresourcesatruntime.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
16SizeClasses
Often,youwantanapplication’sinterfacetohaveadifferentlayoutdependingonthedimensionsandorientationofthescreen.Inthischapter,youwillmodifytheinterfaceforDetailViewControllerinHomepwnersothatwhenitappearsonascreenthathasarelativelysmallheight,thesetoftextfieldsandtheimageviewwillbesidebysideinsteadofstackedontopofoneanother(Figure16.1).
Figure16.1TwopossiblelayoutsforHomepwner’sDetailViewController
Therelativesizesofscreensaredefinedinsizeclasses.Asizeclassrepresentsarelativeamountofscreenspaceinagivendimension.Eachdimension(widthandheight)caneitherbecompactorregular,sotherearefourpossiblecombinationsofsizeclasses:
CompactWidth|CompactHeight
iPhoneswith3.5-,4-,or4.7-inchscreensinlandscapeorientationCompactWidth|RegularHeight
iPhonesofallsizesinportraitorientationRegularWidth|CompactHeight
iPhoneswith5.5-inchscreensinlandscapeorientation
WOW! eBook www.wowebook.org
RegularWidth|RegularHeight
iPadsofallsizesinallorientations
Noticethatthesizeclassescoverbothscreensizesandorientations.Insteadofthinkingaboutinterfacesintermsoforientationordevice,itisbettertothinkintermsofsizeclasses.
Ifyouwantalayouttocovermultiplesizeclasses,thenyoucanusethewildcardAny.AnyWidth|AnyHeightisthedefaultandthemostgeneric.WhenyoueditintheAnyWidth|AnyHeightconfiguration,youareprovidinganinterfacethatiscommonacrossallsizeclasses.ForDetailViewController,youwantalayoutspecifictoalliPhonesinlandscapeorientation,soyouaregoingtodefineitforAnyWidth|CompactHeight.
OpenHomepwner.xcodeprojandMain.storyboard.Selectthe tabtoopenthefileinspector(Command-Option-1)andconfirmthatthecheckboxforUseSizeClassesischecked(Figure16.2).
Figure16.2Sizeclassesenabled
WOW! eBook www.wowebook.org
AnotherSizeClassWheneditingtheinterfaceforaspecificsizeclasscombination,youareabletochange:
propertiesformanyviews
whetheraspecificsubviewisinstalled
whetheraspecificconstraintisinstalled
theconstantofaconstraint
thefontforsubviewsthatdisplaytext
InHomepwner,youaregoingtofocusonthefirstiteminthatlist–adjustingviewpropertiesdependingonthecurrentsizeclassconfiguration.Thegoalistohavetheimageviewbeontherightsideofthelabelsandtextfieldsinacompactheightenvironment.Inaregularheightenvironment,theimageviewwillbebelowthelabelsandtextfields(asitcurrentlyis).Stackviewswillmakethisremarkablyeasy.
Tobegin,youaregoingtoembedtheexistingverticalstackviewwithinanotherstackview.Thiswillmakeiteasytoaddanimageviewtotherightsideofthelabelsandtextfields.Selecttheverticalstackviewandclickthe icontoembedthisstackviewwithinanotherstackview.Withthisnewstackviewselected,opentheAutoLayoutPinmenuandconfigureitasseeninFigure16.3.
Figure16.3Stackviewconstraints
Next,openthenewstackview’sattributesinspector.IncreasetheSpacingtobe8.
Nowyouaregoingtomovetheimageviewfromtheinnerstackviewtotheouterstack
WOW! eBook www.wowebook.org
viewthatyoujustcreated.Thisishowyouwillbeabletohavetheimageviewontherightsideoftherestoftheinterface:inacompactheightenvironment,thestackviewwillsettobehorizontalandtheimageviewwilltakeuptherightsideoftheinterface.
Movingtheimageviewfromonestackviewtotheothercanbealittletricky,soyouaregoingtodoitinafewsteps.
OpenthedocumentoutlineandexpandthesectionfortheDetailViewControllerScene.ExpandtheoutertwostackviewsasseeninFigure16.4.
Figure16.4Expandingthedocumentoutline
DragtheImageViewrightabovethestackviewthatitiscurrentlycontainedwithin(Figure16.5).Thiswillmoveitfromtheinnerstackviewtotheouterstackview.
WOW! eBook www.wowebook.org
Figure16.5Movingtheimageviewtotheouterstackview
Finally,collapsetheinnerstackviewanddragtheImageViewtobebelowitinthestack(Figure16.6).MakesuretheImageViewisindentedatthesamelevelastheinnerstackview.Youmayneedtoupdateframesatthispointtogetridofanywarnings.
Figure16.6Movingtheimageviewbelowtheinnerstackview
Buildandruntheapplication.Confirmthatthebehaviorofthestackviewisunchanged.
Atthispoint,youhaveupdatedeverythingthatiscommontoallsizeclasses.Nextyouwillmodifyspecificsizeclassestochangethelayoutofthecontent.
AtthebottomofInterfaceBuilder,clickonwAnyhAny.Thepop-upthatappearsallowsyoutoselectasizeclass.Theinterfacecanbeconfusing.Thetrickisthatthebottom-rightsquarecoveredbytheblueareatellsyouthesizeclass.Currently,theblueareastretchesrightanddowntodeadcenter,andthelabelatthetopreadsAny(Figure16.7).
WOW! eBook www.wowebook.org
Figure16.7Choosingasizeclasstoedit
Dragtoresizetheblueareatoseetheothersizeclasses.FindandselectCompactHeight(thetop-centerboxinthegrid).Noticethattheviewcontrollerisshorter(Figure16.8).Additionally,thebottomofInterfaceBuilderhasabluebartoremindyouthatyouarenolongereditingthelayoutforallsizeclasses.
WOW! eBook www.wowebook.org
Figure16.8DetailViewControllerintheAny|Compactenvironment
Whatyouwillnowdoisupdatethepropertiesfortheouterstackviewfortheimageviewtobeontherightsideoftherestoftheinterface.
Selecttheouterstackviewandopenitsattributesinspector.UndertheStackViewheading,findtheAxispropertyandclickthe+buttononitsleftside.Fromthepop-upmenu,chooseAnyWidth,thenCompactHeight(Figure16.9).Thiswillallowyoutocustomizethispropertyforjustthecurrentsizeclassconfiguration.
Figure16.9Addingasize-class-specificoption
Forthenewoption(wAnyhC),chooseHorizontal(Figure16.10).Now,whenevertheinterfacehasacompactheight,theouterstackviewwillhaveahorizontalconfiguration.Whentheinterfacehasaregularheight,theouterstackviewwillhaveaverticalconfiguration.
WOW! eBook www.wowebook.org
Figure16.10Customizingtheaxis
Thelastchangeyouwanttomakeisfortheinnerstackviewandtheimageviewtofilltheouterstackviewequally.Todothis,youwillcustomizetheouterstackview’sdistribution.
Withtheattributesinspectorstillopenfortheouterstackview,clickonthe+nexttoDistributionandselectAnyWidth|CompactHeight(current)fromthepop-upmenu.ChangethedistributionforthissizeclasstobeFillEqually(Figure16.11).
Figure16.11Customizingthedistribution
BuildandruntheapplicationoniPhone.Createanitemanddrilldowntoitsdetailstoaddaphoto.Rotatebetweenportraitandlandscapeandnoticehowtheinterfaceislaidoutasyouspecifiedforbothregularandcompactheight.
Withthat,yourHomepwnerapplicationiscomplete.Youhavebuiltanappwithaflexibleinterfacethatcantakephotosandstoredata,andwehopeyouareproudofyouraccomplishment!Takesometimetocelebrate.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:StackedTextFieldandLabelsInacompactheightenvironment,makeitsothetextfieldsandlabelsarestackedverticallyinsteadofhorizontally(Figure16.12).
Figure16.12Textfieldsandlabelsstacked
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
17TouchEventsandUIResponder
Inthenexttwochapters,youwillcreateTouchTracker,anappthatletstheuserdrawbytouchingthescreen.Inthischapter,youwillcreateaviewthatdrawslinesinresponsetotheuserdraggingacrossit(Figure17.1).Usingmultitouch,theuserwillbeabletodrawmorethanonelineatatime.
Figure17.1TouchTracker
WOW! eBook www.wowebook.org
TouchEventsAsasubclassofUIResponder,aUIViewcanoverridefourmethodstohandlethefourdistincttouchevents:
afingerorfingerstouchesthescreenfunctouchesBegan(touches:Set<UITouch>,withEventevent:UIEvent?)
afingerorfingersmovesacrossthescreen(thismessageissentrepeatedlyasafingermoves)functouchesMoved(touches:Set<UITouch>,withEventevent:UIEvent?)
afingerorfingersisremovedfromthescreenfunctouchesEnded(touches:Set<UITouch>,withEventevent:UIEvent?)
asystemevent,likeanincomingphonecall,interruptsatouchbeforeitendsfunctouchesCancelled(touches:Set<UITouch>?,withEventevent:UIEvent?)
Let’swalkthroughthetypicallifecycleofatouch.Whentheuser’sfingertouchesthescreen,aninstanceofUITouchiscreated.ThetouchesBegan(_:withEvent:)methodiscalledontheUIViewthatthefingertouched,andtheUITouchispassedinthroughtheSetoftouches.
Asthefingermovesaroundthescreen,thetouchobjectisupdatedtocontainthecurrentlocationofthefingeronthescreen.Then,thesameUIViewthatthetouchbeganonissentthemessagetouchesMoved(_:withEvent:).TheSetthatispassedasanargumenttothismethodcontainsthesameUITouchthatoriginallywascreatedwhenthefingeritrepresentstouchedthescreen.
Whenthefingerisremovedfromthescreen,thetouchobjectisupdatedonelasttimetocontainthecurrentlocationofthefingerandtheviewthatthetouchbeganonissentthemessagetouchesEnded(_:withEvent:).Afterthatmethodfinishesexecuting,theUITouchobjectisdestroyed.
Fromthisinformation,youcandrawafewconclusionsabouthowtouchobjectswork:
OneUITouchcorrespondstoonefingeronthescreen.Thistouchobjectlivesaslongasthefingerisonthescreenandalwayscontainsthecurrentpositionofthefingeronthescreen.
Theviewthatthefingerstartedonwillreceiveeverytoucheventmessageforthatfinger.EvenifthefingermovesbeyondtheframeoftheUIViewthatthetouchbeganon,thetouchesMoved(_:withEvent:)andtouchesEnded(_:withEvent:)methodswillstillbecalledonthatview.Thus,ifatouchbeginsonaview,thenthatviewownsthetouchforthelifeofthetouch.
Youdonothaveto–norshouldyouever–keepareferencetoaUITouchobject.Theapplicationwillgiveyouaccesstoatouchobjectviathe
WOW! eBook www.wowebook.org
UIRespondermethodscalledatthedistinctpointsinthetouch’slifecycle.
Everytimeatouchdoessomething–likebegins,moves,orends–atoucheventisaddedtoaqueueofeventsthattheUIApplicationobjectmanages.Inpractice,thequeuerarelyfillsup,andeventsaredeliveredimmediately.ThedeliveryofthesetoucheventsinvolvessendingoneoftheUIRespondermessagestotheviewthatownsthetouch.
Whataboutmultipletouches?Ifmultiplefingersdothesamethingattheexactsametimetothesameview,allofthesetoucheventsaredeliveredatonce.Eachtouchobject–oneforeachfinger–isincludedintheSetpassedasanargumentintheUIRespondermessages.However,thewindowofopportunityforthe“exactsametime”isfairlyshort.So,insteadofonerespondermessagewithallofthetouches,thereareusuallymultiplerespondermessageswithoneormoreofthetouches.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingtheTouchTrackerApplicationNowlet’sgetstartedwithyourapplication.InXcode,createanewSingleViewUniversalprojectandnameitTouchTracker(Figure17.2).
Figure17.2CreatingTouchTracker
InbuildingTouchTracker,youaregoingtousethedefaultviewcontrollerandthestoryboardthatthetemplatecreated.Foritsviewandmodellayers,youaregoingtocreateacustomviewclassandacustomstructure.Figure17.3showsthemajorpiecesofTouchTracker.
WOW! eBook www.wowebook.org
Figure17.3ObjectdiagramforTouchTracker
Let’sbeginwithyourcustomstruct.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingtheLineStructYouaregoingtocreatethecustomLinetype.Sofar,allofthetypesthatyouhavecreatedhavebeenclasses.Infact,theyhavebeenCocoaTouchsubclasses;forexample,youhavecreatedsubclassesofNSObject,UIViewController,andUIView.
Linewillbeastruct.Youhaveusedstructsthroughoutthisbook–CGRect,CGSize,andCGPointareallstructs.SotooareString,Int,Array,andDictionary.Nowyouaregoingtocreateoneofyourown.
CreateanewSwiftfilenamedLine.
InLine.swift,importCoreGraphicsanddeclaretheLinestruct.DeclaretwoCGPointpropertiesthatwilldeterminethebeginningandendingpointfortheline.importFoundationimportCoreGraphics
structLine{
varbegin=CGPoint.zerovarend=CGPoint.zero}
Structs
Structsdifferfromclassesinanumberofways:
Structsdonotsupportinheritance.
Structsgetamember-wiseinitializerifnootherinitializersaredeclared.Themember-wiseinitializertakesinanargumentforeachpropertywithinthetype.TheLinestruct,forexample,hasthemember-wiseinitializerinit(begin:CGPoint,end:CGPoint).
Ifallpropertieshavedefaultvaluesandnootherinitializersaredeclared,structsalsogainanemptyinitializer(init())thatcreatesaninstanceandsetsallofthepropertiestotheirdefaultvalue.
Perhapsmostimportantly,structs(andenums)arevaluetypes–asopposedtoclasses,whicharereferencetypes.
Valuetypesvs.referencetypes
Valuetypesaretypeswhosevaluesarecopiedwhentheyareassignedtoanotherinstanceorpassedintheargumentofafunction.Thismeansthatassigninganinstanceofavaluetypetoanotheractuallyassignsacopyofthefirstinstancetothesecondinstance.ValuetypesplayanimportantroleinSwift.Forexample,arraysanddictionariesarebothvaluetypes.Allenumsandstructsyouwritearevaluetypesaswell.
Referencetypesarenotcopiedwhentheyareassignedtoaninstanceorpassedintoanargumentofafunction.Instead,areferencetothesameinstanceispassed.Classesand
WOW! eBook www.wowebook.org
closuresarereferencetypes.
Sowhichdoyouchoose?Ingeneral,wesuggeststartingoutwithavaluetypeunlessyouabsolutelyknowyouneedthebenefitsofareferencetype.Valuetypesareeasiertoreasonaboutbecauseyoudonotneedtoworryaboutwhathappenstoaninstancewhenyouchangevaluesonacopy.Ifyouwouldlikeadeeperdiscussiononthistopic,youshouldcheckoutSwiftProgramming:TheBigNerdRanchGuide.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingDrawViewInadditiontoacustomstruct,TouchTrackerneedsacustomview.
CreateanewSwiftfilenamedDrawView.InDrawView.swift,definetheDrawViewclass.Addtwoproperties:anoptionalLinetokeeptrackofalinethatispossiblybeingdrawnandanarrayofLinestokeeptrackoflinesthathavebeendrawn.importFoundationimportUIKit
classDrawView:UIView{
varcurrentLine:Line?varfinishedLines=[Line]()
}
AninstanceofDrawViewwillbetheviewoftheapplication’srootViewController,thedefaultViewControllerincludedintheproject.TheviewcontrollerneedstoknowthatitsviewwillbeaninstanceofDrawView.
OpenMain.storyboard.SelecttheViewandopentheidentityinspector(Command-Option-3).UnderCustomClass,changetheClasstoDrawView(Figure17.4).
Figure17.4Changingtheviewclass
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DrawingwithDrawViewAninstanceofDrawViewneedstobeabledrawlines.YouaregoingtowriteamethodthatusesUIBezierPathtocreateandstrokeapathbasedonthepropertiesofagivenLine.ThenyouwilloverridedrawRect(_:)todrawthelinesinthearrayoffinishedlinesaswellasthecurrentline,ifany.
InDrawView.swift,implementthemethodforstrokinglinesandoverridedrawRect(_:).varcurrentLine:Line?varfinishedLines=[Line]()
funcstrokeLine(line:Line){letpath=UIBezierPath()path.lineWidth=10path.lineCapStyle=CGLineCap.Round
path.moveToPoint(line.begin)path.addLineToPoint(line.end)path.stroke()}
overridefuncdrawRect(rect:CGRect){//DrawfinishedlinesinblackUIColor.blackColor().setStroke()forlineinfinishedLines{strokeLine(line)}
ifletline=currentLine{//Ifthereisalinecurrentlybeingdrawn,doitinredUIColor.redColor().setStroke()strokeLine(line)}}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TurningTouchesintoLinesAlineisdefinedbytwopoints.YourLinestoresthesepointsaspropertiesnamedbeginandend.Whenatouchbegins,youwillcreateaLineandsetbothofitspropertiestothepointwherethetouchbegan.Whenthetouchmoves,youwillupdatetheLine’send.Whenthetouchends,youwillhaveyourcompleteLine.
InDrawView.swift,implementtouchesBegan(_:withEvent:)tocreateanewline.overridefunctouchesBegan(touches:Set<UITouch>,withEventevent:UIEvent?){lettouch=touches.first!
//Getlocationofthetouchinview'scoordinatesystemletlocation=touch.locationInView(self)
currentLine=Line(begin:location,end:location)
setNeedsDisplay()}
Thiscodefirstfiguresoutthelocationofthetouchwithintheview’scoordinatesystem.ThenitcallssetNeedsDisplay(),whichflagstheviewtoberedrawnattheendoftherunloop.
Next,stillinDrawView.swift,implementtouchesMoved(_:withEvent:)sothatitupdatestheendofthecurrentLine.overridefunctouchesMoved(touches:Set<UITouch>,withEventevent:UIEvent?){lettouch=touches.first!letlocation=touch.locationInView(self)
currentLine?.end=location
setNeedsDisplay()}
Finally,inDrawView.swift,updatetheendlocationofthecurrentLineandaddittothefinishedLinesarraywhenthetouchends.overridefunctouchesEnded(touches:Set<UITouch>,withEventevent:UIEvent?){ifvarline=currentLine{lettouch=touches.first!letlocation=touch.locationInView(self)line.end=location
finishedLines.append(line)}currentLine=nil
setNeedsDisplay()}
Buildandruntheapplicationanddrawsomelinesonthescreen.Whileyouaredrawing,thelineswillappearinred.Oncefinished,theywillappearinblack.
Handlingmultipletouches
Whendrawinglines,youmayhavenoticedthathavingmorethanonefingeronthescreendoesnotdoanything–thatis,youcanonlydrawonelineatatime.Let’supdate
WOW! eBook www.wowebook.org
DrawViewsothatyoucandrawasmanylinesasyoucanfitfingersonthescreen.
Bydefault,aviewwillonlyacceptonetouchatatime.IfonefingerhasalreadytriggeredtouchesBegan(_:withEvent:)buthasnotfinished–andthereforehasnottriggeredtouchesEnded(_:withEvent:)–subsequenttouchesareignored.Inthiscontext,“ignored”meansthatneithertouchesBegan(_:withEvent:)noranyotherUIRespondermethodrelatedtotheextratoucheswillbecalledontheDrawView.
InMain.storyboard,selecttheDrawViewandopentheattributesinspector.ChecktheboxlabeledMultipleTouch(Figure17.5),whichwillsettheDrawViewinstance’smultipleTouchesEnabledpropertytotrue.
Figure17.5MultipleTouchenabled
NowthatDrawViewwillacceptmultipletouches,eachtimeafingertouchesthescreen,moves,orisremovedfromthescreen,theappropriateUIResponderwillbecalledontheview.However,younowhaveaproblem:yourUIRespondercodeassumestherewillonlybeonetouchactiveandonelinebeingdrawnatatime.
Forexample,eachtouch-handlingmethodasksforthefirstelementinthesetoftouchesitreceives.Inasingle-touchview,therewillonlyeverbeoneobjectintheset,soaskingforanyobjectalwaysreturnsthetouchthattriggeredtheevent.Inamultiple-touchview,thesetcancontainmorethanonetouch.Also,DrawViewhasonlyoneproperty(currentLine)thathangsontoalineinprogress.Obviously,youwillneedtoholdasmanylinesastherearetouchescurrentlyonthescreen.Whileyoucouldcreateafewmoreproperties,likecurrentLine1andcurrentLine2,itwouldbeahassletomanagewhichpropertycorrespondstowhichtouch.
Insteadofaddingmoreproperties,youaregoingreplacethesingleLinewithadictionarycontaininginstancesofLine.InDrawView.swift,addanewpropertytoreplacecurrentLine.classDrawView:UIView{
varcurrentLine:Line?varcurrentLines=[NSValue:Line]()
WOW! eBook www.wowebook.org
ThekeytostorethelineinthedictionarywillbederivedfromtheUITouchobjectthatthelinecorrespondsto.Asmoretoucheventsoccur,youcanusethesamealgorithmtoderivethekeyfromtheUITouchthattriggeredtheeventanduseittolookuptheappropriateLineinthedictionary.
NowyouneedtoupdatetheUIRespondermethodstoaddlinesthatarecurrentlybeingdrawntothisdictionary.InDrawView.swift,updatethecodeintouchesBegan(_:withEvent:).overridefunctouchesBegan(touches:Set<UITouch>,withEventevent:UIEvent?){
lettouch=touches.first!
//Getlocationofthetouchinview'scoordinatesystemletlocation=touch.locationInView(self)currentLine=Line(begin:location,end:location)
//Let'sputinalogstatementtoseetheorderofeventsprint(__FUNCTION__)
fortouchintouches{letlocation=touch.locationInView(self)
letnewLine=Line(begin:location,end:location)
letkey=NSValue(nonretainedObject:touch)currentLines[key]=newLine}
setNeedsDisplay()}
Inthiscode,youfirstprintoutthenameofthemethod,whichisrepresentedbythe__FUNCTION__expression.Atcompiletime,thecompilerwillevaluatethisexpressionandreturnaStringthatrepresentsthenameofthemethod.
Second,youenumerateoverallofthetouchesthatbegan,becauseitispossiblethatmorethanonetouchcanbeginatthesametime.(Typically,touchesbeginatdifferenttimesandtouchesBegan(_:withEvent:)getscalledmultipletimesontheDrawViewforeachtouch.Butyouhavetopreparefortheimprobable,ifnottheimpossible.)
Next,noticetheuseofNSValue(nonretainedObject:)toderivethekeytostoretheLine.ThismethodcreatesanNSValueinstancethatholdsontotheaddressoftheUITouchobjectthatwillbeassociatedwiththisline.SinceaUITouchiscreatedwhenatouchbegins,updatedthroughoutitslifetime,anddestroyedwhenthetouchends,theaddressofthatobjectwillbeconstantthrougheachtouch-event-handlingmethod.Figure17.6showsthenewstateofaffairs.
Youmaybewondering:WhynotusetheUITouchitselfasthekey?WhygothroughthehoopofcreatinganNSValue?ThedocumentationforUITouchsaysthatyoushouldneverretain(inotherwords,keepastrongreferenceto)aUITouchobject.Therefore,youwrapthememoryaddressoftheUITouchinaninstanceofNSValue,usingitsinit(nonretainedObject:)initializer.Thedocumentationforthismethodstates:“Thismethodisusefulifyouwanttoaddanobjecttoacollectionbutdon’twantthecollectiontocreateastrongreferencetoit,”whichisexactlywhatyouwant.SincethesameUITouchobjectisreusedfortheentiretyofthattouch’slifecycle,youcanrecreate
WOW! eBook www.wowebook.org
thesameNSValueusingthesameUITouch.
Figure17.6ObjectdiagramformultitouchTouchTracker
UpdatetouchesMoved(_:withEvent:)inDrawView.swiftsothatitcanlookuptherightLine.overridefunctouchesMoved(touches:Set<NSObject>,withEventevent:UIEvent){
lettouch=touches.first!letlocation=touch.locationInView(self)
currentLine?.end=location
//Let'sputinalogstatementtoseetheorderofeventsprint(__FUNCTION__)
fortouchintouches{letkey=NSValue(nonretainedObject:touch)currentLines[key]?.end=touch.locationInView(self)}
setNeedsDisplay()}
Now,updatetouchesEnded(_:withEvent:)tomoveanyfinishedlinesintothefinishedLinesarray.overridefunctouchesEnded(touches:Set<NSObject>,withEventevent:UIEvent){
ifvarline=currentLine{lettouch=touches.first!letlocation=touch.locationInView(self)line.end=location
finishedLines.append(line)}currentLine=nil
//Let'sputinalogstatementtoseetheorderofeventsprint(__FUNCTION__)
fortouchintouches{letkey=NSValue(nonretainedObject:touch)ifvarline=currentLines[key]{
WOW! eBook www.wowebook.org
line.end=touch.locationInView(self)
finishedLines.append(line)currentLines.removeValueForKey(key)}}
setNeedsDisplay()}
Finally,updatedrawRect(_:)todraweachlineincurrentLines.overridefuncdrawRect(rect:CGRect){
//DrawfinishedlinesinblackUIColor.blackColor().setStroke()forlineinfinishedLines{strokeLine(line)}
ifletline=currentLine{//Ifthereisalinecurrentlybeingdrawn,doitinredUIColor.redColor().setStroke()strokeLine(line)}
//DrawcurrentlinesinredUIColor.redColor().setStroke()for(_,line)incurrentLines{strokeLine(line)}}
Buildandruntheapplicationandstartdrawinglineswithmultiplefingers.(Onthesimulator,holddowntheOptionkeywhileyoudragtosimulatemultiplefingers.)Noticetheorderingofthelogmessagesintheconsole.
YoushouldknowthatwhenaUIRespondermethodliketouchesMoved(_:withEvent:)iscalledonaview,onlythetouchesthathavemovedwillbeinthesetoftouches.Thus,itispossibleforthreetouchestobeonaview,butonlyonetouchtobeinthesetoftouchespassedintooneofthesemethods.Additionally,onceaUITouchbeginsonaview,alltoucheventmethodsarecalledonthatsameviewoverthetouch’slifetime,evenifthattouchmovesoffoftheviewitbeganon.
ThelastthingleftforthebasicsofTouchTrackeristohandlewhathappenswhenatouchiscanceled.AtouchcanbecanceledwhentheapplicationisinterruptedbytheOS(forexample,whenaphonecallcomesin)whileatouchiscurrentlyonthescreen.Whenatouchiscanceled,anystateitsetupshouldbereverted.Inthiscase,youshouldremoveanylinesinprogress.
InDrawView.swift,implementtouchesCancelled(_:withEvent:).overridefunctouchesCancelled(touches:Set<UITouch>?,withEventevent:UIEvent?){
//Let'sputinalogstatementtoseetheorderofeventsprint(__FUNCTION__)
currentLines.removeAll()
setNeedsDisplay()}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
@IBInspectableWhenworkinginInterfaceBuilder,youareabletomodifyattributesfortheviewsthatyouaddtothecanvas.Forexample,youcansetthebackgroundcoloronaview,thetextonalabel,andthecurrentprogressonaslider.YoucanaddthissamebehaviortoyourowncustomUIViewsubclassesforcertaintypes.Let’saddintheabilityfortheDrawView’scurrentlinecolor,finishedlinecolor,andlinethicknesstobecustomizedthroughInterfaceBuilder.
InDrawView.swift,declarethreepropertiestoreferencethesevalues.Givethemdefaultvaluesandhavetheviewflagitselfforredrawingwheneverthesepropertieschange.varcurrentLines=[NSValue:Line]()varfinishedLines=[Line]()
@IBInspectablevarfinishedLineColor:UIColor=UIColor.blackColor(){didSet{setNeedsDisplay()}}
@IBInspectablevarcurrentLineColor:UIColor=UIColor.redColor(){didSet{setNeedsDisplay()}}
@IBInspectablevarlineThickness:CGFloat=10{didSet{setNeedsDisplay()}}
The@IBInspectablekeywordletsInterfaceBuilderknowthatthisisapropertythatyouwanttocustomizethroughtheattributesinspector.Manyofthecommontypesaresupportedby@IBInspectable:Booleans,strings,numbers,CGPoint,CGSize,CGRect,UIColor,UIImage,andafewmoreareallcandidates.
NowupdatestrokeLine(_:)anddrawView(_:)tousethesenewproperties.funcstrokeLine(line:Line){letpath=UIBezierPath()path.lineWidth=10path.lineWidth=lineThicknesspath.lineCapStyle=CGLineCap.Round
path.moveToPoint(line.begin)path.addLineToPoint(line.end)path.stroke()}
overridefuncdrawRect(rect:CGRect){//DrawfinishedlinesinblackUIColor.blackColor().setStroke()finishedLineColor.setStroke()forlineinfinishedLines{strokeLine(line)}
//DrawcurrentlinesinredUIColor.redColor().setStroke()currentLineColor.setStroke()for(_,line)incurrentLines{strokeLine(line)
WOW! eBook www.wowebook.org
}}
NowwhenyouaddaDrawViewtothecanvasinInterfaceBuilder,youcancustomizethesethreepropertiesintheattributesinspectortobedifferentfordifferentinstances(Figure17.7).
Figure17.7CustomizingDrawView
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:ColorsMakeitsotheangleatwhichalineisdrawndictatesitscoloronceithasbeenaddedtocurrentLines.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:CirclesUsetwofingerstodrawcircles.Tryhavingeachfingerrepresentonecorneroftheboundingboxaroundthecircle.RecallthatyoucansimulatetwofingersonthesimulatorbyholdingdowntheOptionkey.(Hint:thisismucheasierifyoutracktouchesthatareworkingonacircleinaseparatedictionary.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:TheResponderChainInChapter13,wetalkedbrieflyaboutthefirstresponder.AUIRespondercanbeafirstresponderandreceivetouchevents.UIViewisoneexampleofaUIRespondersubclass,buttherearemanyothers,includingUIViewController,UIApplication,andUIWindow.Youareprobablythinking,“Butyoucan’ttouchaUIViewController.It’snotanonscreenobject.”Youareright–youcannotsendatoucheventdirectlytoaUIViewController,butviewcontrollerscanreceiveeventsthroughtheresponderchain.
EveryUIResponderhasamethodcallednextResponder(),andtogethertheseobjectsmakeuptheresponderchain(Figure17.8).Atoucheventstartsattheviewthatwastouched.ThenextResponderofaviewistypicallyitsUIViewController(ifithasone)oritssuperview(ifitdoesnot).ThenextResponder()ofaviewcontrolleristypicallyitsview’ssuperview.Thetop-mostsuperviewisthewindow.Thewindow’snextResponder()isthesingletoninstanceofUIApplication.
Figure17.8Responderchain
HowdoesaUIRespondernothandleanevent?ItforwardsthesamemessagetoitsnextResponder().ThatiswhatthedefaultimplementationofmethodsliketouchesBegan(_:withEvent:)do.Soifamethodisnotoverridden,itsnextresponderwillattempttohandlethetouchevent.Iftheapplication(thelastobjectintheresponderchain)doesnothandletheevent,thenitisdiscarded.
Youcanexplicitlysendamessagetoanextresponder,too.Let’ssaythereisaviewthattrackstouches,butifadouble-tapoccurs,itsnextrespondershouldhandleit.Thecodewouldlooklikethis:overridefunctouchesBegan(touches:Set<UITouch>,withEventevent:UIEvent?){lettouch=touches.first!iftouch.tapCount==2{nextResponder()?.touchesBegan(touches,withEvent:event)}
WOW! eBook www.wowebook.org
else{//Goontohandletouchesthatarenotdouble-taps}}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:UIControlTheclassUIControlisthesuperclassforseveralclassesinCocoaTouch,includingUIButtonandUISlider.Youhaveseenhowtosetthetargetsandactionsforthesecontrols.NowwecantakeacloserlookathowUIControloverridesthesameUIRespondermethodsyouimplementedinthischapter.
InUIControl,eachpossiblecontroleventisassociatedwithaconstant.Buttons,forexample,typicallysendactionmessagesontheUIControlEvents.TouchUpInsidecontrolevent.Atargetregisteredforthiscontroleventwillonlyreceiveitsactionmessageiftheusertouchesthecontrolandthenliftsthefingeroffthescreeninsidetheframeofthecontrol.
Forabutton,however,youcanhaveactionsonothereventtypes.Forexample,youmighttriggeramethodiftheuserremovesthefingerinsideoroutsidetheframe.Assigningthetargetandactionprogrammaticallywouldlooklikethis:button.addTarget(self,action:"resetTemperature:",forControlEvents:[.TouchUpInside,.TouchUpOutside])
NowconsiderhowUIControlhandlesUIControlEvents.TouchUpInside.//Nottheexactcode.Thereisabitmoregoingon!overridefunctouchesEnded(touches:Set<UITouch>,withEventevent:UIEvent?){
//Referencetothetouchthatisendinglettouch=touches.first!
//Locationofthatpointinthiscontrol'scoordinatesystemlettouchLocation=touch.locationInView(self)
//Isthatpointstillinmyviewingbounds?ifself.bounds.contains(touchLocation){//Sendoutactionmessagestoalltargetsregisteredforthisevent!sendActionsForControlEvents(.TouchUpInside)}else{//Thetouchendedoutsidethebounds:differentcontroleventsendActionsForControlEvents(.TouchUpOutside)}}
Sohowdotheseactionsgetsenttotherighttarget?AttheendoftheUIRespondermethodimplementations,thecontrolsendsthemessagesendActionsForControlEvents(_:)toitself.Thismethodlooksatallofthetarget-actionpairsthecontrolhas.Ifanyofthemareregisteredforthecontroleventpassedastheargument,thosetargetsaresentanactionmessage.
However,acontrolneversendsamessagedirectlytoitstargets.Instead,itroutesthesemessagesthroughtheUIApplicationobject.Whynothavecontrolssendtheactionmessagesdirectlytothetargets?Controlscanalsohavenil-targetedactions.IfaUIControl’stargetisnil,theUIApplicationfindsthefirstresponderofitsUIWindowandsendstheactionmessagetoit.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
18UIGestureRecognizerand
UIMenuControllerInChapter17,youhandledrawtouchesbyimplementingmethodsfromUIResponder.Sometimesyouwanttodetectaspecificpatternoftouchesthatmakeagesture,likeapinchoraswipe.Insteadofwritingcodetodetectcommongesturesyourself,youcanuseinstancesofUIGestureRecognizer.
AUIGestureRecognizerinterceptstouchesthatareontheirwaytobeinghandledbyaview.Whenitrecognizesaparticulargesture,itcallsamethodontheobjectofyourchoice.ThereareseveraltypesofgesturerecognizersbuiltintotheSDK.Inthischapter,youwillusethreeofthemtoallowTouchTrackeruserstoselect,move,anddeletelines(Figure18.1).YouwillalsoseehowtouseanotherinterestingiOSclass,UIMenuController.
WOW! eBook www.wowebook.org
Figure18.1TouchTrackerbytheendofthechapter
WOW! eBook www.wowebook.org
UIGestureRecognizerSubclassesYoudonotinstantiateUIGestureRecognizeritself.Instead,thereareanumberofsubclassesofUIGestureRecognizer,andeachoneisresponsibleforrecognizingaparticulargesture.
TouseaninstanceofaUIGestureRecognizersubclass,yougiveitatarget-actionpairandattachittoaview.Wheneverthegesturerecognizerrecognizesitsgestureontheview,itwillsendtheactionmessagetoitstarget.AllUIGestureRecognizeractionmessageshavethesameform:funcaction(gestureRecognizer:UIGestureRecognizer){}
Whenrecognizingagesture,thegesturerecognizerinterceptsthetouchesdestinedfortheview(Figure18.2).Thus,thetypicalUIRespondermethodsliketouchesBegan(_:withEvent:)maynotbecalledonaviewwithgesturerecognizers.
Figure18.2Gesturerecognizersintercepttouches
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DetectingTapswithUITapGestureRecognizerThefirstUIGestureRecognizersubclassyouwilluseisUITapGestureRecognizer.Whentheusertapsthescreentwice,allofthelinesonthescreenwillbecleared.
OpenTouchTracker.xcodeprojfromChapter17andDrawView.swift.Addaninit?(coder:)methodandinstantiateaUITapGestureRecognizerthatrequirestwotapstofireandcalltheactionmethodonitstarget.requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)
letdoubleTapRecognizer=UITapGestureRecognizer(target:self,action:"doubleTap:")doubleTapRecognizer.numberOfTapsRequired=2addGestureRecognizer(doubleTapRecognizer)}
Nowwhenadouble-tapoccursonaninstanceofDrawView,themethoddoubleTap(_:)willbecalledonthatinstance.ImplementthismethodinDrawView.swift.funcdoubleTap(gestureRecognizer:UIGestureRecognizer){print("Recognizedadoubletap")
currentLines.removeAll(keepCapacity:false)finishedLines.removeAll(keepCapacity:false)setNeedsDisplay()}
NoticethattheargumenttotheactionmethodforagesturerecognizeristheinstanceofUIGestureRecognizerthatcalledthemethod.Inthecaseofadouble-tap,youdonotneedanyinformationfromtherecognizer,butyouwillneedinformationfromtheotherrecognizersyouinstalllaterinthechapter.
Buildandruntheapplication,drawafewlines,anddouble-tapthescreentoclearthem.
Youmayhavenoticed(especiallyonthesimulator)thatthefirsttapofadouble-tapresultsinasmallreddotbeingdrawn.ThisdotappearsbecausetouchesBegan(_:withEvent:)iscalledontheDrawViewonthefirsttap,creatingaveryshortline.Checktheconsoleandyouwillseethefollowingsequenceofevents:touchesBegan(_:withEvent:)RecognizedadoubletaptouchesCancelled(_:withEvent:)
Gesturerecognizersworkbyinspectingtoucheventstodetermineiftheirparticulargesturehasoccurred.Beforeagestureisrecognized,thegesturerecognizerinterceptsalltheUIRespondermethodcalls.Ifithasnotrecognizeditsgesture,eachcallisforwardedontotheview.
Recognizingataprequiresthatatouchbeginandend.ThismeansthattheUITapGestureRecognizercannotknowwhetherthetouchisatapwhentouchesBegan(_:withEvent:)isoriginallycalled,sothemethodiscalledontheviewaswell.Whenthetouchends,thetapisrecognizedandthegesturerecognizerclaims
WOW! eBook www.wowebook.org
thetouchforitself.ItdoessobycallingtouchesCancelled(_:withEvent:)ontheview.Afterthat,nomoreUIRespondermethodswillbecalledontheviewforthatparticulartouch.
Topreventthisreddotfromappearingtemporarily,youmustpreventtouchesBegan(_:withEvent:)frombeingcalledontheview.YoucantellaUIGestureRecognizertodelaycallingtouchesBegan(_:withEvent:)onitsviewifitisstillpossiblethatitsgesturemightberecognizedforthattouch.
InDrawView.swift,modifyinit?(coder:)todojustthis.requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)
letdoubleTapRecognizer=UITapGestureRecognizer(target:self,action:"doubleTap:")doubleTapRecognizer.numberOfTapsRequired=2doubleTapRecognizer.delaysTouchesBegan=trueaddGestureRecognizer(doubleTapRecognizer)}
Buildandruntheapplication,drawsomelines,andthendouble-taptoclearthem.Youwillnolongerseethereddotwhiledouble-tapping.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
MultipleGestureRecognizersThenextstepistoaddanothergesturerecognizerthatallowstheusertoselectaline.(Later,auserwillbeabletodeletetheselectedline.)YouwillinstallanotherUITapGestureRecognizerontheDrawViewthatonlyrequiresonetap.
InDrawView.swift,modifyinit?(coder:)toaddthisgesturerecognizer.requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)
letdoubleTapRecognizer=UITapGestureRecognizer(target:self,action:"doubleTap:")doubleTapRecognizer.numberOfTapsRequired=2doubleTapRecognizer.delaysTouchesBegan=trueaddGestureRecognizer(doubleTapRecognizer)
lettapRecognizer=UITapGestureRecognizer(target:self,action:"tap:")tapRecognizer.delaysTouchesBegan=trueaddGestureRecognizer(tapRecognizer)}
Now,implementtap(_:)inDrawView.swifttologthetaptotheconsole.functap(gestureRecognizer:UIGestureRecognizer){print("Recognizedatap")}
Buildandruntheapplication.Trytappinganddouble-tapping.Tappingoncelogstheappropriatemessagetotheconsole.Double-tapping,however,triggersbothtap(_:)anddoubleTap(_:).
Insituationswhereyouhavemultiplegesturerecognizers,itisnotuncommontohaveonegesturerecognizerfireandclaimatouchwhenyoureallywantedanothergesturerecognizertohandleit.Inthesecases,yousetupdependenciesbetweenrecognizersthatsay,“Waitamomentbeforeyoufire,becausethistouchmightbemine!”
Ininit?(coder:),makeitsothetapRecognizerwaitsuntilthedoubleTapRecognizerfailstorecognizeadouble-tapbeforeclaimingthesingletapforitself.requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)
letdoubleTapRecognizer=UITapGestureRecognizer(target:self,action:"doubleTap:")doubleTapRecognizer.numberOfTapsRequired=2doubleTapRecognizer.delaysTouchesBegan=trueaddGestureRecognizer(doubleTapRecognizer)
lettapRecognizer=UITapGestureRecognizer(target:self,action:"tap:")tapRecognizer.delaysTouchesBegan=truetapRecognizer.requireGestureRecognizerToFail(doubleTapRecognizer)addGestureRecognizer(tapRecognizer)}
Buildandruntheapplicationagainandtryoutsometaps.Asingletapnowtakesasmallamountoftimetofireafterthetapoccurs,butdouble-tappingnolongertriggersthetap(_:)message.
Next,let’sbuildontheDrawViewsothattheusercanselectalinewhenitistapped.First,addapropertyatthetopofDrawView.swifttoholdontotheindexofaselected
WOW! eBook www.wowebook.org
line.classDrawView:UIView{
varcurrentLines=[NSValue:Line]()varfinishedLines=[Line]()varselectedLineIndex:Int?
NowmodifydrawRect(_:)todrawtheselectedlineingreen.overridefuncdrawRect(rect:CGRect){finishedLineColor.setStroke()forlineinfinishedLines{strokeLine(line)}
currentLineColor.setStroke()for(_,line)incurrentLines{strokeLine(line)}
ifletindex=selectedLineIndex{UIColor.greenColor().setStroke()letselectedLine=finishedLines[index]strokeLine(selectedLine)}}
InDrawView.swift,addanindexOfLineAtPoint(_:)methodthatreturnstheindexoftheLineclosesttoagivenpoint.funcindexOfLineAtPoint(point:CGPoint)->Int?{
//Findalineclosetopointfor(index,line)infinishedLines.enumerate(){letbegin=line.beginletend=line.end
//CheckafewpointsonthelinefortinCGFloat(0).stride(to:1.0,by:0.05){letx=begin.x+((end.x-begin.x)*t)lety=begin.y+((end.y-begin.y)*t)
//Ifthetappedpointiswithin20points,let'sreturnthislineifhypot(x-point.x,y-point.y)<20.0{returnindex}}}
//Ifnothingiscloseenoughtothetappedpoint,thenwedidnotselectalinereturnnil}
Thestridemethodwillallowttostartat0andgoupto(butnotreach)thetovalue,incrementingthevalueoftbythebyvalue.Itisanalogoustothefollowingcode:forvart:CGFloat=0;t<1.0;t+=0.05{...}
Thereareother,betterwaystodeterminetheclosestlinetoapoint,butthissimpleimplementationwillworkforyourpurposes.
Thepointtobepassedinisthepointwherethetapoccurred.Youcaneasilygetthisinformation.EveryUIGestureRecognizerhasalocationInView(_:)method.Callingthismethodonthegesturerecognizerwillgiveyouthecoordinatewherethegestureoccurredinthecoordinatesystemoftheviewthatispassedastheargument.
WOW! eBook www.wowebook.org
InDrawView.swift,updatetap(_:)tocalllocationInView(_:)onthegesturerecognizer,passtheresulttoindexOfLineAtPoint(_:),andmakethereturnedindextheselectedLineIndex.functap(gestureRecognizer:UIGestureRecognizer){print("Recognizedatap")
letpoint=gestureRecognizer.locationInView(self)selectedLineIndex=indexOfLineAtPoint(point)
setNeedsDisplay()}
Iftheuserdouble-tapstoclearalllineswhilealineisselected,theapplicationwilltrap.Toaddressthis,updatedoubleTap(_:)tosettheselectedLineIndextonil.funcdoubleTap(gestureRecognizer:UIGestureRecognizer){print("Recognizedadoubletap")
selectedLineIndex=nilcurrentLines.removeAll(keepCapacity:false)finishedLines.removeAll(keepCapacity:false)setNeedsDisplay()}
Buildandruntheapplication.Drawafewlinesandthentapone.Thetappedlineshouldappearingreen,butrememberthatittakesamomentbeforethetapisknownnottobeadouble-tap.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UIMenuControllerNextyouaregoingtomakeitsothatwhentheuserselectsaline,amenuwiththeoptiontodeletethatlineappearswheretheusertapped.Thereisabuilt-inclassforprovidingthissortofmenucalledUIMenuController(Figure18.3).AmenucontrollerhasalistofUIMenuItemobjectsandispresentedinanexistingview.Eachitemhasatitle(whatshowsupinthemenu)andanaction(themessageitsendsthefirstresponderofthewindow).
Figure18.3AUIMenuController
ThereisonlyoneUIMenuControllerperapplication.Whenyouwishtopresentthisinstance,youfillitwithmenuitems,giveitarectangletopresentfrom,andsetittobevisible.
DothisinDrawView.swift’stap(_:)methodiftheuserhastappedonaline.Iftheusertappedsomewherethatisnotnearaline,thecurrentlyselectedlinewillbedeselectedandthemenucontrollerwillhide.functap(gestureRecognizer:UIGestureRecognizer){print("Recognizedatap")
letpoint=gestureRecognizer.locationInView(self)selectedLineIndex=indexOfLineAtPoint(point)
//Grabthemenucontrollerletmenu=UIMenuController.sharedMenuController()
ifselectedLineIndex!=nil{
//MakeDrawViewthetargetofmenuitemactionmessagesbecomeFirstResponder()
//Createanew"Delete"UIMenuItemletdeleteItem=UIMenuItem(title:"Delete",action:"deleteLine:")menu.menuItems=[deleteItem]
//Tellthemenuwhereitshouldcomefromandshowitmenu.setTargetRect(CGRect(x:point.x,y:point.y,width:2,height:2),inView:self)menu.setMenuVisible(true,animated:true)}else{//Hidethemenuifnolineisselectedmenu.setMenuVisible(false,animated:true)}
setNeedsDisplay()}
Foramenucontrollertoappear,aviewthatrespondstoatleastoneactionmessageintheUIMenuController’smenuitemsmustbethefirstresponderofthewindow–thisiswhyyoucalledthemethodbecomeFirstResponder()ontheDrawViewbeforesettingupthemenucontroller.
WOW! eBook www.wowebook.org
Ifyouhaveacustomviewclassthatneedstobecomethefirstresponder,youmustalsooverridecanBecomeFirstResponder().InDrawView.swift,overridethismethodtoreturntrue.overridefunccanBecomeFirstResponder()->Bool{returntrue}
Youcanbuildandruntheapplicationnow,butwhenyouselectaline,themenuwillnotappear.Whenbeingpresented,themenucontrollergoesthrougheachmenuitemandasksthefirstresponderifitimplementstheactionmessageforthatitem.Ifthefirstresponderdoesnotimplementthatmethod,thenthemenucontrollerwillnotshowtheassociatedmenuitem.Ifnomenuitemshavetheiractionmessagesimplementedbythefirstresponder,themenuisnotshownatall.
TogettheDeletemenuitem(andthemenuitself)toappear,implementdeleteLine(_:)inDrawView.swift.funcdeleteLine(sender:AnyObject){//RemovetheselectedlinefromthelistoffinishedLinesifletindex=selectedLineIndex{finishedLines.removeAtIndex(index)selectedLineIndex=nil
//RedraweverythingsetNeedsDisplay()}}
Buildandruntheapplication.Drawaline,taponit,andthenselectDeletefromthemenuitem.
Ifyouselectalineandthendouble-taptoclearalllines,themenucontrollerwillstillbevisible.IftheselectedLineIndexeverbecomesnil,themenucontrollershouldnotbevisible.
AddapropertyobservertoselectedLineIndexthatsetsthemenucontrollertobenotvisibleiftheindexissettonil.varselectedLineIndex:Int?{didSet{ifselectedLineIndex==nil{letmenu=UIMenuController.sharedMenuController()menu.setMenuVisible(false,animated:true)}}}
Buildandruntheapplication.Drawaline,selectit,andthendouble-tapthebackground.Thelinesandthemenucontrollerwillnolongerbevisible.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
MoreGestureRecognizersInthissection,youaregoingtoaddtheabilityforausertoselectalinebypressingandholding(alongpress)andthenmovetheselectedlinebydraggingthefinger(apan).ThiswillrequiretwomoresubclassesofUIGestureRecognizer:UILongPressGestureRecognizerandUIPanGestureRecognizer.
UILongPressGestureRecognizer
InDrawView.swift,instantiateaUILongPressGestureRecognizerininit?(coder:)andaddittotheDrawView....
addGestureRecognizer(tapRecognizer)
letlongPressRecognizer=UILongPressGestureRecognizer(target:self,action:"longPress:")addGestureRecognizer(longPressRecognizer)}
NowwhentheuserholdsdownontheDrawView,themethodlongPress(_:)willbecalledonit.Bydefault,atouchmustbeheld0.5secondstobecomealongpress,butyoucanchangetheminimumPressDurationofthegesturerecognizerifyoulike.
Sofar,youhaveworkedwithtapgestures.Atapisadiscretegesture.Bythetimeitisrecognized,thegestureisover,andtheactionmessagehasbeendelivered.Alongpress,ontheotherhand,isacontinuousgesture.Continuousgesturesoccurovertime.Tokeeptrackofwhatisgoingonwithacontinuousgesture,youcancheckarecognizer’sstateproperty.
Forexample,consideratypicallongpress:
Whentheusertouchesaview,thelongpressrecognizernoticesapossiblelongpressbutmustwaittoseewhetherthetouchisheldlongenoughtobecomealongpressgesture.Therecognizer’sstateisUIGestureRecognizerState.Possible.
Oncetheuserholdsthetouchlongenough,thelongpressisrecognizedandthegesturehasbegun.Therecognizer’sstateisUIGestureRecognizerState.Began.
Whentheuserremovesthefinger,thegesturehasended.Therecognizer’sstateisUIGestureRecognizerState.Ended.
Whenthelong-pressgesturerecognizertransitionsfrompossibletobeganandfrombegantoended,itsendsitsactionmessagetoitstarget.Todeterminewhichtransitiontriggeredtheaction,youcheckthegesturerecognizer’sstate.
Rememberthatthelongpressispartofalargerfeature.Inthenextsection,youwillenabletheusertomovetheselectedlinebydraggingitwiththesamefingerthatbeganthelongpress.SohereistheplanforimplementingthelongPress(_:)actionmethod:
WOW! eBook www.wowebook.org
Whentherecognizerisinthebeganstate,youwillselecttheclosestlinetowherethegestureoccurred.Whentherecognizerisintheendedstate,youwilldeselecttheline.
InDrawView.swift,implementlongPress(_:).funclongPress(gestureRecognizer:UIGestureRecognizer){print("Recognizedalongpress")
ifgestureRecognizer.state==.Began{letpoint=gestureRecognizer.locationInView(self)selectedLineIndex=indexOfLineAtPoint(point)
ifselectedLineIndex!=nil{currentLines.removeAll(keepCapacity:false)}}elseifgestureRecognizer.state==.Ended{selectedLineIndex=nil}
setNeedsDisplay()}
Buildandruntheapplication.Drawalineandthenpressandholdit;thelinewillturngreenandbecometheselectedline.Whenyouletgo,thelinewillreverttoitsformercolorandwillnolongerbetheselectedline.
UIPanGestureRecognizerandsimultaneousrecognizers
InDrawView.swift,declareaUIPanGestureRecognizerasapropertysothatyouhaveaccesstoitinallofyourmethods.classDrawView:UIView{
varcurrentLines=[NSValue:Line]()varfinishedLines=[Line]()varselectedLineIndex:Int?varmoveRecognizer:UIPanGestureRecognizer!
Next,inDrawView.swift,addcodetoinit?(coder:)toinstantiateaUIPanGestureRecognizer,setoneofitsproperties,andaddittotheDrawView.letlongPressRecognizer=UILongPressGestureRecognizer(target:self,action:"longPress:")addGestureRecognizer(longPressRecognizer)
moveRecognizer=UIPanGestureRecognizer(target:self,action:"moveLine:")moveRecognizer.cancelsTouchesInView=falseaddGestureRecognizer(moveRecognizer)}
WhatiscancelsTouchesInView?EveryUIGestureRecognizerhasthisproperty,whichdefaultstotrue.WhencancelsTouchesInViewistrue,thegesturerecognizerwill“eat”anytouchitrecognizes,andtheviewwillnotgetachancetohandlethetouchviathetraditionalUIRespondermethods,liketouchesBegan(_:withEvent:).
Usually,thisiswhatyouwant,butnotalways.Inthiscase,ifthepangesturerecognizerweretoeatitstouches,thenuserswouldnotbeabletodrawlines.WhenyousetcancelsTouchesInViewtofalse,youensurethatanytouchrecognizedbythegesturerecognizerwillalsobedeliveredtotheviewviatheUIRespondermethods.
WOW! eBook www.wowebook.org
InDrawView.swift,addasimpleimplementationfortheactionmethod:funcmoveLine(gestureRecognizer:UIPanGestureRecognizer){print("Recognizedapan")}
Buildandruntheappanddrawsomelines.BecausecancelsTouchesInViewisfalse,thepangestureisrecognized,butlinescanstillbedrawn.YoucancommentoutthelinethatsetscancelsTouchesInViewandrunagaintoseethedifference.
Soon,youwillupdatemoveLine(_:)toredrawtheselectedlineastheuser’sfingermovesacrossthescreen.Butfirstyouneedtwogesturerecognizerstobeabletohandlethesametouch.Normally,whenagesturerecognizerrecognizesitsgesture,iteatsitandnootherrecognizergetsachancetohandlethattouch.Tryit:runtheapp,drawaline,pressandholdtoselecttheline,andthenmoveyourfingeraround.Theconsolereportsthelongpressbutnotthepan.
Inyourcase,thedefaultbehaviorisproblematic:youruserswillpressandholdtoselectalineandthenpantomovetheline-withoutliftingthefingerinbetween.Thus,thetwogestureswilloccursimultaneously,andthepangesturerecognizermustbeallowedtorecognizeapaneventhoughthelongpressgesturehasalreadyrecognizedalongpress.
Toallowagesturerecognizertorecognizeitsgesturesimultaneouslywithanothergesturerecognizer,youimplementamethodfromtheUIGestureRecognizerDelegateprotocol:optionalfuncgestureRecognizer(_gestureRecognizer:UIGestureRecognizer,shouldRecognizeSimultaneouslyWithGestureRecognizerotherGestureRecognizer:UIGestureRecognizer)->Bool
Thefirstparameteristhegesturerecognizerthatisaskingforguidance.Itsaystoitsdelegate,“Sothere’smeandthisotherrecognizer,andoneofusjustrecognizedagesture.Shouldtheonewhodidnotrecognizeitstayinthepossiblestateandcontinuetotrackthistouch?”
Notethatthecallitselfdoesnottellyouwhichofthetworecognizershasrecognizeditsgesture-and,thus,whichofthemwillpotentiallybedeprivedofthechancetorecognizeitsgesture.
Bydefault,themethodreturnsfalse,andthegesturerecognizerstillinthepossiblestateleavesthetouchinthehandsofthegesturealreadyintherecognizedstate.Youcanimplementthemethodtoreturntruetoallowbothrecognizerstorecognizetheirgesturesinthesametouch.(Ifyouneedtodeterminewhichofthetworecognizershasrecognizeditsgesture,youcanchecktherecognizers’stateproperties.)
Toenablepanningwhilelongpressing,youaregoingtogivethepangesturerecognizeradelegate(theDrawView).Then,whenthelongpressrecognizerrecognizesitsgesture,thepangesturerecognizerwillcallthesimultaneousrecognitionmethodonitsdelegate.YouwillimplementthismethodinDrawViewtoreturntrue.Thiswillallowthepangesturerecognizertorecognizeanypanningthatoccurswhilealongpressisinprogress.
First,inDrawView.swift,declarethatDrawViewconformstotheUIGestureRecognizerDelegateprotocol.classDrawView:UIView,UIGestureRecognizerDelegate{
WOW! eBook www.wowebook.org
varcurrentLines=[NSValue:Line]()varfinishedLines=[Line]()varselectedLineIndex:Int?varmoveRecognizer:UIPanGestureRecognizer!
Next,ininit?(coder:),settheDrawViewtobethedelegateoftheUIPanGestureRecognizer.letlongPressRecognizer=UILongPressGestureRecognizer(target:self,action:"longPress:")addGestureRecognizer(longPressRecognizer)
moveRecognizer=UIPanGestureRecognizer(target:self,action:"moveLine:")moveRecognizer.delegate=selfmoveRecognizer.cancelsTouchesInView=falseaddGestureRecognizer(moveRecognizer)}
Finally,inDrawView.swift,implementthedelegatemethodtoreturntrue.funcgestureRecognizer(gestureRecognizer:UIGestureRecognizer,shouldRecognizeSimultaneouslyWithGestureRecognizerotherGestureRecognizer:UIGestureRecognizer)->Bool{returntrue}
Forthissituation,whereonlyyourpangesturerecognizerhasadelegate,thereisnoneedtodomorethanreturntrue.Inmorecomplicatedscenarios,youwouldusethepassed-ingesturerecognizerstomorecarefullycontrolsimultaneousrecognition.
Now,whenalongpressbegins,theUIPanGestureRecognizerwillcontinuetokeeptrackofthetouch,andiftheuser’sfingerbeginstomove,thepanrecognizerwillrecognizethepan.Toseethedifference,runtheapp,drawaline,selectit,andthenpan.Theconsolewillreportbothgestures.
TheUIGestureRecognizerDelegateprotocolincludesothermethodstohelpyoutweakthebehaviorofyourgesturerecognizers.Visittheprotocolreferencepageformoreinformation.
Inadditiontothestatesyouhavealreadyseen,apangesturerecognizersupportsthechangedstate.Whenafingerstartstomove,thepanrecognizerentersthebeganstateandcallsamethodonitstarget.Whilethefingermovesaroundthescreen,therecognizertransitionstothechangedstateandcallstheactionmethodonitstargetrepeatedly.Whenthefingerleavesthescreen,therecognizer’sstateissettoended,andthemethodiscalledonthetargetforthefinaltime.
ThenextstepistoimplementthemoveLine(_:)methodthatthepanrecognizercallsonitstarget.Inthisimplementation,youwillcallthemethodtranslationInView(_:)onthepanrecognizer.ThisUIPanGestureRecognizermethodreturnshowfarthepanhasmovedasaCGPointinthecoordinatesystemoftheviewpassedastheargument.Whenthepangesturebegins,thispropertyissettothezeropoint(wherebothxandyequalzero).Asthepanmoves,thisvalueisupdated–ifthepangoesveryfartotheright,ithasahighxvalue;ifthepanreturnstowhereitbegan,itstranslationgoesbacktothezeropoint.
InDrawView.swift,implementmoveLine(_:).NoticethatbecauseyouwillsendthegesturerecognizeramethodfromtheUIPanGestureRecognizerclass,the
WOW! eBook www.wowebook.org
parameterofthismethodmustbeareferencetoaninstanceofUIPanGestureRecognizerratherthanUIGestureRecognizer.funcmoveLine(gestureRecognizer:UIPanGestureRecognizer){print("Recognizedapan")
//Ifalineisselected...ifletindex=selectedLineIndex{//Whenthepanrecognizerchangesitsposition...ifgestureRecognizer.state==.Changed{//Howfarhasthepanmoved?lettranslation=gestureRecognizer.translationInView(self)
//Addthetranslationtothecurrentbeginningandendpointsoftheline//Makesuretherearenocopyandpastetypos!finishedLines[index].begin.x+=translation.xfinishedLines[index].begin.y+=translation.yfinishedLines[index].end.x+=translation.xfinishedLines[index].end.y+=translation.y
//RedrawthescreensetNeedsDisplay()}}else{//Ifnolineisselected,donotdoanythingreturn}}
Buildandruntheapplication.Touchandholdonalineandbegindragging–andyouwillimmediatelynoticethatthelineandyourfingerarewayoutofsync.Thismakessensebecauseyouareaddingthecurrenttranslationoverandoveragaintotheline’soriginalendpoints.Youreallyneedthegesturerecognizertoreportthechangeintranslationsincethelasttimethismethodwascalledinstead.Fortunately,youcandothis.Youcansetthetranslationofapangesturerecognizerbacktothezeropointeverytimeitreportsachange.Then,thenexttimeitreportsachange,itwillhavethetranslationsincethelastevent.
NearthebottomofmoveLine(_:)inDrawView.swift,addthefollowinglineofcode.finishedLines[index].end.x+=translation.xfinishedLines[index].end.y+=translation.y
gestureRecognizer.setTranslation(CGPoint.zero,inView:self)
//RedrawthescreensetNeedsDisplay()
Buildandruntheapplicationandmovealinearound.Worksgreat!
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
MoreonUIGestureRecognizerYouhaveonlyscratchedthesurfaceofUIGestureRecognizer.Therearemoresubclasses,moreproperties,andmoredelegatemethods,andyoucanevencreaterecognizersofyourown.ThissectionwillgiveyouanideaofwhatUIGestureRecognizeriscapableof.Youcanstudythedocumentationtolearnevenmore.
Whenagesturerecognizerisonaview,itisreallyhandlingalloftheUIRespondermethods,liketouchesBegan(_:withEvent:),foryou.Gesturerecognizersareprettygreedy,sotheytypicallydonotletaviewreceivetoucheventsortheyatleastdelaythedeliveryofthoseevents.Youcansetpropertiesontherecognizer,likedelaysTouchesBegan,delaysTouchesEnded,andcancelsTouchesInView,tochangethisbehavior.Ifyouneedfinercontrolthanthisall-or-nothingapproach,youcanimplementdelegatemethodsfortherecognizer.
Attimes,youmayhavetwogesturerecognizerslookingforverysimilargestures.YoucanchainrecognizerstogethersothatoneisrequiredtofailforthenextonetostartusingthemethodrequireGestureRecognizerToFail(_:).
Onethingyoumustunderstandtomastergesturerecognizersishowtheyinterprettheirstate.Overall,therearesevenstatesarecognizercanenter:
UIGestureRecognizerState.Possible
UIGestureRecognizerState.Failed
UIGestureRecognizerState.Began
UIGestureRecognizerState.Cancelled
UIGestureRecognizerState.Changed
UIGestureRecognizerState.Recognized
UIGestureRecognizerState.Ended
Thepossiblestateiswhererecognizersspendmostoftheirtime.Whenagesturetransitionstoanystateotherthanthepossiblestateorthefailedstate,theactionmessageoftherecognizerissentanditsstatepropertycanbecheckedtoseewhy.
Thefailedstateisusedbyrecognizerswatchingforamultitouchgesture.Atsomepoint,theuser’sfingersmayachieveapositionfromwhichtheycannolongermakethatrecognizer’sgesture.Atthatpoint,thegesturerecognizerfails.Arecognizerentersthecanceledstatewhenitisinterrupted,suchasbyanincomingphonecall.
Ifagestureiscontinuous,likeapan,thegesturerecognizerwillenterthebeganstateandthengointothechangedstateuntilthegestureends.Whenthegestureends(oriscanceled),therecognizerenterstheended(orcanceled)stateandsendsitsactionmessageafinaltimebeforereturningtothepossiblestate.
Forgesturerecognizersthatpickuponadiscretegesturelikeatap,youwillonlyseetheWOW! eBook
www.wowebook.org
recognizedstate(whichhasthesamevalueastheendedstate).
Thefourbuilt-inrecognizersthatyoudidnotimplementinthischapterareUIPinchGestureRecognizer,UISwipeGestureRecognizer,UIScreenEdgePanGestureRecognizer,andUIRotationGestureRecognizer.Eachhaspropertiesthatallowyoutofine-tuneitsbehavior.Thedocumentationwillshowyouhow.
Finally,ifthereisagesturethatyouwanttorecognizethatisnotimplementedbythebuilt-insubclassesofUIGestureRecognizer,youcansubclassUIGestureRecognizeryourself.Thisisanintenseundertakingandoutsidethescopeofthisbook.YoucanreadtheMethodsforSubclassingsectionoftheUIGestureRecognizerdocumentationtolearnwhatisrequired.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:MysteriousLinesThereisabugintheapplication.Ifyoutaponalineandthenstartdrawinganewonewhilethemenuisvisible,youwilldragtheselectedlineanddrawanewlineatthesametime.Fixthisbug.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:SpeedandSizePiggy-backoffofthepangesturerecognizertorecordthevelocityofthepanwhenyouaredrawingaline.Adjustthethicknessofthelinebeingdrawnbasedonthisspeed.Makenoassumptionsabouthowsmallorlargethevelocityvalueofthepanrecognizercanbe.(Inotherwords,logavarietyofvelocitiestotheconsolefirst.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
PlatinumChallenge:ColorsHaveathree-fingerswipeupwardbringupapanelthatshowscolors.Selectingoneofthosecolorsshouldmakeanylinesyoudrawafterwardappearinthatcolor.Noextralinesshouldbedrawnbyputtingupthatpanel–oranylinesdrawnshouldbeimmediatelydeletedwhentheapplicationrealizesthatitisdealingwithathree-fingerswipe.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActionsTheUIMenuControlleristypicallyresponsibleforshowingtheuseran“edit”menuwhenitisdisplayed.(Thinkofatextfieldortextviewwhenyoupressandhold.)Therefore,anunmodifiedmenucontroller(onethatyoudonotsetthemenuitemsfor)alreadyhasdefaultmenuitemsthatitpresents,likeCut,Copy,andotherfamiliaroptions.Eachitemhasanactionmessagewiredup.Forexample,cut:issenttotheviewpresentingthemenucontrollerwhentheCutmenuitemistapped.
AllinstancesofUIResponderimplementthesemethods,but,bydefault,thesemethodsdonotdoanything.SubclasseslikeUITextFieldoverridethesemethodstodosomethingappropriatefortheircontext,likecutthecurrentlyselectedtext.ThemethodsarealldeclaredintheUIResponderStandardEditActionsprotocol.
IfyouoverrideamethodfromUIResponderStandardEditActionsinaview,itsmenuitemwillautomaticallyappearinanymenuyoushowforthatview.ThisworksbecausethemenucontrollercallsthemethodcanPerformAction(_:withSender:)onitsview,whichreturnstrueorfalsedependingonwhethertheviewimplementsthismethod.
Ifyouwanttoimplementoneofthesemethodsbutdonotwantittoappearinthemenu,youcanoverridecanPerformAction(_:withSender:)toreturnfalse.overridefunccanPerformAction(action:Selector,withSendersender:AnyObject?)->Bool{
ifaction=="copy:"{returnfalse}else{//Elsereturnthedefaultbehaviorreturnsuper.canPerformAction(action,withSender:sender)}}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
19WebServices
Inthenextfourchapters,youwillcreateanapplicationnamedPhotoramathatreadsinalistofrecentphotosfromFlickr.Thischapterwilllaythefoundationandfocusonimplementingthewebservicerequestsresponsibleforfetchingthemetadataforrecentphotosaswellasdownloadingtheimagedataforaspecificphoto.InChapter20,youwilldisplayalloftherecentphotosinagridlayout.Figure19.1showsPhotoramaattheendofthischapter.
Figure19.1Photorama
YourwebbrowserusestheHTTPprotocoltocommunicatewithawebserver.Inthesimplestinteraction,thebrowsersendsarequesttotheserverspecifyingaURL.Theserverrespondsbysendingbacktherequestedpage(typicallyHTMLandimages),whichthebrowserformatsanddisplays.
Inmorecomplexinteractions,browserrequestsincludeotherparameters,suchasformdata.Theserverprocessestheseparametersandreturnsacustomized,ordynamic,webpage.
Webbrowsersarewidelyusedandhavebeenaroundforalongtime,sothetechnologiessurroundingHTTParestableandwelldeveloped:HTTPtrafficpassesneatlythroughmostfirewalls,webserversareverysecureandhavegreatperformance,andwebapplicationdevelopmenttoolshavebecomeeasytouse.
YoucanwriteaclientapplicationforiOSthatleveragestheHTTPinfrastructuretotalktoaweb-enabledserver.Theserversideofthisapplicationisawebservice.YourclientapplicationandthewebservicecanexchangerequestsandresponsesviaHTTP.
WOW! eBook www.wowebook.org
BecausetheHTTPprotocoldoesnotcarewhatdataittransports,theseexchangescancontaincomplexdata.ThisdataistypicallyinJSON(JavaScriptObjectNotation)orXML(ExtensibleMarkupLanguage)format.Ifyoucontrolthewebserveraswellastheclient,youcanuseanyformatyoulike.Ifnot,youhavetobuildyourapplicationtousewhatevertheserversupports.
PhotoramawillmakeawebservicerequesttogetrecentphotosfromFlickr.Thewebserviceishostedathttps://api.flickr.com/services/rest.ThedatathatisreturnedwillbeJSONthatdescribesthephotos.
WOW! eBook www.wowebook.org
StartingthePhotoramaApplicationCreateanewSingleViewApplicationfortheUniversaldevicefamily.NamethisapplicationPhotorama,asshowninFigure19.2.
Figure19.2Creatingasingleviewapplication
Let’sknockoutthebasicUIbeforefocusingonwebservices.CreateanewSwiftfilenamedPhotosViewController.InPhotosViewController.swift,definethePhotosViewControllerclassandgiveitanimageViewproperty.importFoundationimportUIKit
classPhotosViewController:UIViewController{
@IBOutletvarimageView:UIImageView!
}
Intheprojectnavigator,deletetheexistingViewController.swift.
OpenMain.storyboardandselecttheViewController.OpenitsidentityinspectorandchangetheClasstoPhotosViewController.WiththePhotosViewControllerstillselected,selecttheEditormenuandchooseEmbedIn→NavigationController.
SelecttheNavigationControllerandopenitsattributesinspector.UndertheViewControllerheading,makesuretheboxforIsInitialViewControllerischecked.
DraganImageViewontothecanvasforPhotosViewControllerandaddconstraintstopinittoalledgesofthesuperview.ConnecttheimageviewtotheimageViewoutletonPhotosViewController.OpentheattributesinspectorfortheimageviewandchangetheModetoAspectFill.
Finally,double-clickonthecenterofthenavigationbarforthePhotosViewControllerand
WOW! eBook www.wowebook.org
giveitatitleofPhotorama.YourinterfacewilllooklikeFigure19.3.
Figure19.3InitialPhotoramainterface
Buildandruntheapplicationtomakesuretherearenoerrors.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BuildingtheURLCommunicationwithserversisdoneviarequests.Arequestencapsulatesinformationabouttheinteractionbetweentheapplicationandtheserver,anditsmostimportantpieceofinformationisthedestinationURL.
Inthissection,youwillbuilduptheURLforretrievingrecentphotosfromtheFlickrwebservice.Thearchitectureoftheapplicationwillreflectbestpractices.Forexample,eachtypethatyoucreatewillencapsulateasingleresponsibility.Thiswillmakeyourtypesrobustandflexibleandyourapplicationeasiertoreasonabout.TobeagoodiOSdeveloper,younotonlyneedtogetthejobdone,youalsoneedtogetitdonethoughtfullyandwithforesight.
FormattingURLsandrequests
Theformatofawebservicerequestvariesdependingontheserverthattherequestisreachingoutto.Therearenoset-in-stoneruleswhenitcomestowebservices.Youwillneedtofindthedocumentationforthewebservicetoknowhowtoformatarequest.Aslongasaclientapplicationsendstheserverwhatitwants,youhaveaworkingexchange.
Flickr’srecentphotoswebservicewantsaURLthatlookslikethis:https://api.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=a6d819499131071f158fd740860a5a88&extras=url_h,date_taken&format=json&nojsoncallback=1
Webservicerequestscomeinallsortsofformats,dependingonwhatthecreatorofthatwebserviceistryingtoaccomplish.Therecentphotoswebservice,wherepiecesofinformationarebrokenupintokey-valuepairs,isprettycommon.
Thekey-valuepairsthataresuppliedaspartoftheURLarecalledqueryitems.EachofthequeryitemsfortherecentphotosrequestisdefinedbyandisuniquetotheFlickrAPI.
ThemethoddetermineswhichendpointyouwanttohitontheFlickrAPI.Fortherecentphotos,thisisthestring“flickr.photos.getRecent”.
Theapi_keyisakeythatFlickrgeneratestoauthorizeanapplicationtousetheFlickrAPI.
Theextrasareattributespassedintocustomizetheresponse.Here,theurl_h,date_takenvaluetellstheFlickrserverthatyouwantthephotoURLstoalsocomebackintheresponsealongwiththedatethephotowastaken.
TheformatitemspecifiesthatyouwantthepayloadcomingbacktobeJSON.
ThenojsoncallbackitemspecifiesthatyouwantJSONbackinitsrawformat.
NSURLComponents
Youwillcreatetwotypestodealwithallofthewebserviceinformation.The
WOW! eBook www.wowebook.org
FlickrAPIstructwillberesponsibleforknowingandhandlingallFlickr-relatedinformation.ThisincludesknowinghowtogeneratetheURLsthattheFlickrAPIexpectsaswellasknowingtheformatoftheincomingJSONandhowtoparsethatJSONintotherelevantmodelobjects.ThePhotoStoreclasswillhandletheactualwebservicecalls.Let’sstartbycreatingtheFlickrAPIstruct.
CreateanewSwiftfilenamedFlickrAPI.ThisfilewilldefineaFlickrAPIstructthatwillcontainalloftheknowledgethatisspecifictotheFlickrAPI.
InFlickrAPI.swift,declaretheFlickrAPIstruct.importFoundation
structFlickrAPI{
}
AnenumerationwillbeusedtospecifywhichendpointontheFlickrservertohit.Forthisapplication,youwillonlybeworkingwiththeendpointtogetrecentphotos.However,FlickrsupportsmanyadditionalAPIs,suchassearchingforimagesbasedonastring.Usinganenumnowwillmakeiteasiertoaddendpointsinthefuture.
InFlickrAPI.swift,createtheMethodenumeration.EachcaseofMethodhasarawvaluethatmatchesthecorrespondingFlickrendpoint.Also,defineaconstantforthebaseURLstring.importFoundation
enumMethod:String{caseRecentPhotos="flickr.photos.getRecent"}
structFlickrAPI{
}
InChapter2,youlearnedthatenumerationscanhaverawvaluesassociatedwiththem.AlthoughtherawvaluesareoftenInts,youcanseehereagreatuseofStringastherawvaluefortheMethodenumeration.
Nowdeclareatype-levelpropertytoreferencethebaseURLstringforthewebservicerequests.enumMethod:String{caseRecentPhotos="flickr.photos.getRecent"}
structFlickrAPI{
staticletbaseURLString="https://api.flickr.com/services/rest"}
Atype-levelproperty(ormethod)isonethatisaccessedonthetypeitself–inthiscase,theFlickrAPItype.Forstructs,typepropertiesandmethodsaredeclaredwiththestatickeyword;classesusetheclasskeyword.YouusedatypemethodontheUIViewclassinChapter8whenyoucalledtheanimateWithDuration(_:animations:)method.YoualsousedatypemethodonUIImagePickerControllerinChapter14whenyoucalledtheisSourceTypeAvailable(_:)method.Here,youaredeclaringatype-levelpropertyonFlickrAPI.
WOW! eBook www.wowebook.org
ThebaseURLStringisanimplementationdetailoftheFlickrAPItype,andnootherfileneedstoknowaboutit.Instead,theywillaskforacompletedURLfromFlickrAPI.TokeepotherfilesfrombeingabletoaccessbaseURLString,youwillmarkthepropertyasprivate.structFlickrAPI{
privatestaticletbaseURLString="https://api.flickr.com/services/rest"}
Thisiscalledaccesscontrol.Youcancontrolwhatcanaccessthepropertiesandmethodsonyourowntypes.Therearethreelevelsofaccesscontrolthatcanbeappliedtotypes,properties,andmethods:
public–Anythingcanaccessthistype,property,ormethod.Thisincludeseverythinginyourproject(whichisonemodule)aswellascodefromothermodules(suchasthird-partyframeworks).
internal–Thisisthedefault.Anythinginthecurrentmodulecanaccessthistype,property,ormethod.Foranapp,onlyfileswithinyourprojectcanaccessthese.Ifyouwriteathird-partylibrary,thenonlyfileswithinthatthird-partylibrarycanaccessthem–appsthatuseyourthird-partylibrarycannot.
private–Anythinginthesamesourcefilecanseethistype,property,ormethod.Thisisdifferentthanotherlanguageswhereonlythetypecanseeit.InSwift,thevisibilityforthingsmarkedasprivateisthesourcefile(the.swiftfile).
NowyouaregoingtocreateatypemethodthatbuildsuptheFlickrURLforaspecificendpoint.Thismethodwillaccepttwoarguments:thefirstwillspecifywhichendpointtohit,usingtheMethodenumeration,andthesecondwillbeanoptionaldictionaryofqueryitemparametersassociatedwiththerequest.
ImplementthismethodinyourFlickrAPIstructinFlickrAPI.swift.Fornow,thismethodwillreturnanemptyURL.privatestaticfuncflickrURL(methodmethod:Method,parameters:[String:String]?)->NSURL{
returnNSURL()}
NoticethattheflickrURL(method:parameters:)methodisprivate.ItisanimplementationdetailoftheFlickrAPIstruct.AninternaltypemethodwillbeexposedtotherestoftheprojectforeachofthespecificendpointURLs(whichiscurrentlyjusttherecentphotosendpoint).TheseinternaltypemethodswillcallthroughtotheflickrURL(method:parameters:)method.
InFlickrAPI.swift,defineandimplementtherecentPhotosURL()method.staticfuncrecentPhotosURL()->NSURL{returnflickrURL(method:.RecentPhotos,parameters:["extras":"url_h,date_taken"])}
TimetoconstructthefullURL.YouhavethebaseURLdefinedasaconstant,andthe
WOW! eBook www.wowebook.org
queryitemsarebeingpassedintotheflickrURL(method:parameters:)methodviatheparametersargument.YouwillbuilduptheURLusingtheNSURLComponentsclass,whichisdesignedtotakeinthesevariouscomponentsandconstructanNSURLfromthem.
UpdatetheflickrURL(method:parameters:)methodtoconstructaninstanceofNSURLComponentsfromthebaseURL.Then,loopovertheincomingparametersandcreatetheassociatedNSURLQueryIteminstances.privatestaticfuncflickrURL(methodmethod:Method,parameters:[String:String]?)->NSURL?{
returnNSURL()
letcomponents=NSURLComponents(string:baseURLString)!
varqueryItems=[NSURLQueryItem]()
ifletadditionalParams=parameters{for(key,value)inadditionalParams{letitem=NSURLQueryItem(name:key,value:value)queryItems.append(item)}}components.queryItems=queryItems
returncomponents.URL!}
ThelaststepinsettinguptheURListopassintheparametersthatarecommontoallrequests:method,api_key,format,andnojsoncallback.
TheAPIkeyisatokengeneratedbyFlickrtoidentifyyourapplicationandauthenticateitwiththewebservice.WehavegeneratedanAPIkeyforthisapplicationbycreatingaFlickraccountandregisteringthisapplication.(IfyouwouldlikeyourownAPIkey,youwillneedtoregisteranapplicationathttps://www.flickr.com/services/apps/create/.)
InFlickrAPI.swift,createaconstantthatreferencesthistoken.structFlickrAPI{
privatestaticletbaseURLString="https://api.flickr.com/services/rest"privatestaticletAPIKey="a6d819499131071f158fd740860a5a88"
Double-checktomakesureyouhavetypedintheAPIkeyexactlyaspresentedhere.Ithastomatchexactlyortheserverwillrejectyourrequests.IfyourAPIkeyisnotworkingorifyouhaveanyproblemswiththerequests,checkouttheforumsathttp://forums.bignerdranch.comforhelp.
FinishimplementingflickrURL(method:parameters:)toaddthecommonqueryitemstotheNSURLComponents.privatestaticfuncflickrURL(methodmethod:Method,parameters:[String:String]?)->NSURL{
letcomponents=NSURLComponents(string:baseURLString)!
varqueryItems=[NSURLQueryItem]()
letbaseParams=["method":method.rawValue,"format":"json",
WOW! eBook www.wowebook.org
"nojsoncallback":"1","api_key":APIKey]
for(key,value)inbaseParams{letitem=NSURLQueryItem(name:key,value:value)queryItems.append(item)}
ifletadditionalParams=parameters{for(key,value)inadditionalParams{letitem=NSURLQueryItem(name:key,value:value)queryItems.append(item)}}components.queryItems=queryItems
returncomponents.URL!}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SendingtheRequestAURLrequestencapsulatesinformationaboutthecommunicationfromtheapplicationtotheserver.Mostimportantly,itspecifiestheURLoftheserverfortherequest,butitalsohasatimeoutinterval,acachepolicy,andothermetadataabouttherequest.ArequestisrepresentedbytheNSURLRequestclass.CheckouttheFortheMoreCurioussectionattheendofthischapterformoreinformation.
TheNSURLSessionAPIisacollectionofclassesthatusearequesttocommunicatewithaserverinanumberofways.TheNSURLSessionTaskclassisresponsibleforcommunicatingwithaserver.TheNSURLSessionclassisresponsibleforcreatingtasksthatmatchagivenconfiguration.
Anewclass,PhotoStore,willberesponsibleforinitiatingthewebservicerequests.ItwillusetheNSURLSessionAPIandtheFlickrAPIstructtofetchalistofrecentphotosanddownloadtheimagedataforeachphoto.
CreateanewSwiftfilenamedPhotoStore.
OpenPhotoStore.swiftanddeclarethePhotoStoreclass.importFoundation
classPhotoStore{
}
NSURLSession
Let’slookatafewofthepropertiesonNSURLRequest:
allHTTPHeaderFields–ThisisadictionaryofmetadataabouttheHTTPtransaction,includingcharacterencodingandhowtheservershouldhandlecaching.
allowsCellularAccess–ThisBooleanrepresentswhetherarequestisallowedtousecellulardata.
cachePolicy–Thisdeterminesifandhowthelocalcacheshouldbeused.
HTTPMethod–Thisistherequestmethod.ThedefaultisGET;othervaluesarePOST,PUT,andDELETE.
timeoutInterval–Thisisthemaximumdurationaconnectiontotheserverwillbeattemptedfor.
TheclassthatcommunicateswiththewebserviceisaninstanceofNSURLSessionTask.Therearethreekindsoftasks:datatasks,downloadtasks,anduploadtasks.NSURLSessionDataTaskretrievesdatafromtheserverandreturnsitasNSDatainmemory.NSURLSessionDownloadTaskretrievesdatafromtheserverandreturnsitasafilesavedtothefilesystem.NSURLSessionUploadTasksendsdatatotheserver.
WOW! eBook www.wowebook.org
Often,youwillhaveagroupofrequeststhathavemanypropertiesincommon.Forexample,maybesomedownloadsshouldneverhappenovercellulardata,ormaybecertainrequestsshouldbecacheddifferentlythanothers.Itcanbecometedioustoconfigurerelatedrequeststhesameway.
ThisiswhereNSURLSessioncomesinhandy.NSURLSessionactsasafactoryforNSURLSessionTaskinstances.Thesessioniscreatedwithaconfigurationthatspecifiespropertiesthatarecommonacrossallofthetasksthatitcreates.AlthoughmanyapplicationsmightonlyneedtouseasingleinstanceofNSURLSession,havingthepowerandflexibilityofmultiplesessionsisagreattooltohaveatyourdisposal.
InPhotoStore.swift,addapropertytoholdontoaninstanceofNSURLSession.classPhotoStore{
letsession:NSURLSession={letconfig=NSURLSessionConfiguration.defaultSessionConfiguration()returnNSURLSession(configuration:config)}()
}
InPhotoStore.swift,implementthefetchRecentPhotos()methodtocreateanNSURLRequestthatconnectstoapi.flickr.comandasksforthelistofrecentphotos.Then,usetheNSURLSessiontocreateanNSURLSessionDataTaskthattransfersthisrequesttotheserver.funcfetchRecentPhotos(){
leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
ifletjsonData=data{ifletjsonString=NSString(data:jsonData,encoding:NSUTF8StringEncoding){print(jsonString)}}elseifletrequestError=error{print("Errorfetchingrecentphotos:\(requestError)")}else{print("Unexpectederrorwiththerequest")}}task.resume()}
CreatingtheNSURLRequestisfairlystraightforward:youcreateanNSURLinstanceusingtheFlickrAPIstructandinstantiatearequestobjectwithit.
Bygivingthesessionarequestandacompletionclosuretocallwhentherequestfinishes,thesessionwillreturnaninstanceofNSURLSessionTask.SincePhotoramaisrequestingdatafromawebservice,thetypeoftaskwillbeaninstanceofNSURLSessionDataTask.Tasksarealwayscreatedinthesuspendedstate,socallingresume()onthetaskwillstartthewebservicerequest.Fornow,thecompletionblockwilljustprintouttheJSONdatareturnedfromtherequest.
Tomakearequest,PhotosViewControllerwillcalltheappropriatemethodson
WOW! eBook www.wowebook.org
PhotoStore.Todothis,PhotosViewControllerneedsareferencetoaninstanceofPhotoStore.
AtthetopofPhotosViewController.swift,addapropertytohangontoaninstanceofPhotoStore.classPhotosViewController:UIViewController{
@IBOutletvarimageView:UIImageView!varstore:PhotoStore!
ThestoreisadependencyofthePhotosViewController.YouwillusepropertyinjectiontogivethePhotosViewControlleritsstoredependency,justasyoudidwiththeviewcontrollersinHomepwner.
OpenAppDelegate.swiftandusepropertyinjectiontogivethePhotosViewControlleraninstanceofPhotoStore.funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{//Overridepointforcustomizationafterapplicationlaunch.
letrootViewController=window!.rootViewControlleras!UINavigationControllerletphotosViewController=rootViewController.topViewControlleras!PhotosViewControllerphotosViewController.store=PhotoStore()
returntrue}
NowthatthePhotosViewControllercaninteractwiththePhotoStore,kickoffthewebserviceexchangewhentheviewcontrolleriscomingonscreenforthefirsttime.
InPhotosViewController.swift,overrideviewDidLoad()andfetchtherecentphotos.overridefuncviewDidLoad(){super.viewDidLoad()
store.fetchRecentPhotos()}
Buildandruntheapplication.AstringrepresentationoftheJSONdatacomingbackfromthewebservicewillprinttotheconsole.(Ifyoudonotseeanythingprinttotheconsole,makesureyoutypedtheURLandAPIkeycorrectly.)
TheresponsewilllooksomethinglikeFigure19.4.
WOW! eBook www.wowebook.org
Figure19.4Webserviceconsoleoutput
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ModelingthePhotoNext,youwillcreateaPhotoclasstorepresenteachphotothatisreturnedfromthewebservicerequest.Therelevantpiecesofinformationthatyouwillneedforthisapplicationaretheid,thetitle,theurl_h,andthedatetaken.
CreateanewSwiftfileandnameitPhoto.OpenthisfileanddeclarethePhotoclasswithpropertiesforthephotoID,thetitle,andtheremoteURL.Finally,addadesignatedinitializerthatsetsuptheinstance.importFoundation
classPhoto{
lettitle:StringletremoteURL:NSURLletphotoID:StringletdateTaken:NSDate
init(title:String,photoID:String,remoteURL:NSURL,dateTaken:NSDate){self.title=titleself.photoID=photoIDself.remoteURL=remoteURLself.dateTaken=dateTaken}}
YouwillusethisclassshortlyonceyouareparsingtheJSONdata.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
JSONDataJSONdata,especiallywhenitiscondensedlikeitisinyourconsole,mayseemdaunting.However,itisactuallyaverysimplesyntax.JSONcancontainthemostbasictypesusedtorepresentmodelobjects:arrays,dictionaries,strings,andnumbers.AJSONdictionarycontainsoneormorekey-valuepairs,wherethekeyisastringandthevaluecanbeanotherdictionaryorastring,number,orarray.Anarraycanconsistofstrings,numbers,dictionaries,andotherarrays.Thus,aJSONdocumentisanestedsetofthesetypesofvalues.
HereisanexampleofsomereallysimpleJSON:{"name":"Christian","friends":["Stacy","Mikey"],"job":{"company":"BigNerdRanch","title":"SeniorNerd"}}
ThisJSONdocumentbeginsandendswithcurlybraces({and}),whichinJSONdelimitadictionary.Withinthecurlybracesarethekey-valuepairsthatbelongtothedictionary.Thisdictionarycontainsthreekey-valuepairs(name,friends,andjob).
Astringisrepresentedbytextwithinquotationmarks.Stringsareusedasthekeyswithinadictionaryandcanbeusedasvalues,too.Thus,thevalueassociatedwiththenamekeyinthetop-leveldictionaryisthestringChristian.
Arraysarerepresentedwithsquarebrackets([and]).AnarraycancontainanyotherJSONinformation.Inthiscase,thefriendskeyholdsanarrayofstrings(StacyandMikey).
Adictionarycancontainotherdictionaries,andthefinalkeyinthetop-leveldictionary,job,isassociatedwithadictionarythathastwokey-valuepairs(companyandtitle).
PhotoramawillparseouttheusefulinformationfromtheJSONdataandstoreitinaPhotoinstance.
NSJSONSerialization
Applehasabuilt-inclassforparsingJSONdata,NSJSONSerialization.YoucanhandthisclassabunchofJSONdata,anditwillcreateadictionaryforeveryJSONdictionary(theJSONspecificationcallsthese“objects”),anarrayforeveryJSONarray,aStringforeveryJSONstring,andanNSNumberforeveryJSONnumber.Let’sseehowthisclasshelpsyou.
OpenPhotoStore.swiftandupdatefetchRecentPhotos()toprinttheJSONobjecttotheconsole.funcfetchRecentPhotos(){
leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)
WOW! eBook www.wowebook.org
lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
ifletjsonData=data{ifletjsonString=NSString(data:jsonData,encoding:NSUTF8StringEncoding){print(jsonString)}do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(jsonData,options:[])print(jsonObject)}catchleterror{print("ErrorcreatingJSONobject:\(error)")}}elseifletrequestError=error{print("Errorfetchingrecentphotos:\(requestError)")}else{print("Unexpectederrorwiththerequest")}}task.resume()}
Buildandrun,thenchecktheconsole.YouwillseetheJSONdataagain,butnowitwillbeformatteddifferentlybecauseprint()doesagoodjobformattingdictionariesandarrays.
TheformatoftheJSONdataisdictatedbytheAPI,soyouwilladdthecodetoparsetheJSONtotheFlickrAPIstruct.
Parsingthedatathatcomesbackfromtheservercouldgowronginanumberofways:ThedatamightnotcontainJSON.Thedatacouldbecorrupt.ThedatamightcontainJSONbutnotmatchtheformatthatyouexpect.Tomanagethepossibilityoffailure,youwilluseanenumerationwithassociatedvaluestorepresentthesuccessorfailureoftheparsing.
Enumerationsandassociatedvalues
YoulearnedaboutthebasicsofenumerationsinChapter2,andyouhavebeenusingthemthroughoutthisbook–includingtheMethodenumusedearlierinthischapter.Associatedvaluesareausefulfeatureofenumerations.Let’stakeamomenttolookatasimpleexamplebeforeyouusethisfeatureinPhotorama.
Enumerationsareaconvenientwayofdefiningandrestrictingthepossiblevaluesforavariable.Forexample,let’ssayyouareworkingonahomeautomationapp.Youcoulddefineanenumerationtospecifytheovenstate,likethis:enumOvenState{caseOncaseOff}
Iftheovenison,youalsoneedtoknowwhattemperatureitissetto.Associatedvaluesareaperfectsolutiontothissituation.enumOvenState{caseOn(Double)
WOW! eBook www.wowebook.org
caseOff}
varovenState=OvenState.On(450)
Eachcaseofanenumerationcanhavedataofanytypeassociatedwithit.ForOvenState,its.OncasehasanassociatedDoublethatrepresentstheoven’stemperature.Noticethatnotallcasesneedtohaveassociatedvalues.
Retrievingtheassociatedvaluefromanenumisoftendoneusingaswitchstatement.switchovenState{caselet.On(temperature):print("Theovenisonandsetto\(temperature)degrees.")case.Off:print("Theovenisoff.")}
Notethatthe.Oncaseusesaletkeywordtostoretheassociatedvalueinthetemperatureconstant,whichcanbeusedwithinthecaseclause.(Itcanusethevarkeywordinsteadiftemperatureneedstobeavariable.)ConsideringthevaluegiventoovenState,theswitchstatementabovewouldresultintheline“Theovenisonandsetto450degrees.”printedtotheconsole.
Inthenextsection,youwilluseanenumerationwithassociatedvaluestotieasuccessfulresultstatusofarequesttotheFlickrwebservicewiththedatacontainingrecentphotosortieafailureresultstatuswitherrorinformation.
ParsingJSONdata
InFlickrAPI.swift,addanenumerationnamedPhotosResulttothetopofthefilethathasacaseforbothsuccessandfailure.enumMethod:String{caseRecentPhotos="flickr.photos.getRecent"}
enumPhotosResult{caseSuccess([Photo])caseFailure(ErrorType)}
IfthedataisvalidJSONandcontainsanarrayofphotos,thosephotoswillbeassociatedwiththeSuccesscase.Ifthereareanyerrorsduringtheparsingprocess,therelevantErrorTypewillbepassedalongwiththeFailurecase.
ErrorTypeisaprotocolthatallerrorsconformto.NSErroristheerrorthatmanyiOSframeworksthrow,anditconformstoErrorType.YouwillcreateyourownErrorTypeshortly.
InFlickrAPI.swift,implementamethodthattakesinaninstanceofNSDataandusestheNSJSONSerializationclasstoconvertthedataintothebasicfoundationobjects.staticfuncphotosFromJSONData(data:NSData)->PhotosResult{do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(data,options:[])
varfinalPhotos=[Photo]()
WOW! eBook www.wowebook.org
return.Success(finalPhotos)}catchleterror{return.Failure(error)}}
(Thiscodewillgeneratesomewarnings.Youwillresolvethemshortly.)
IftheincomingdataisvalidJSONdata,thenthejsonObjectinstancewillreferencetheappropriatemodelobject.Ifnot,thentherewasaproblemwiththedataandyoupassalongtheerror.YounowneedtogetthephotoinformationoutoftheJSONobjectandintoinstancesofPhoto.
WhentheNSURLSessionDataTaskfinishes,youwilluseNSJSONSerializationtoconverttheJSONdataintoadictionary.Figure19.5showshowthedatawillbestructured.
AtthetopleveloftheincomingJSONdataisadictionary.Thevalueassociatedwiththe“photos”keycontainstheimportantinformation,andthemostimportantisthearrayofdictionaries.
Figure19.5JSONobjects
WOW! eBook www.wowebook.org
Asyoucansee,youhavetodigprettydeeplytogettheinformationthatyouneed.
IfthestructureoftheJSONdoesnotmatchyourexpectation,youwillreturnacustomerror.
AtthetopofFlickrAPI.swift,declareacustomenumtorepresentpossibleerrorsfortheFlickrAPI.enumPhotosResult{caseSuccess([Photo])caseFailure(ErrorType)}
enumFlickrError:ErrorType{caseInvalidJSONData}
InphotosFromJSONData(_:),digdownthroughtheJSONtogettothearrayofdictionariesrepresentingtheindividualphotos.staticfuncphotosFromJSONData(data:NSData)->PhotosResult{do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(data,options:[])
guardletjsonDictionary=jsonObjectas?[NSObject:AnyObject],photos=jsonDictionary["photos"]as?[String:AnyObject],photosArray=photos["photo"]as?[[String:AnyObject]]else{
//TheJSONstructuredoesn'tmatchourexpectationsreturn.Failure(FlickrError.InvalidJSONData)}
varfinalPhotos=[Photo]()return.Success(finalPhotos)}catchleterror{return.Failure(error)}}
ThenextstepistogetthephotoinformationoutofthedictionaryandintoPhotomodelobjects.
YouwillneedaninstanceofNSDateFormattertoconvertthedatetakenstringintoaninstanceofNSDate.
InFlickrAPI.swift,addaconstantinstanceofNSDateFormatter.privatestaticletbaseURLString="https://api.flickr.com/services/rest"privatestaticletAPIKey="a6d819499131071f158fd740860a5a88"
privatestaticletdateFormatter:NSDateFormatter={letformatter=NSDateFormatter()formatter.dateFormat="yyyy-MM-ddHH:mm:ss"returnformatter}()
StillinFlickrAPI.swift,writeanewmethodtoparseaJSONdictionaryintoaPhotoinstance.privatestaticfuncphotoFromJSONObject(json:[String:AnyObject])->Photo?{guardletphotoID=json["id"]as?String,title=json["title"]as?String,dateString=json["datetaken"]as?String,photoURLString=json["url_h"]as?String,url=NSURL(string:photoURLString),
WOW! eBook www.wowebook.org
dateTaken=dateFormatter.dateFromString(dateString)else{
//Don'thaveenoughinformationtoconstructaPhotoreturnnil}
returnPhoto(title:title,photoID:photoID,remoteURL:url,dateTaken:dateTaken)}
NowupdatephotosFromJSONData(_:)toparsethedictionariesintoPhotoobjectsandthenreturntheseaspartoftheSuccessenumerator.AlsohandlethepossibilitythattheJSONformathaschanged,sonophotoswereabletobefound.staticfuncphotosFromJSONData(data:NSData)->PhotosResult{
do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(data,options:[])
guardletjsonDictionary=jsonObjectas?[NSObject:AnyObject],photos=jsonDictionary["photos"]as?[String:AnyObject],photosArray=photos["photo"]as?[[String:AnyObject]]else{
//TheJSONstructuredoesn'tmatchourexpectationsreturn.Failure(FlickrError.InvalidJSONData)}
varfinalPhotos=[Photo]()forphotoJSONinphotosArray{ifletphoto=photoFromJSONObject(photoJSON){finalPhotos.append(photo)}}
iffinalPhotos.count==0&&photosArray.count>0{//Weweren'tabletoparseanyofthephotos//MaybetheJSONformatforphotoshaschangedreturn.Failure(FlickrError.InvalidJSONData)}return.Success(finalPhotos)}catchleterror{return.Failure(error)}}
Next,inPhotoStore.swift,writeanewmethodthatwillprocesstheJSONdatathatisreturnedfromthewebservicerequest.funcprocessRecentPhotosRequest(datadata:NSData?,error:NSError?)->PhotosResult{guardletjsonData=dataelse{return.Failure(error!)}
returnFlickrAPI.photosFromJSONData(jsonData)}
NowupdatefetchRecentPhotos()tousethemethodyoujustcreated.funcfetchRecentPhotos(){leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
ifletjsonData=data{do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(jsonData,options:[])print(jsonObject)}
WOW! eBook www.wowebook.org
catchleterror{print("ErrorcreatingJSONobject:\(error)")}}elseifletrequestError=error{print("Errorfetchingrecentphotos:\(requestError)")}else{print("Unexpectederrorwiththerequest")}
letresult=self.processRecentPhotosRequest(data:data,error:error)}task.resume()}
NowupdatethemethodsignatureforfetchRecentPhotos()totakeinacompletionclosurethatwillbecalledoncethewebservicerequestiscompleted.funcfetchRecentPhotos(){funcfetchRecentPhotos(completioncompletion:(PhotosResult)->Void){leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
letresult=self.processRecentPhotosRequest(data:data,error:error)completion(result)}task.resume()}
Fetchingdatafromawebserviceisanasynchronousprocess.Oncetherequeststarts,itmaytakeanontrivialamountoftimeforaresponsetocomebackfromtheserver.Becauseofthis,thefetchRecentPhotos(_:)methodcannotdirectlyreturnaninstanceofPhotosResult.Instead,thecallerofthismethodwillsupplyacompletionclosureforthePhotoStoretocalloncetherequestiscomplete.
ThisfollowsthesamepatternthatNSURLSessionTaskuseswithitscompletionhandler:thetaskiscreatedwithaclosureforittocalloncethewebservicerequestcompletes.Figure19.6describestheflowofdatawiththewebservicerequest.
WOW! eBook www.wowebook.org
Figure19.6Webservicerequestdataflow
InPhotosViewController.swift,updatetheimplementationoftheviewDidLoad()toprintouttheresultofthewebservicerequest.overridefuncviewDidLoad()super.viewDidLoad()
store.fetchRecentPhotos(){(photosResult)->Voidin
switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")caselet.Failure(error):print("Errorfetchingrecentphotos:\(error)")}
}}
Buildandruntheapplication.Oncethewebservicerequestcompletes,youshouldseethenumberofphotosfoundprintedtotheconsole.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DownloadingandDisplayingtheImageDataYouhavedonealotalreadyinthischapter:youhavesuccessfullyinteractedwiththeFlickrAPIviaawebservicerequest,andyouhaveparsedtheincomingJSONdataintoPhotomodelobjects.Unfortunately,youhavenothingtoshowforitexceptsomelogmessagesintheconsole.
Inthissection,youwillusetheURLreturnedfromthewebservicerequesttodownloadtheimagedata.ThenyouwillcreateaninstanceofUIImagefromthatdataand,finally,youwilldisplaythefirstimagereturnedfromtherequestinaUIImageView.(Inthenextchapter,youwilldisplayalloftheimagesthatarereturnedinagridlayoutdrivenbyaUICollectionView.)
Thefirststepisdownloadingtheimagedata.Thisprocesswillbeverysimilartothewebservicerequesttodownloadthephotos’JSONdata.
OpenPhoto.swiftandgiveitanoptionalUIImageproperty.ImporttheUIKitframeworkinsteadofFoundation.importFoundationimportUIKit
classPhoto{
lettitle:StringletremoteURL:NSURLletphotoID:StringletdateTaken:NSDatevarimage:UIImage?
OpenPhotoStore.swift,importUIKit,andaddanenumerationtothetopofthefilethatrepresentstheresultofdownloadingtheimage.ThisenumerationwillfollowthesamepatternasthePhotosResultenumeration,takingadvantageofassociatedvalues.YouwillalsocreateanErrorTypetorepresentphotoerrors.importFoundationimportUIKit
enumImageResult{caseSuccess(UIImage)caseFailure(ErrorType)}
enumPhotoError:ErrorType{caseImageCreationError}
Ifthedownloadissuccessful,theSuccesscasewillhavetheUIImageassociatedwithit.Ifthereisanerror,theFailurecasewillhavetheErrorTypeassociatedwithit.
Now,inthesamefile,implementamethodtodownloadtheimagedata.LikethefetchRecentPhotos(_:)method,thisnewmethodwilltakeinacompletionclosurethatwillreturnaninstanceofImageResult.funcfetchImageForPhoto(photo:Photo,completion:(ImageResult)->Void){
letphotoURL=photo.remoteURLletrequest=NSURLRequest(URL:photoURL)
lettask=session.dataTaskWithRequest(request){
WOW! eBook www.wowebook.org
(data,response,error)->Voidin
}task.resume()}
Nowimplementamethodthatprocessesthedatafromthewebservicerequestintoanimage,ifpossible.funcprocessImageRequest(datadata:NSData?,error:NSError?)->ImageResult{
guardletimageData=data,image=UIImage(data:imageData)else{
//Couldn'tcreateanimageifdata==nil{return.Failure(error!)}else{return.Failure(PhotoError.ImageCreationError)}}
return.Success(image)}
StillinPhotoStore.swift,updatefetchImageForPhoto(_:completion:)tousethisnewmethod.funcfetchImageForPhoto(photo:Photo,completion:(ImageResult)->Void){
letphotoURL=photo.remoteURLletrequest=NSURLRequest(URL:photoURL)
lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
letresult=self.processImageRequest(data:data,error:error)
ifcaselet.Success(image)=result{photo.image=image}
completion(result)}task.resume()}
Sinceyouonlyneedtohandlethe.Successcase,youuseanifcasestatementtocheckwhetherresulthasavalueof.Success.Thisisidenticaltoonlyhandlingthatcaseinaswitchstatement://Thiscodeifcaselet.Success(image)=result{photo.image=image}
//Behavesjustlikethiscodeswitchresult{caselet.Success(image):photo.image=imagecase.Failure(_):break}
Totestthiscode,youwilldownloadtheimagedataforthefirstphotothatisreturnedfromtherecentphotosrequestanddisplayitontheimageview.
OpenPhotosViewController.swiftandupdateviewDidLoad().
WOW! eBook www.wowebook.org
overridefuncviewDidLoad()super.viewDidLoad()
store.fetchRecentPhotos(){(photosResult)->Voidin
switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")
ifletfirstPhoto=photos.first{self.store.fetchImageForPhoto(firstPhoto){(imageResult)->Voidin
switchimageResult{caselet.Success(image):self.imageView.image=imagecaselet.Failure(error):print("Errordownloadingimage:\(error)")}}}caselet.Failure(error):print("Errorfetchingrecentphotos:\(error)")}}}
Althoughyoucouldbuildandruntheapplicationatthispoint,theimagemayormaynotappearintheimageviewwhenthewebservicerequestfinishes.Why?Thecodethatupdatestheimageviewisnotbeingrunonthemainthread.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
TheMainThreadModerniOSdeviceshavemulticoreprocessorsthatenablethemtorunmultiplechunksofcodesimultaneously.Thesecomputationsproceedinparallel,sothisisreferredtoasparallelcomputing.Whendifferentcomputationsareinflightatthesametime,thisisknownasconcurrency,andthecomputationsaresaidtobehappeningconcurrently.Acommonwaytoexpressthisisbyrepresentingeachcomputationwithadifferentthreadofcontrol.
Sofarinthisbook,allofyourcodehasbeenrunningonthemainthread.ThemainthreadissometimesreferredtoastheUI(userinterface)thread,becauseanycodethatmodifiestheUImustrunonthemainthread.
Whenthewebservicecompletes,youwantittoupdatetheimageview.Butbydefault,NSURLSessionDataTaskrunsthecompletionhandleronabackgroundthread.Youneedawaytoforcecodetorunonthemainthreadinordertoupdatetheimageview.YoucandothateasilyusingtheNSOperationQueueclass.
InPhotosViewController.swift,updateviewDidLoad()tocallthecompletionclosureonthemainthread.overridefuncviewDidLoad()super.viewDidLoad()
store.fetchRecentPhotos(){(photosResult)->Voidin
switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")
ifletfirstPhoto=photos.first{self.store.fetchImageForPhoto(firstPhoto){(imageResult)->Voidin
switchimageResult{caselet.Success(image):self.imageView.image=imageNSOperationQueue.mainQueue().addOperationWithBlock{self.imageView.image=image}caselet.Failure(error):print("Errordownloadingimage:\(error)")}}}caselet.Failure(error):print("Errorfetchingrecentphotos:\(error)")}}}
Buildandruntheapplication.Nowthattheimageviewisbeingupdatedonthemainthread,youwillhavesomethingtoshowforallyourhardwork:theimagewillappearwhenthewebservicerequestfinishes.(Itmighttakealittletimetoshowtheimageifthewebservicerequesttakesawhiletofinish.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:PrintingtheResponseInformationThecompletionhandlerfordataTaskWithRequest(_:completionHandler:)providesaninstanceofNSURLResponse.WhenmakingHTTPrequests,thisresponseisoftypeNSURLHTTPResponse(asubclassofNSURLResponse).PrintthestatusCodeandheaderFieldstotheconsole.Thesepropertiesareveryusefulwhendebuggingwebservicecalls.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
FortheMoreCurious:HTTPWhenNSURLSessionTaskinteractswithawebserver,itdoessoaccordingtotherulesoutlinedintheHTTPspecification.Thespecificationisveryclearabouttheexactformatoftherequest/responseexchangebetweentheclientandtheserver.AnexampleofasimpleHTTPrequestisshowninFigure19.7.
Figure19.7HTTPrequestformat
AnHTTPrequesthasthreeparts:arequestline,requestheaders,andanoptionalrequestbody.Therequestlineisthefirstlineoftherequestandtellstheserverwhattheclientistryingtodo.Inthisrequest,theclientistryingtoGETtheresourceat/index.html.(ItalsospecifiestheHTTPversionthattherequestwillbeconformingto.)
ThewordGETisanHTTPmethod.WhilethereareanumberofsupportedHTTPmethods,youwillseeGETandPOSTmostoften.ThedefaultofNSURLRequest,GET,indicatesthattheclientwantsaresourcefromtheserver.Theresourcerequestedmightbeanactualfileonthewebserver’sfilesystem,oritcouldbegenerateddynamicallyatthemomenttherequestisreceived.Asaclient,youshouldnotcareaboutthisdetail,butmorethanlikelytheJSONresourcesyourequestedinthischapterwerecreateddynamically.
Inadditiontogettingthingsfromaserver,youcansenditinformation.Forexample,manywebserversallowyoutouploadphotos.AclientapplicationwouldpasstheimagedatatotheserverthroughanHTTPrequest.Inthissituation,youwouldusetheHTTPmethodPOST,andyouwouldincludearequestbody.Thebodyofarequestisthepayloadyouaresendingtotheserver–typicallyJSON,XML,orbinarydata.
Whentherequesthasabody,itmustalsohavetheContent-Lengthheader.Handily,NSURLRequestwillcomputethesizeofthebodyandaddthisheaderforyou.
HereisanexampleofhowtoPOSTanimagetoanimaginarysiteusinganNSMutableURLRequest.ifletsomeURL=NSURL(string:"http://www.photos.example.com/upload"){letimage=profileImage()
WOW! eBook www.wowebook.org
letdata=UIImagePNGRepresentation(image)
letreq=NSMutableURLRequest(URL:someURL)
//ThisaddstheHTTPbodydataandautomaticallysetsthecontent-lengthheaderreq.HTTPBody=data
//ThischangestheHTTPmethodintherequestlinereq.HTTPMethod="POST"
//Ifyouwantedtosetarequestheader,suchastheAcceptheaderreq.setValue("text/json",forHTTPHeaderField:"Accept")}
Figure19.8showswhatasimpleHTTPresponsemightlooklike.WhileyouwillnotbemodifyingthecorrespondingNSHTTPURLResponseinstance,itisnicetounderstandwhatitismodeling.
Figure19.8HTTPresponseformat
Asyoucansee,theformatoftheresponseisnottoodifferentfromtherequest.Itincludesastatusline,responseheaders,and,ofcourse,theresponsebody.Yes,thisiswherethatpesky“404NotFound”comesfrom!
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
20CollectionViews
Inthischapter,youwillcontinueworkingonthePhotoramaapplicationbydisplayingtherecentFlickrphotosinagridusingtheUICollectionViewclass.Thischapterwillalsoreinforcethedatasourcedesignpatternthatyouusedinpreviouschapters.Figure20.1showsyouwhattheapplicationwilllooklikeattheendofthischapter.
Figure20.1Photoramawithacollectionview
InChapter9,youworkedwithUITableView.Tableviewsareagreatwaytodisplayandeditacolumnofinformationinahierarchicallist.Likeatableview,acollectionviewalsodisplaysanorderedcollectionofitems,butinsteadofdisplayingtheinformationinahierarchicallist,thecollectionviewhasalayoutobjectthatdrivesthedisplayofinformation.Youwilluseabuilt-inlayoutobject,theUICollectionViewFlowLayout,topresenttherecentphotosinascrollablegrid.
WOW! eBook www.wowebook.org
DisplayingtheGridLet’stackletheinterfacefirst.YouaregoingtochangetheuserinterfaceforPhotosViewControllertodisplayacollectionviewinsteadofdisplayingtheimageview.
OpenMain.storyboardandlocatethePhotoramaimageview.DeletetheimageviewfromthecanvasanddragaCollectionViewontothecanvas.Selectboththecollectionviewanditssuperview.(Theeasiestwaytodothisisusingthedocumentoutline.)OpentheAutoLayoutAlignmenu,configureitlikeFigure20.2,andclickAdd4Constraints.
Figure20.2Collectionviewconstraints
BecauseyouusedtheAlignmenutopintheedges,thecollectionviewwillbepinnedtothetopoftheentireviewinsteadoftothetoplayoutguide.Thisisusefulforscrollviews(andtheirsubclasses,likeUITableViewandUICollectionView)sothatthecontentwillscrollunderneaththenavigationbar.Thescrollviewwillautomaticallyupdateitsinsetstomakethecontentvisible,asyousawinChapter9.ThecanvaswillnowlooklikeFigure20.3.
WOW! eBook www.wowebook.org
Figure20.3Storyboardcanvas
Currently,thecollectionviewandthecollectionviewcellsbothhaveaclearbackgroundcolor.Opentheattributesinspectorforthecollectionviewandchangeitsbackgroundcolortowhite.Thenselectthecollectionviewcell–thesmallrectangleintheupper-leftcornerofthecollectionview–andgiveitablackbackgroundcolor.
Selecttheblackcollectionviewcellandopenitsattributesinspector.SettheIdentifiertoUICollectionViewCell(Figure20.4).
Figure20.4Settingthereuseidentifier
Thecollectionviewisnowonthecanvas,butyouneedawaytopopulatethecellswithdata.Todothis,youwillcreateanewclasstoactasthedatasourceofthecollectionview.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CollectionViewDataSourceApplicationsareconstantlychanging,sopartofbeingagoodiOSdeveloperisbuildingapplicationsinawaythatallowsthemtoadapttochangingrequirements.
ThePhotoramaapplicationwilldisplayasinglecollectionviewofphotos.YoucoulddosomethingsimilartowhatyoudidinHomepwnerandmakethePhotosViewControllerbethedatasourceofthecollectionview.Theviewcontrollerwouldimplementtherequireddatasourcemethods,andeverythingwouldworkjustfine.
Atleast,itwouldworkfornow.Whatif,sometimeinthefuture,youdecidedtohaveadifferentscreenthatalsodisplayedacollectionviewofphotos?Maybeinsteadofdisplayingtherecentphotos,itwoulduseadifferentwebservicetodisplayallthephotosmatchingasearchterm.Inthiscase,youwouldneedtoreimplementthesamedatasourcemethodswithinthenewviewcontrollerwithessentiallythesamecode.Thatwouldnotbeideal.
Instead,youwillabstractoutthecollectionviewdatasourcecodeintoanewclass.Thisclasswillberesponsibleforrespondingtodatasourcequestions–anditwillbereusableasnecessary.
CreateanewSwiftfilenamedPhotoDataSource.OpenthisfileanddeclarethePhotoDataSourceclass.importFoundationimportUIKit
classPhotoDataSource:NSObject,UICollectionViewDataSource{
varphotos=[Photo]()
}
ToconformtotheUICollectionViewDataSourceprotocol,atypealsoneedstoconformtotheNSObjectProtocol.TheeasiestandmostcommonwaytoconformtothisprotocolistosubclassfromNSObject,asyoudidabove.
TheUICollectionViewDataSourceprotocoldeclarestworequiredmethodstoimplement:funccollectionView(collectionView:UICollectionView,numberOfItemsInSectionsection:Int)->IntfunccollectionView(collectionView:UICollectionView,cellForItemAtIndexPathindexPath:NSIndexPath)->UICollectionViewCell
YoumightnoticethatthesetwomethodslookverysimilartothetworequiredmethodsofUITableViewDataSourcethatyousawinChapter9.Thefirstdatasourcecallbackaskshowmanycellstodisplay,andthesecondasksfortheUICollectionViewCelltodisplayforagivenindexpath.
ImplementthesetwomethodsinPhotoDataSource.swift.classPhotoDataSource:NSObject,UICollectionViewDataSource{
varphotos=[Photo]()
WOW! eBook www.wowebook.org
funccollectionView(collectionView:UICollectionView,numberOfItemsInSectionsection:Int)->Int{returnphotos.count}
funccollectionView(collectionView:UICollectionView,cellForItemAtIndexPathindexPath:NSIndexPath)->UICollectionViewCell{
letidentifier="UICollectionViewCell"letcell=collectionView.dequeueReusableCellWithReuseIdentifier(identifier,forIndexPath:indexPath)
returncell}}
Next,thecollectionviewneedstoknowthataninstanceofPhotoDataSourceisthedatasourceobject.
InPhotosViewController.swift,addapropertytoreferenceaninstanceofPhotoDataSourceandanoutletforaUICollectionViewinstance.Also,youwillnotneedtheimageViewanymore,sodeleteit.classPhotosViewController:UIViewController{
@IBOutletvarimageView:UIImageView!@IBOutletvarcollectionView:UICollectionView!
varstore:PhotoStore!letphotoDataSource=PhotoDataSource()
UpdateviewDidLoad()tosetthedatasourceonthecollectionview.overridefuncviewDidLoad(){super.viewDidLoad()
collectionView.dataSource=photoDataSource
...
Finally,updatethephotoDataSourceobjectwiththeresultofthewebservicerequestandreloadthecollectionview.overridefuncviewDidLoad()super.viewDidLoad()
collectionView.dataSource=photoDataSource
store.fetchRecentPhotos(){(photosResult)->Voidin
switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")ifletfirstPhoto=photos.first{self.store.fetchImageForPhoto(firstPhoto){(imageResult)->VoidinswitchimageResult{caselet.Success(image):NSOperationQueue.mainQueue().addOperationWithBlock(){self.imageView.image=image}caselet.Failure(error):print("Errordownloadingimage:\(error)")}}}caselet.Failure(error):print("Errorfetchingrecentphotos:\(error)")}
WOW! eBook www.wowebook.org
NSOperationQueue.mainQueue().addOperationWithBlock(){switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")self.photoDataSource.photos=photoscaselet.Failure(error):self.photoDataSource.photos.removeAll()print("Errorfetchingrecentphotos:\(error)")}self.collectionView.reloadSections(NSIndexSet(index:0))}}}
ThelastthingyouneedtodoismakethecollectionViewoutletconnection.
OpenMain.storyboardandnavigatetothecollectionview.Control-dragfromthePhotosViewControllertothecollectionviewandconnectittothecollectionViewoutlet.
Buildandruntheapplication.Afterthewebservicerequestcompletes,checktheconsoletoconfirmthatphotoswerefound.OntheiOSdevice,therewillbeagridofblacksquarescorrespondingtothenumberofphotosfound(Figure20.5).Thesecellsarearrangedinaflowlayout.Aflowlayoutfitsasmanycellsonarowaspossiblebeforeflowingdowntothenextrow.IfyourotatetheiOSdevice,youwillseethecellsfillthegivenarea.
Figure20.5Initialflowlayout
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CustomizingtheLayoutThedisplayofcellsisnotdrivenbythecollectionviewitselfbutbythecollectionview’slayout.Thelayoutobjectisresponsiblefortheplacementofcellsonscreen.Layouts,inturn,aredrivenbyasubclassofUICollectionViewLayout.
TheflowlayoutthatPhotoramaiscurrentlyusingisUICollectionViewFlowLayout,whichistheonlyconcreteUICollectionViewLayoutsubclassprovidedbytheUIKitframework.
SomeofthepropertiesyoucancustomizeonUICollectionViewFlowLayoutare:
scrollDirection–Doyouwanttoscrollverticallyorhorizontally?
minimumLineSpacing–Whatistheminimumspacingbetweenlines?
minimumInteritemSpacing–Whatistheminimumspacingbetweenitemsinarow(orcolumn,ifscrollinghorizontally)?
itemSize–Whatisthesizeofeachitem?
sectionInset–Whatarethemarginsusedtolayoutcontentforeachsection?
Figure20.6showshowthesepropertiesaffectthepresentationofcellsusingUICollectionViewFlowLayout.
WOW! eBook www.wowebook.org
Figure20.6UICollectionViewFlowLayoutproperties
OpenMain.storyboardandselectthecollectionview.OpenthesizeinspectorandconfiguretheCellSize,MinimumSpacing,andSectionInsetsasshowninFigure20.7.
WOW! eBook www.wowebook.org
Figure20.7Collectionviewsizeinspector
Buildandruntheapplicationtoseehowthelayouthaschanged.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
CreatingaCustomUICollectionViewCellNextyouaregoingtocreateacustomUICollectionViewCellsubclasstodisplaythephotos.Whiletheimagedataisdownloading,thecollectionviewcellwilldisplayaspinningactivityindicatorusingtheUIActivityIndicatorViewclass.
CreateanewSwiftfilenamedPhotoCollectionViewCell.InPhotoCollectionViewCell.swift,definePhotoCollectionViewCellasasubclassofUICollectionViewCell.Thenaddoutletstoreferencetheimageviewandtheactivityindicatorview.importFoundationimportUIKit
classPhotoCollectionViewCell:UICollectionViewCell{
@IBOutletvarimageView:UIImageView!@IBOutletvarspinner:UIActivityIndicatorView!
}
Theactivityindicatorviewshouldonlyspinwhenthecellisnotdisplayinganimage.InsteadofalwaysupdatingthespinnerwhentheimageViewisupdated,orviceversa,youwillwriteahelpermethodtotakecareofitforyou.
CreatethishelpermethodinPhotoCollectionViewCell.swift.funcupdateWithImage(image:UIImage?){ifletimageToDisplay=image{spinner.stopAnimating()imageView.image=imageToDisplay}else{spinner.startAnimating()imageView.image=nil}}
Itwouldbenicetoreseteachcelltothespinningstatebothwhenthecellisfirstcreatedandwhenthecellisgettingreused.ThemethodawakeFromNib()iscalledaftertheinterfacefileisloadedinandtheoutletconnectionsaremade.Thisisagoodopportunitytodoanyuserinterfacecustomization.ThemethodprepareForReuse()iscalledwhenacellisabouttobereused.
ImplementthesetwomethodsinPhotoCollectionViewCell.swifttoresetthecellbacktothespinningstate.overridefuncawakeFromNib(){super.awakeFromNib()
updateWithImage(nil)}
overridefuncprepareForReuse(){super.prepareForReuse()
updateWithImage(nil)}
Youwilluseaprototypecelltosetuptheinterfaceforthecollectionviewcellinthestoryboard,justasyoudidinChapter11forItemCell.Ifyourecall,eachprototypecell
WOW! eBook www.wowebook.org
correspondstoavisuallyuniquecellwithauniquereuseidentifier.Mostofthetime,theprototypecellswillbeassociatedwithdifferentUICollectionViewCellsubclassestoprovidebehaviorspecifictothatkindofcell.
Inthecollectionview’sattributesinspector,youcanadjustthenumberofItemsthatthecollectionviewdisplays,andeachitemcorrespondstoaprototypecellinthecanvas.ForPhotorama,youonlyneedonekindofcell:thePhotoCollectionViewCellthatdisplaysaphoto.
OpenMain.storyboardandselectthecollectionviewcell.Intheidentityinspector,changetheClasstoPhotoCollectionViewCell(Figure20.8).
Figure20.8Changingthecellclass
DraganimageviewontotheUICollectionViewCell.Addconstraintstopintheimageviewtotheedgesofthecell.OpentheattributesinspectorfortheimageviewandsettheModetoAspectFill.Thiswillcutoffpartsofthephotos,butitwillallowthephotostocompletelyfillinthecollectionviewcell.
Next,draganactivityindicatorviewontopoftheimageview.Addconstraintstocentertheactivityindicatorviewbothhorizontallyandverticallywiththeimageview.OpenitsattributesinspectorandselectHidesWhenStopped(Figure20.9).
Figure20.9Configuringtheactivityindicator
Selectthecollectionviewcellagain.Thiscanbeabittrickytodoonthecanvassincethenewlyaddedsubviewscompletelycoverthecellitself.AhelpfulInterfaceBuildertipistoholdControlandShifttogetherandthenclickontopoftheviewyouwanttoselect.Youwillbepresentedwithalistofalloftheviewsandcontrollersunderthepointyouclicked(Figure20.10).
WOW! eBook www.wowebook.org
Figure20.10Selectingthecellonthecanvas
Withthecellselected,opentheconnectionsinspectorandconnecttheimageViewandspinnerpropertiestotheimageviewandactivityindicatorviewonthecanvas(Figure20.11).
WOW! eBook www.wowebook.org
Figure20.11ConnectingPhotoCollectionViewCelloutlets
OpenPhotoDataSource.swiftandupdatethedatasourcemethodtousethePhotoCollectionViewCell.funccollectionView(collectionView:UICollectionView,cellForItemAtIndexPathindexPath:NSIndexPath)->UICollectionViewCell{
letidentifier="UICollectionViewCell"letcell=collectionView.dequeueReusableCellWithReuseIdentifier(identifier,forIndexPath:indexPath)as!PhotoCollectionViewCell
letphoto=photos[indexPath.row]cell.updateWithImage(photo.image)
returncell}
Buildandruntheapplication.Whentherecentphotosrequestcompletes,youwillseetheactivityindicatorviewsallspinning(Figure20.12).
WOW! eBook www.wowebook.org
Figure20.12Customcollectionviewsubclass
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
DownloadingtheImageDataNowallthatisleftisdownloadingtheimagedataforthephotosthatcomebackintherequest.Thistaskisnotverydifficult,butitrequiressomethought.Imagesarelargefiles,anddownloadingthemcouldeatupyourusers’cellulardataallowance.AsaconsiderateiOSdeveloper,youwanttomakeyourapp’sdatausageisonlywhatitneedstobe.
Consideryouroptions.YoucoulddownloadtheimagedatainviewDidLoad()whenthefetchRecentPhotos(_:)methodcallsitscompletionclosure.Atthatpoint,youalreadyassigntheincomingphotostothephotosproperty,soyoucoulditerateoverallofthosephotosanddownloadtheirimagedatarightatthatpoint.
Althoughthiswouldwork,itwouldbeverycostly.Therecouldbealargenumberofphotoscomingbackintheinitialrequest,andtheusermayneverevenscrolldownintheapplicationfarenoughtoseesomeofthem.Ontopofthat,ifyouinitializetoomanyrequestssimultaneously,someoftherequestsmaytimeoutwhilewaitingforotherrequeststofinish.Sothisisprobablynotthebestsolution.
Instead,itmakessensetodownloadtheimagedataforonlythecellsthattheuserisattemptingtoview.UICollectionViewhasamechanismtosupportthisthroughitsUICollectionViewDelegatemethodcollectionView(_:willDisplayCell:forItemAtIndexPath:).Thisdelegatemethodwillbecalledeverytimeacellisgettingdisplayedonscreenandisagreatopportunitytodownloadtheimagedata.
RecallthatthedataforthecollectionviewisdrivenbyaninstanceofPhotoDataSource,areusableclasswiththesingleresponsibilityofdisplayingphotosinacollectionview.Collectionviewsalsohaveadelegate,whichisresponsibleforhandlinguserinteractionwiththecollectionview.Thisincludestaskssuchasmanagingcellselectionandtrackingcellscomingintoandoutofview.Thisresponsibilityismoretightlycoupledwiththeviewcontrolleritself,sowhereasthedatasourceisaninstanceofPhotoDataSource,thecollectionview’sdelegatewillbethePhotosViewController.
InPhotosViewController.swift,havetheclassconformtotheUICollectionViewDelegateprotocol.classPhotosViewController:UIViewController,UICollectionViewDelegate{
(SincetheUICollectionViewDelegateprotocolonlydefinesoptionalmethods,Xcodedoesnotreportanyerrorswhenyouaddthisdeclaration.)
UpdateviewDidLoad()tosetthePhotosViewControllerasthedelegateofthecollectionview.overridefuncviewDidLoad(){super.viewDidLoad()
collectionView.dataSource=photoDataSourcecollectionView.delegate=self
...
WOW! eBook www.wowebook.org
Finally,implementthedelegatemethodinPhotosViewController.swift.funccollectionView(collectionView:UICollectionView,willDisplayCellcell:UICollectionViewCell,forItemAtIndexPathindexPath:NSIndexPath){
letphoto=photoDataSource.photos[indexPath.row]
//Downloadtheimagedata,whichcouldtakesometimestore.fetchImageForPhoto(photo){(result)->Voidin
NSOperationQueue.mainQueue().addOperationWithBlock(){
//Theindexpathforthephotomighthavechangedbetweenthe//timetherequeststartedandfinished,sofindthemost//recentindexpath
//(Note:Youwillhaveanerroronthenextline;youwillfixitsoon)letphotoIndex=self.photoDataSource.photos.indexOf(photo)!letphotoIndexPath=NSIndexPath(forRow:photoIndex,inSection:0)
//Whentherequestfinishes,onlyupdatethecellifit'sstillvisibleifletcell=self.collectionView.cellForItemAtIndexPath(photoIndexPath)as?PhotoCollectionViewCell{cell.updateWithImage(photo.image)}}}}
IftheimagealreadyexistsonthePhoto,theimagedatashouldnotbedownloadedfromthewebserviceagain.
OpenPhotoStore.swiftandupdatefetchImageForPhoto(_:completion:)toreturnthephoto’simageifithasalreadybeendownloaded.funcfetchImageForPhoto(photo:Photo,completion:(ImageResult)->Void){
ifletimage=photo.image{completion(.Success(image))return}
letphotoURL=photo.remoteURL...
Let’sfixtheerroryousawwhenfindingtheindexofphotointhephotosarray.TheindexOf(_:)methodworksbycomparingtheitemthatyouarelookingfortoeachoftheitemsinthecollection.Itdoesthisusingthe==operator.TypesthatconformtotheEquatableprotocolmustimplementthisoperator,andPhotodoesnotyetconformtoEquatable.
InPhoto.swift,declarethatPhotoconformstotheEquatableprotocolandimplementtherequiredoverloadingofthe==operator.classPhoto:Equatable{...}
func==(lhs:Photo,rhs:Photo)->Bool{//TwoPhotosarethesameiftheyhavethesamephotoIDreturnlhs.photoID==rhs.photoID}
Customoperatorshavetobedeclaredoutsideofthetypedeclaration.Thecompilerrecognizesthatthe==operatoroccursbetweentwoinstancesofPhoto(basedonthe
WOW! eBook www.wowebook.org
argumentstothefunction)andthereforevalidatesthattherequirementfortheEquatableprotocolhasbeensatisfied.
InSwift,itiscommontogrouprelatedchunksoffunctionalityintoanextension.Let’stakeashortdetourtolearnaboutextensionsandthenusethisknowledgetoseehowconformingtotheEquatableprotocolisoftendoneinpractice.
Extensions
Extensionsserveacoupleofpurposes:theyallowyoutogroupchunksoffunctionalityintoalogicalunit,andtheyalsoallowyoutoaddfunctionalitytoyourowntypesaswellastypesprovidedbythesystemorotherframeworks.Beingabletoaddfunctionalitytoatypewhosesourcecodeyoudonothaveaccesstoisaverypowerfulandflexibletool.Extensionscanbeaddedtoclasses,structs,andenums.Let’stakealookatanexample.
SayyouwantedtoaddfunctionalitytotheInttypetoprovideadoubledvalueofthatInt.Forexample:letfourteen=7.doubled//Thevalueoffourteenis'14'
YoucanaddthisfunctionalitybyextendingtheInttype:extensionInt{vardoubled:Int{returnself*2}}
Withextensions,youcanaddcomputedproperties,addmethods,andconformtoprotocols.However,youcannotaddstoredpropertiestoanextension.
Extensionsprovideagreatmechanismforgroupingrelatedpiecesoffunctionality.Theycanmakethecodemorereadableandhelpwithlong-termmaintainabilityofyourcodebase.Onecommonchunkoffunctionalitythatisoftengroupedintoanextensionisconformancetoaprotocolalongwiththemethodsofthatprotocol.
UpdatePhoto.swifttouseanextensiontoconformtotheEquatableprotocol.classPhoto:Equatable{...}
extensionPhoto:Equatable{}
func==(lhs:Photo,rhs:Photo)->Bool{//TwoPhotosarethesameiftheyhavethesamephotoIDreturnlhs.photoID==rhs.photoID}
TheextensiondeclaresthatPhotoconformstotheEquatableprotocol.Whileitmightmakesenseforthe==operatortobeincludedinthisPhotoextension,aswementionedoperatorsmustbedeclaredoutsidethescopeofaspecifictype.Therefore,topreservethegroupingasmuchaspossible,theoperatorisdefinedimmediatelyafterthe(empty)extension.
Thisisasimplifiedexample,butextensionsareverypowerfulforbothextendingexistingtypesandgroupingrelatedfunctionality.Infact,theSwiftstandardlibrarymakes
WOW! eBook www.wowebook.org
extensiveuseofextensions–andyouwill,too.
Buildandruntheapplication.Theimagedatawilldownloadforthecellsvisibleonscreen(Figure20.13).Scrolldowntomakemorecellsvisible.Atfirst,youwillseetheactivityindicatorviewsspinning,butsoontheimagedataforthosecellswillload.Ifyouscrollbackup,therewillbenodelayinloadingimagedata.Becausetheimageswerealreadydownloadedforthosecells,youwillnotseeanyactivityindicatorviewsandtheimageswillbeimmediatelyvisible.
Figure20.13Imagedownloadsinprogress
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
NavigatingtoaPhotoInthissection,youaregoingtoaddfunctionalitytoallowausertonavigatetoanddisplayasinglephoto.
CreateanewSwiftfilenamedPhotoInfoViewController.Inthisnewfile,declarethePhotoInfoViewControllerclassandaddanimageViewoutlet.importFoundationimportUIKit
classPhotoInfoViewController:UIViewController{
@IBOutletvarimageView:UIImageView!}
Nowsetuptheinterfaceforthisviewcontroller.OpenMain.storyboardanddraganewViewControllerontothecanvasfromtheobjectlibrary.Withthisviewcontrollerselected,openitsidentityinspectorandchangetheClasstoPhotoInfoViewController.
Whentheusertapsononeofthecollectionviewcells,theapplicationwillnavigatetothisnewviewcontroller.Control-dragfromtheUICollectionViewCelltothePhotoInfoViewControllerandselecttheShowsegue.Withthenewsegueselected,openitsattributesinspectorandgivethesegueanIdentifierofShowPhoto(Figure20.14).
Figure20.14Navigationtoaphoto
AddanimageviewtothePhotoInfoViewController’sview.SetupitsAutoLayoutconstraintstopintheimageviewtoallfoursides.OpentheattributesinspectorfortheimageviewandsetitsModetoAspectFit.
WOW! eBook www.wowebook.org
Finally,connecttheimageviewtotheimageViewoutlet.
Whentheusertapsacell,theShowPhotoseguewillbetriggered.Atthispoint,thePhotosViewControllerwillneedtopassboththePhotoandthePhotoStoretothePhotoInfoViewController.
OpenPhotoInfoViewController.swiftandaddtwoproperties.classPhotoInfoViewController:UIViewController{
@IBOutletvarimageView:UIImageView!
varphoto:Photo!{didSet{navigationItem.title=photo.title}}varstore:PhotoStore!}
Whenphotoissetonthisviewcontroller,thenavigationitemwillbeupdatedtodisplaythenameofthephoto.
NowoverrideviewDidLoad()tosettheimageontheimageViewwhentheviewisloaded.overridefuncviewDidLoad(){super.viewDidLoad()
store.fetchImageForPhoto(photo){(result)->Voidinswitchresult{caselet.Success(image):NSOperationQueue.mainQueue().addOperationWithBlock(){self.imageView.image=image}caselet.Failure(error):print("Errorfetchingimageforphoto:\(error)")}}}
InPhotosViewController.swift,implementprepareForSegue(_:sender:)topassalongthephotoandthestore.overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){ifsegue.identifier=="ShowPhoto"{ifletselectedIndexPath=collectionView.indexPathsForSelectedItems()?.first{
letphoto=photoDataSource.photos[selectedIndexPath.row]
letdestinationVC=segue.destinationViewControlleras!PhotoInfoViewControllerdestinationVC.photo=photodestinationVC.store=store}}}
Buildandruntheapplication.Afterthewebservicerequesthasfinished,tapononeofthephotostoseeitinthenewviewcontroller(Figure20.15).
WOW! eBook www.wowebook.org
Figure20.15Displayingaphoto
Collectionviewsareapowerfulwaytodisplaydatausingaflexiblelayout.Youhavejustbarelytappedintothepowerofcollectionviewsinthischapter.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:UpdatedItemSizesHavethecollectionviewalwaysdisplayfouritemsperrow,takingupasmuchasthescreenwidthaspossible.Thisshouldworkinbothportraitandlandscapeorientations.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
GoldChallenge:CreatingaCustomLayoutCreateacustomlayoutthatdisplaysthephotosinaflipbook.Youwillneedtousethetransformpropertyonthecelllayertogetanappropriate3-Deffect.YoucansubclassUICollectionViewLayoutforthischallenge,butalsoconsidersubclassingUICollectionViewFlowLayout.CheckouttheclassreferenceforUICollectionViewLayoutformoreinformation.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
21CoreData
WhendecidingbetweenapproachestosavingandloadingforiOSapplications,thefirstquestionis“Localorremote?”Ifyouwanttosavedatatoaremoteserver,youwilllikelyuseawebservice.Ifyouwanttostoredatalocally,youhavetoaskanotherquestion:“ArchivingorCoreData?”
YourHomepwnerapplicationuseskeyedarchivingtosaveitemdatatothefilesystem.Thebiggestdrawbacktoarchivingisitsall-or-nothingnature:toaccessanythinginthearchive,youmustunarchivetheentirefile,andtosaveanychanges,youmustrewritetheentirefile.CoreData,ontheotherhand,canfetchasubsetofthestoredobjects.Andifyouchangeanyofthoseobjects,youcanupdatejustthatpartofthefile.Thisincrementalfetching,updating,deleting,andinsertingcanradicallyimprovetheperformanceofyourapplicationwhenyouhavealotofmodelobjectsbeingshuttledbetweenthefilesystemandRAM.
WOW! eBook www.wowebook.org
ObjectGraphsCoreDataisaframeworkthatletsyouexpresswhatyourmodelobjectsareandhowtheyarerelatedtooneanother.Itthentakescontrolofthelifetimesoftheseobjects,makingsuretherelationshipsarekeptuptodate.Whenyousaveandloadtheobjects,CoreDatamakessureeverythingisconsistent.Thiscollectionofmodelobjectsisoftencalledanobjectgraph,astheobjectscanbethoughtofasnodesandtherelationshipsasverticesinamathematicalgraph.
OftenyouwillhaveCoreDatasaveyourobjectgraphtoaSQLitedatabase.DeveloperswhoareusedtootherSQLtechnologiesmightexpecttotreatCoreDatalikeanobject-relationalmappingsystem,butthismindsetwillleadtoconfusion.UnlikeanORM,CoreDatatakescompletecontrolofthestorage,whichjusthappenstobearelationaldatabase.Youdonothavetodescribethingslikethedatabaseschemaandforeignkeys–CoreDatadoesthat.YoujusttellCoreDatawhatneedsstoringandletitworkouthowtostoreit.
CoreDatagivesyoutheabilitytofetchandstoredatainarelationaldatabasewithouthavingtoknowthedetailsoftheunderlyingstoragemechanism.ThischapterwillgiveyouanunderstandingofCoreDataasyouaddpersistencetothePhotoramaapplication.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
EntitiesArelationaldatabasehassomethingcalledatable.Atablerepresentsatype:youcanhaveatableofpeople,atableofacreditcardpurchases,oratableofrealestatelistings.Eachtablehasanumberofcolumnstoholdpiecesofinformationaboutthetype.Atablethatrepresentspeoplemighthavecolumnsforlastname,dateofbirth,andheight.Everyrowinthetablerepresentsanexampleofthetype–e.g.,asingleperson.
ThisorganizationtranslateswelltoSwift.EverytableislikeaSwifttype.Everycolumnisoneofthetype’sproperties.Everyrowisaninstanceofthattype.Thus,CoreData’sjobistomovedatatoandfromthesetworepresentations(Figure21.1).
Figure21.1RoleofCoreData
CoreDatausesdifferentterminologytodescribetheseideas:atable/typeiscalledanentity,andthecolumns/propertiesarecalledattributes.ACoreDatamodelfileisthedescriptionofeveryentityalongwithitsattributesinyourapplication.InPhotorama,youaregoingtodescribeaPhotoentityinamodelfileandgiveitattributesliketitle,remoteURL,anddateTaken.
Modelingentities
OpenPhotorama.xcodeproj.Createanewfile,butdonotmakeitaSwiftfileliketheonesyouhavecreatedbefore.Instead,selectCoreDataintheiOSsectionandcreateanewDataModel(Figure21.2).NameitPhotorama.
WOW! eBook www.wowebook.org
Figure21.2Creatingthemodelfile
ThiswillcreatethePhotorama.xcdatamodeldfileandaddittoyourproject.SelectthisfilefromtheprojectnavigatorandtheeditorareawillrevealtheuserinterfaceformanipulatingaCoreDatamodelfile.
FindtheAddEntitybuttonatthebottomleftofthewindowandclickit.Anewentitywillappearinthelistofentitiesinthelefthandtable.Double-clickthisentityandchangeitsnametoPhoto(Figure21.3).
WOW! eBook www.wowebook.org
Figure21.3CreatingthePhotoentity
NowyourPhotoentityneedsattributes.RememberthatthesewillbethepropertiesofthePhotoclass.Thenecessaryattributesarelistedbelow.Foreachattribute,clickthe+buttonintheAttributessectionandedittheAttributeandTypevalues.
photoIDisaString.
photoKeyisaString.
titleisaString.
dateTakenisaDate.
remoteURLisaTransformable.(ItisanNSURL,butthatisnotoneofthepossibilities.Wewilldiscuss“transformable”next.)
Bydefault,theCoreDatamodeleditormakesallattributesoptional.ItdoesnotmakesenseforthePhotoattributestobeoptional,solet’schangethemalltobenon-optional.
Selectalloftheattributesbyclickingonthefirstattributeinthelist,holdingtheShiftkey,andthenclickingthelastattributeinthelist.Opentheutilitiesview,openthedatamodel
WOW! eBook www.wowebook.org
inspector(the icon),andunchecktheOptionalcheckbox(Figure21.4).
Figure21.4Makingtheattributesnon-optional
Sincethesepropertiesarenolongeroptional,youwillhavetoprovidevaluesforthemwhentheobjectiscreated.Youwilldothatlaterinthischapter.
Transformableattributes
CoreDataisonlyabletostorecertaindatatypesinitsstore.NSURLisnotoneofthesetypes,soyoudeclaredtheremoteURLattributeastransformable.Withatransformableattribute,CoreDatawillconverttheobjectintoatypethatitcanstorewhensavingandthenconvertitbacktotheoriginalobjectwhenloadingfromthefilesystem.
AtransformableattributerequiresanNSValueTransformersubclasstohandletheconversionsbetweentypes.Ifyoudonotspecifyacustomsubclass,thesystemwillusethetransformernamedNSKeyedUnarchiveFromDataTransformer.ThistransformerusesarchivingtoconverttheobjecttoandfromNSData.SinceNSURLconformstoNSCoding,thedefaultNSKeyedUnarchiveFromDataTransformerwillbesufficient.IfthetypeyouwantedtotransformdidnotconformtoNSCoding,youwouldneedtowriteyourowncustomNSValueTransformersubclass.
Atthispoint,yourmodelfileissufficienttosaveandloadphotos.Inthenextsection,youwillcreateacustomsubclassforthePhotoentity.
NSManagedObjectandsubclasses
WhenanobjectisfetchedwithCoreData,itsclass,bydefault,isNSManagedObject.NSManagedObjectisasubclassofNSObjectthatknowshowtocooperatewiththerestofCoreData.AnNSManagedObjectworksabitlikeadictionary:itholdsakey-valuepairforeveryproperty(attributeorrelationship)intheentity.
AnNSManagedObjectislittlemorethanadatacontainer.Ifyouneedyourmodelobjectstodosomethinginadditiontoholdingdata,youmustsubclassNSManagedObject.Then,inyourmodelfile,youspecifythatthisentityisrepresentedbyinstancesofyoursubclass,notthestandardNSManagedObject.
WhenanewPhotoiscreated,anewUUIDneedstobegeneratedforthephotoKey.ThisfunctionalitycannotberepresentedwithNSManagedObjectitself,soyouwillcreateacustomsubclass.
WOW! eBook www.wowebook.org
XcodecangenerateNSManagedObjectsubclassesforyoubasedonwhatyouhavedefinedinyourCoreDatamodelfile.
Intheprojectnavigator,selectthePhoto.swiftfileanddeleteit.Makesureyoumoveittothetrashwhenpromptedtomakesureitdoesnotstillexistintheprojectdirectory.
Createanewfile.FromtheiOSsectionofthetemplateselectionscreen,chooseCoreData,andthenchooseNSManagedObjectsubclassandclickNext.Onthenextscreen,checktheboxforPhotoramaandclickNext.ChecktheboxforthePhotoentityandclickNext.Finally,makesurethelanguageissettoSwiftandclickCreate.
Thetemplatewillcreatetwofilesforyou:Photo.swiftandPhoto+CoreDataProperties.swift.ThetemplateplacesalloftheattributesthatyoudefinedinthemodelfileintoPhoto+CoreDataProperties.swift.Ifyoueverchangeyourentityinthemodelfile,youcansimplydeletePhoto+CoreDataProperties.swiftandrepeatthestepstogeneratetheNSManagedObjectsubclass.XcodewillrecognizethatyoualreadyhavePhoto.swiftandwillonlyrecreatePhoto+CoreDataProperties.swift.
OpenPhoto+CoreDataProperties.swiftandtakealookatwhatthetemplatecreatedforyou.
[email protected],whichisspecifictoCoreData,letsthecompilerknowthatthestorageandimplementationofthesepropertieswillbeprovidedatruntime.BecauseCoreDatawillcreatetheNSManagedObjectinstances,youcannolongeruseacustominitializer,sothepropertiesneedtobevariablesinsteadofconstants.
WhenyoumadethePhotoentity’sattributesnon-optionalinthemodelfile,youmadeacontractwithCoreDatathatthoseattributeswouldalwayshaveavaluewhenaninstanceofPhotowasbeingpersisted.Unfortunately,thatcontractwithCoreDataisnottranslatedbyXcodeintooptionalsinSwift,sowhenXcodegeneratedthePhotosubclass,itdidnotmakethepropertiesnon-optional.
ToadheretoyourcontractwithCoreData,maketheattributesinPhoto+CoreDataProperties.swiftnon-optional.(Theorderofyourattributesmightbedifferent;thatisOK.)@NSManagedvarphotoID:String?@NSManagedvarphotoKey:String?@NSManagedvartitle:String?@NSManagedvardateTaken:NSDate?@NSManagedvarremoteURL:NSObject?
InPhoto+CoreDataProperties.swift,changetheremoteURLtypetobeNSURL.RecallthatthispropertyisatransformablepropertyandwillautomaticallybeconvertedtoandfromNSDatawhenbeingstoredinandretrievedfromCoreData.@NSManagedvarphotoID:String@NSManagedvarphotoKey:String@NSManagedvartitle:String@NSManagedvardateTaken:NSDate@NSManagedvarremoteURL:NSObject@NSManagedvarremoteURL:NSURL
WOW! eBook www.wowebook.org
AnycustompropertiesorcodethatyouwanttoaddshouldbeaddedtoPhoto.swift.Let’saddbackthebehavioryoupreviouslyhad.
InPhoto.swift,addtheoptionalUIImageproperty.YouwillneedtoimportUIKitaswell.importFoundationimportUIKitimportCoreData
classPhoto:NSManagedObject{
//Insertcodeheretoaddfunctionalitytoyourmanagedobjectsubclass
varimage:UIImage?
}
Ofcourse,whenyoulaunchanapplicationthefirsttime,therearenosavedphotos.Whenthewebservicerequestreturns,thephotoswillbeaddedtothedatabase.Whenobjectsareaddedtothedatabase,theyaresentthemessageawakeFromInsert().HereiswhereyouwillsettheinitialvaluesforthepropertiesofaPhoto.
ImplementawakeFromInsert()inPhoto.swift.overridefuncawakeFromInsert(){super.awakeFromInsert()
//Givethepropertiestheirinitialvaluestitle=""photoID=""remoteURL=NSURL()photoKey=NSUUID().UUIDStringdateTaken=NSDate()}
YouhavecreatedyourmodelgraphanddefinedyourPhotoentity.ThenextstepistosetuptheCoreDatastack,whichwillmanagetheinteractionsbetweentheapplicationandCoreData.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BuildingtheCoreDataStackTheCoreDatastackconsistsoftheclassesthatinterfacewithyourentitiesaswellasthemodelfilethatdescribestheentitiestosaveandloadinstancestoastore(suchasthefilesystem).YouwilllearnabouteachcomponentinthestackandwhatroleitplaysinCoreData.
CreateanewSwiftfileandnameitCoreDataStack.OpenthisfileanddeclaretheCoreDataStackclassalongwitharequiredinitializerthatacceptsthenameofthemodelfile.DonotforgettoimporttheCoreDataframework.importFoundationimportCoreData
classCoreDataStack{
letmanagedObjectModelName:String
requiredinit(modelName:String){managedObjectModelName=modelName}
}
NSManagedObjectModel
Youworkedwiththemodelfileearlierinthechapter.Themodelfileiswhereyoudefinetheentitiesforyourapplicationalongwiththeirproperties.ThemodelfileisaninstanceofNSManagedObjectModel.
InCoreDataStack.swift,addapropertytoreadinthemodelfilefromthemainbundle.classCoreDataStack{
letmanagedObjectModelName:String
privatelazyvarmanagedObjectModel:NSManagedObjectModel={letmodelURL=NSBundle.mainBundle().URLForResource(self.managedObjectModelName,withExtension:"momd")!returnNSManagedObjectModel(contentsOfURL:modelURL)!}()
requiredinit(modelName:String){managedObjectModelName=modelName}}
First,themodelfileislocatedwithinthemainbundle.ThenaninstanceofNSManagedObjectModeliscreatedusingthisURL.
Noticethelazykeywordinthepropertydeclaration.Thelazykeywordallowsapropertytobelazilyloaded.Inotherwords,thefirsttimethatthemanagedObjectModelpropertyisaccessed,theassociatedclosurewillbeexecuted.Lazyloadingallowspropertyinitializationtobedeferreduntilthepropertyisactuallyneeded.
WOW! eBook www.wowebook.org
NSPersistentStoreCoordinator
CoreDatacanpersistdatatodiskusingafewdifferentformats:
SQLite DataissavedtodiskusingaSQLitedatabase.Thisisthemostcommonlyusedstoretype.
Atomic Dataissavedtodiskusingabinaryformat.
XML DataissavedtodiskusinganXMLformat.ThisstoretypeisnotavailableoniOS.
In-Memory Dataisnotsavedtodisk,butinsteadisstoredinmemory.
ThemappingbetweenanobjectgraphandthepersistentstoreisaccomplishedusinganinstanceofNSPersistentStoreCoordinator.Thepersistentstorecoordinatorneedstoknowtwothings:“Whataremyentities?”and“WhereamIsavingtoandloadingdatafrom?”Toanswerthesequestions,youwillinstantiateanNSPersistentStoreCoordinatorwiththeNSManagedObjectModel.Thenyouwilladdapersistentstore,representingoneofthepersistenceformatsabove,tothecoordinator.
InCoreDataStack.swift,addtwopropertiestosetupthepersistentstorecoordinator.privatevarapplicationDocumentsDirectory:NSURL={leturls=NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory,inDomains:.UserDomainMask)returnurls.first!}()
privatelazyvarpersistentStoreCoordinator:NSPersistentStoreCoordinator={
varcoordinator=NSPersistentStoreCoordinator(managedObjectModel:self.managedObjectModel)
letpathComponent="\(self.managedObjectModelName).sqlite"leturl=self.applicationDocumentsDirectory.URLByAppendingPathComponent(pathComponent)
letstore=try!coordinator.addPersistentStoreWithType(NSSQLiteStoreType,configuration:nil,URL:url,options:nil)
returncoordinator}()
Afterthecoordinatoriscreated,youattempttoaddaspecificstoretothecoordinator.Ataminimum,thisstoreneedstoknowitstypeandwhereitshouldpersistthedata.
NSManagedObjectContext
WOW! eBook www.wowebook.org
TheportalthroughwhichyouinteractwithyourentitiesistheNSManagedObjectContext.Themanagedobjectcontextisassociatedwithaspecificpersistentstorecoordinator.Youcanthinkofthemanagedobjectcontextasanintelligentscratchpad.Whenyouaskthecontexttofetchsomeentities,thecontextwillworkwithitspersistentstorecoordinatortobringtemporarycopiesoftheentitiesandobjectgraphintomemory.Unlessyouaskthecontexttosaveitschanges,thepersisteddataremainsthesame.
StillinCoreDataStack.swift,addanewpropertytoholdontoaninstanceofNSManagedObjectContext.lazyvarmainQueueContext:NSManagedObjectContext={
letmoc=NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)moc.persistentStoreCoordinator=self.persistentStoreCoordinatormoc.name="MainQueueContext(UIContext)"
returnmoc}()
YourCoreDatastackiscompletefornow.NowyoujustneedtomakethePhotoStoreresponsibleforowningtheCoreDatastack.
OpenPhotoStore.swiftandaddapropertyforaCoreDataStackinstance.classPhotoStore{
letcoreDataStack=CoreDataStack(modelName:"Photorama")
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UpdatingItemsWiththeCoreDatastackcomplete,youcannowinteractwithit.Primarily,youwilldothisthroughitsmainQueueContext.Thisishowyouwillbothcreatenewentitiesandsavechanges.
Insertingintothecontext
Whenanentityiscreated,itshouldbeinsertedintoamanagedobjectcontext.
OpenFlickrAPI.swiftandimportCoreData.importFoundationimportCoreData
ThenupdatethephotoFromJSONObject(_:)methodtotakeinanadditionalargumentoftypeNSManagedObjectContext.ItwillthenusethiscontexttoinsertnewPhotoinstances.privatestaticfuncphotoFromJSONObject(json:[String:AnyObject],inContextcontext:NSManagedObjectContext)->Photo?{
guardletphotoID=json["id"]as?String,title=json["title"]as?String,dateString=json["datetaken"]as?String,photoURLString=json["url_h"]as?String,url=NSURL(string:photoURLString),dateTaken=dateFormatter.dateFromString(dateString)else{returnnil}
returnPhoto(title:title,photoID:photoID,URL:url)
varphoto:Photo!context.performBlockAndWait(){photo=NSEntityDescription.insertNewObjectForEntityForName("Photo",inManagedObjectContext:context)as!Photophoto.title=titlephoto.photoID=photoIDphoto.remoteURL=urlphoto.dateTaken=dateTaken}
returnphoto}
EachNSManagedObjectContextisassociatedwithaspecificconcurrencyqueue,andthemainQueueContextisassociatedwiththemain,oruserinterface,queue.Youhavetointeractwithacontextonthequeuethatitisassociatedwith.NSManagedObjectContexthastwomethodsthatensurethishappens:performBlock(_:)andperformBlockAndWait(_:).ThedifferencebetweenthemisthatperformBlock(_:)isasynchronousandperformBlockAndWait(_:)issynchronous.SinceyouarereturningtheresultoftheinsertoperationfromthephotoFromJSONObject(_:inContext:)method,youusethesynchronousmethod.
ThephotoFromJSONObject(_:)methodiscalledfromthemethodphotosFromJSONData(_:).Updatethismethodtotakeinacontextandpassitto
WOW! eBook www.wowebook.org
thephotoFromJSONObject(_:)method.staticfuncphotosFromJSONData(data:NSData,inContextcontext:NSManagedObjectContext)->PhotosResult{
do{letjsonObject:AnyObject=tryNSJSONSerialization.JSONObjectWithData(data,options:[])
guardletjsonDictionary=jsonObjectas?[NSObject:AnyObject],photos=jsonDictionary["photos"]as?[String:AnyObject],photosArray=photos["photo"]as?[[String:AnyObject]]else{
//TheJSONstructuredoesn'tmatchourexpectationsreturn.Failure(FlickrError.InvalidJSONData)}
varfinalPhotos=[Photo]()forphotoJSONinphotosArray{ifletphoto=photoFromJSONObject(photoJSON,inContext:context){finalPhotos.append(photo)}}
...
Finally,youneedtopassthemainQueueContexttotheFlickrAPIstructoncethewebservicerequestsuccessfullycompletes.
OpenPhotoStore.swiftandupdateprocessRecentPhotosRequest(data:error:).funcprocessRecentPhotosRequest(datadata:NSData?,error:NSError?)->PhotosResult{
guardletjsonData=dataelse{return.Failure(error!)}
returnFlickrAPI.photosFromJSONData(jsonData,inContext:self.coreDataStack.mainQueueContext)}
Buildandruntheapplicationnowthatallerrorshavebeenaddressed.Althoughthebehaviorremainsunchanged,theapplicationisnowbackedbyCoreData.Inthenextsection,youwillimplementsavingforboththephotosandtheirassociatedimagedata.
Savingchanges
RecallthatNSManagedObjectchangesdonotpersistuntilyoutellthecontexttosavethesechanges.
OpenCoreDataStack.swiftandaddanewmethodthatsavesthechangestothecontext.funcsaveChanges()throws{varerror:ErrorType?mainQueueContext.performBlockAndWait(){
ifself.mainQueueContext.hasChanges{do{tryself.mainQueueContext.save()}catchletsaveError{error=saveError}}
WOW! eBook www.wowebook.org
}ifleterror=error{throwerror}}
ThesaveChanges()methodwillnothandleanysaveerrorsitself.Instead,itwillthrowtheerrorsothatitispassedalongtothecaller.
OpenPhotoStore.swiftandupdatefetchRecentPhotos(_:)tosavethechangestothecontextafterPhotoentitieshavebeeninsertedintothecontext.funcfetchRecentPhotos(completioncompletion:(PhotosResult)->Void){leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidin
letresult=self.processRecentPhotosRequest(data:data,error:error)varresult=self.processRecentPhotosRequest(data:data,error:error)
ifcaselet.Success(photos)=result{do{tryself.coreDataStack.saveChanges()}catchleterror{result=.Failure(error)}}
completion(result)}task.resume()}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
UpdatingtheDataSourceOneproblemwiththeappatthemomentisthatfetchRecentPhotos(completion:)onlyreturnsthenewlyinsertedphotos.Nowthattheapplicationsupportssaving,itshouldreturnallofthephotos–thepreviouslysavedphotosaswellasthenewlyinsertedones.YouneedtoaskCoreDataforallofthePhotoentities,andyouwillaccomplishthisusingafetchrequest.
Fetchrequestsandpredicates
TogetobjectsbackfromtheNSManagedObjectContext,youmustprepareandexecuteanNSFetchRequest.Afterafetchrequestisexecuted,youwillgetanarrayofalltheobjectsthatmatchtheparametersofthatrequest.
Afetchrequestneedsanentitydescriptionthatdefineswhichentityyouwanttogetobjectsfrom.TofetchPhotoinstances,youspecifythePhotoentity.Youcanalsosettherequest’ssortdescriptorstospecifytheorderoftheobjectsinthearray.AsortdescriptorhasakeythatmapstoanattributeoftheentityandaBoolthatindicateswhethertheordershouldbeascendingordescending.
ThesortDescriptorspropertyonNSFetchRequestisanarrayofNSSortDescriptorinstances.Whyanarray?Thearrayisusefulifyouthinktheremightbecollisionswhensorting.Forexample,sayyouaresortinganarrayofpeoplebytheirlastnames.Itisentirelypossiblethatmultiplepeoplehavethesamelastname,soyoucanspecifythatpeoplewiththesamelastnameshouldbesortedbytheirfirstnames.ThiswouldbeimplementedbyanarrayoftwoNSSortDescriptorinstances.Thefirstsortdescriptorwouldhaveakeythatmapstotheperson’slastname,andthesecondsortdescriptorwouldhaveakeythatmapstotheperson’sfirstname.
ApredicateisrepresentedbytheNSPredicateclassandcontainsaconditionthatcanbetrueorfalse.Ifyouwantedtofindallphotoswithagivenidentifier,youwouldcreateapredicateandaddittothefetchrequestlikethis:letpredicate=NSPredicate.predicateWithFormat("photoID==\(someIdentifier)")request.predicate=predicate
Theformatstringforapredicatecanbeverylongandcomplex.Apple’sPredicateProgrammingGuideisacompletediscussionofwhatispossible.
YouwanttosortthereturnedinstancesofPhotobydateTakenindescendingorder.Todothis,youwillinstantiateanNSFetchRequest,givingitthe“Photo”entityname.ThenyouwillgivethefetchrequestanarrayofNSSortDescriptorinstances.ForPhotorama,thisarraywillcontainasinglesortdescriptorthatsortsphotosbytheirdateTakenproperties.Finally,youwillaskthemanagedobjectcontexttoexecutethisfetchrequest.
ImporttheCoreDataframeworkatthetopofPhotoStore.swift.importUIKitimportCoreData
WOW! eBook www.wowebook.org
StillinPhotoStore.swift,implementamethodthatwillfetchthePhotoinstancesfromthemainqueuecontext.funcfetchMainQueuePhotos(predicatepredicate:NSPredicate?=nil,sortDescriptors:[NSSortDescriptor]?=nil)throws->[Photo]{
letfetchRequest=NSFetchRequest(entityName:"Photo")fetchRequest.sortDescriptors=sortDescriptorsfetchRequest.predicate=predicate
letmainQueueContext=self.coreDataStack.mainQueueContextvarmainQueuePhotos:[Photo]?varfetchRequestError:ErrorType?mainQueueContext.performBlockAndWait(){do{mainQueuePhotos=trymainQueueContext.executeFetchRequest(fetchRequest)as?[Photo]}catchleterror{fetchRequestError=error}}
guardletphotos=mainQueuePhotoselse{throwfetchRequestError!}
returnphotos}
Afterthewebservicerequestfinishes,youneedtoreturnthephotos.InPhotoStore.swift,updatefetchRecentPhotos(completion:)tofetchthemainqueuephotoswhenthewebservicefinishes.varresult=self.processRecentPhotosRequest(data:data,error:error)
ifcaselet.Success(photos)=result{letmainQueueContext=self.coreDataStack.mainQueueContextmainQueueContext.performBlockAndWait(){try!mainQueueContext.obtainPermanentIDsForObjects(photos)}letobjectIDs=photos.map{$0.objectID}letpredicate=NSPredicate(format:"selfIN%@",objectIDs)letsortByDateTaken=NSSortDescriptor(key:"dateTaken",ascending:true)
do{tryself.coreDataStack.saveChanges()
letmainQueuePhotos=tryself.fetchMainQueuePhotos(predicate:predicate,sortDescriptors:[sortByDateTaken])result=.Success(mainQueuePhotos)}catchleterror{result=.Failure(error)}}
completion(result)
Thiswillonlyreturnthenewlyinsertedphotos.YouwanttheinterfaceforPhotosViewControllertodisplayallofthephotos.
OpenPhotosViewController.swiftandupdateviewDidLoad()tofetchanddisplayallofthephotossavedtoCoreData.overridefuncviewDidLoad()super.viewDidLoad()
collectionView.dataSource=photoDataSourcecollectionView.delegate=self
WOW! eBook www.wowebook.org
store.fetchRecentPhotos(){(photosResult)->Voidin
NSOperationQueue.mainQueue().addOperationWithBlock(){switchphotosResult{caselet.Success(photos):print("Successfullyfound\(photos.count)recentphotos.")self.photoDataSource.photos=photoscaselet.Failure(error):self.photoDataSource.photos.removeAll()print("Errorfetchingrecentphotos:\(error)")}self.collectionView.reloadSections(NSIndexSet(index:0))}
letsortByDateTaken=NSSortDescriptor(key:"dateTaken",ascending:true)letallPhotos=try!self.store.fetchMainQueuePhotos(predicate:nil,sortDescriptors:[sortByDateTaken])
NSOperationQueue.mainQueue().addOperationWithBlock(){self.photoDataSource.photos=allPhotosself.collectionView.reloadSections(NSIndexSet(index:0))}}}
Previouslysavedphotoswillnowbereturnedwhenthewebservicerequestfinishes.Butthereisstilloneproblem:iftheapplicationisrunmultipletimesandthesamephotoisreturnedfromthewebservicerequest,itwillbeinsertedintothecontextmultipletimes.Thisisnotgood–youdonotwantduplicatephotos.Luckilythereisauniqueidentifierforeachphoto.Whentherecentphotoswebservicerequestfinishes,theidentifierforeachphotointheincomingJSONcanbecomparedtothephotosstoredinCoreData.Ifoneisfoundwiththesameidentifier,thatphotowillbereturned.Otherwise,anewphotowillbeinsertedintothecontext.
Todothis,youneedawaytotellthefetchrequestthatitshouldnotreturnallphotosbutinsteadonlythephotosthatmatchsomespecificcriteria.Inthiscase,thespecificcriteriais“onlyphotosthathavethisspecificidentifier,”ofwhichthereshouldeitherbezerooronephoto.InCoreData,thisisdonewithapredicate.
InFlickrAPI.swift,updatephotoFromJSONObject(_:inContext:)tocheckwhetherthereisanexistingphotowithagivenIDbeforeinsertinganewone.privatestaticfuncphotoFromJSONObject(json:[String:AnyObject],inContextcontext:NSManagedObjectContext)->Photo?{
guardletphotoID=json["id"]as?String,title=json["title"]as?String,photoURLString=json["url_h"]as?String,url=NSURL(string:photoURLString),dateString=json["datetaken"]as?String,dateTaken=dateFormatter.dateFromString(dateString)else{
//Don'thaveenoughinformationtoconstructaPhotoreturnnil}
letfetchRequest=NSFetchRequest(entityName:"Photo")letpredicate=NSPredicate(format:"photoID==\(photoID)")fetchRequest.predicate=predicate
varfetchedPhotos:[Photo]!context.performBlockAndWait(){fetchedPhotos=try!context.executeFetchRequest(fetchRequest)as![Photo]}iffetchedPhotos.count>0{returnfetchedPhotos.first
WOW! eBook www.wowebook.org
}
varphoto:Photo!context.performBlockAndWait(){photo=NSEntityDescription.insertNewObjectForEntityForName("Photo",inManagedObjectContext:context)as!Photophoto.title=titlephoto.photoID=photoIDphoto.remoteURL=urlphoto.dateTaken=dateTaken}
returnphoto
}
DuplicatephotoswillnolongerbeinsertedintoCoreData.
Buildandruntheapplication.ThephotoswillappearjustastheydidbeforeintroducingCoreData.AsyoudidinChapter15,closetheapplicationusingtheHomebutton(orShift-Command-Hinthesimulator).LaunchtheapplicationagainandyouwillseethephotosthatCoreDatasavedinthecollectionview.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SavingImagestoDiskAlthoughthePhotoentitiesarebeingpersisted,theimagedataassociatedwitheachphotoisnot.Largeblobsofdata,suchasanimage,shouldnotbestoredinCoreDataduetoperformanceconcerns.Instead,youshouldstoredatalikethisonthefilesystem.
Fortheimagedata,youwillusethesameapproachthatyouusedinyourHomepwnerapplication.Infact,youwillusethesameImageStoreclassthatyouwroteforthatproject.
OpenHomepwner.xcodeprojanddragtheImageStore.swiftfilefromtheHomepwnerapplicationtothePhotoramaapplication.MakesuretochooseCopyitemsifneeded.OncetheImageStore.swiftfilehasbeenaddedtoPhotorama,youcanclosetheHomepwnerproject.
BackinPhotorama,openPhotoStore.swiftandgiveitapropertyforanImageStore.classPhotoStore{
letcoreDataStack=CoreDataStack(modelName:"Photorama")letimageStore=ImageStore()
ThenupdatefetchImageForPhoto(_:completion:)tosavetheimagesusingtheimageStore.funcfetchImageForPhoto(photo:Photo,completion:(ImageResult)->Void){
letphotoKey=photo.photoKeyifletimage=photo.image{ifletimage=imageStore.imageForKey(photoKey){photo.image=imagecompletion(.Success(image))return}
letphotoURL=photo.remoteURLletrequest=NSURLRequest(URL:photoURL)
lettask=session.dataTaskWithRequest(request){(data,response,error)->Voidinletresult=self.processImageRequest(data:data,error:error)
ifcaselet.Success(image)=result{photo.image=imageself.imageStore.setImage(image,forKey:photoKey)}
completion(result)}task.resume()}
Buildandruntheapplication.Nowwhentheimagedataisdownloaded,itwillbesavedtothefilesystem.Thenexttimethatphotoisrequested,itwillbeloadedfromthefilesystemifitisnotcurrentlyinmemory.
ThePhotoramaapplicationisnowpersistingitsdatabetweenruns.ThephotometadataisbeingpersistedusingCoreData,andtheimagedataisbeingpersisteddirectlytothefilesystem.Asyouhaveseen,thereisnoone-size-fits-allapproachtodatapersistence.Instead,eachpersistencemechanismhasitsownsetofbenefitsanddrawbacks.Inthis
WOW! eBook www.wowebook.org
chapter,youhaveexploredoneofthose,CoreData,butyouhaveonlyseenthetipoftheiceberg.InChapter22,youwillexploretheCoreDataframeworkfurthertolearnaboutrelationshipsandperformance.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
BronzeChallenge:PhotoViewCountAddanattributetothePhotoentitythattrackshowmanytimesaphotoisviewed.DisplaythisnumbersomewhereonthePhotoInfoViewControllerinterface.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
22CoreDataRelationships
CoreDataisnotthatexcitingwithjustoneentity.MuchofthepowerbehindCoreDatacomestolightwhentherearemultipleentitiesthatarerelatedtooneanother,becauseCoreDatamanagesrelationshipsbetweenentities.
Inthischapter,youaregoingtoaddtagstothephotosinPhotoramawithlabelssuchas“Nature,”“Electronics,”or“Selfies.”Userswillbeabletoaddoneormoretagstophotosandalsocreatetheirowncustomtags(Figure22.1).
Figure22.1FinalPhotoramaapplication
WOW! eBook www.wowebook.org
RelationshipsOneofthebenefitsofusingCoreDataisthatentitiescanberelatedtooneanotherinawaythatallowsyoutodescribecomplexmodels.Relationshipsbetweenentitiesarerepresentedbyreferencesbetweenobjects.Therearetwokindsofrelationships:to-oneandto-many.
Whenanentityhasato-onerelationship,eachinstanceofthatentitywillhaveareferencetoaninstanceintheentityithasarelationshipto.
Whenanentityhasato-manyrelationship,eachinstanceofthatentityhasareferencetoaSet.Thissetcontainstheinstancesoftheentitythatithasarelationshipwith.Toseethisinaction,youaregoingtoaddanewentitytothemodelfile.
ReopenthePhotoramaapplication.InPhotorama.xcdatamodeld,addanotherentitycalledTag.GiveitanattributecallednameoftypeString.Tagwillallowuserstotagphotos.
Aphotomighthavemultipletagsthatdescribeit,andatagmightbeassociatedwithmultiplephotos.Forexample,apictureofaniPhonemightbetagged“Electronics”and“Apple,”andapictureofaBetamaxplayermightbetagged“Electronics”and“Rare.”TheTagentitywillhaveato-manyrelationshiptothePhotoentitybecausemanyinstancesofPhotocanhavethesameTag.AndthePhotoentitywillhaveato-manyrelationshiptotheTagentitybecauseaphotocanbeassociatedwithmanyTags.
AsFigure22.2shows,aPhotowillhaveareferencetoasetofTags,andaTagwillhaveareferencetoasetofPhotos.
WOW! eBook www.wowebook.org
Figure22.2EntitiesinPhotorama
Whentheserelationshipsaresetup,youwillbeabletoaskaPhotoobjectforthesetofTagobjectsthatitisassociatedwithandaskaTagobjectforthesetofPhotoobjectsthatitisassociatedwith.
Toaddthesetworelationshipstothemodelfile,firstselecttheTagentityandclickthe+buttonintheRelationshipssection.IntheRelationshipcolumn,nametherelationshipphotos.IntheDestinationcolumn,selectPhoto.Inthedatamodelinspector,changetheTypedropdownfromToOnetoToManyandunchecktheOptionalcheckbox(Figure22.3).
Figure22.3Creatingthephotosrelationship
WOW! eBook www.wowebook.org
Next,selectthePhotoentity.AddarelationshipnamedtagsandpickTagasitsdestination.Inthedatamodelinspector,changetheTypedropdowntoToManyanduncheckitsOptionalcheckbox.
Nowthatyouhavetwounidirectionalrelationships,youcanmakethemintoaninverserelationship.Aninverserelationshipisabidirectionalrelationshipbetweentwoentities.WithaninverserelationshipsetupbetweenPhotoandTag,CoreDatacanensurethatyourobjectgraphremainsinaconsistentstatewhenanychangesaremade.
Tocreatetheinverserelationship,clickNoInverseintheInversecolumnandselectphotos(Figure22.4).IfyoureturntotheTagentity,youwillseethatthephotosrelationshipnowshowstagsasitsinverse.
Figure22.4Creatingthetagsrelationship
NowopenPhoto+CoreDataProperties.swift.Addthetagsproperty,whichwillbeaSetofNSManagedObjectinstances.extensionPhoto{
@NSManagedvarphotoID:String@NSManagedvarphotoKey:String@NSManagedvartitle:String@NSManagedvardateTaken:NSDate@NSManagedvarremoteURL:NSURL@NSManagedvartags:Set<NSManagedObject>
}
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
AddingTagstotheInterfaceWhenusersnavigatetoaspecificphoto,theycurrentlyseeonlythetitleofthephotoandtheimageitself.Let’supdatetheinterfacetoincludeaphoto’sassociatedtags.
OpenMain.storyboardandnavigatetotheinterfaceforPhotoInfoViewController.Addatoolbartothebottomoftheview.UpdatetheAutoLayoutconstraintssothatthetoolbarisanchoredtothebottom,justasitwasinHomepwner.ThebottomconstraintfortheimageViewshouldbeanchoredtothetopofthetoolbarinsteadofthebottomofthesuperview.AddaUIBarButtonItemtothetoolbar,ifoneisnotalreadypresent,andgiveitatitleofTags.YourinterfacewilllooklikeFigure22.5.
Figure22.5PhotoInfoViewControllerinterface
CreateanewSwiftfilenamedTagsViewController.OpenthisfileanddeclaretheTagsViewControllerclassasasubclassofUITableViewController.ImportUIKitandCoreDatainthisfile.importFoundationimportUIKitimportCoreData
WOW! eBook www.wowebook.org
classTagsViewController:UITableViewController{
}
TheTagsViewControllerwilldisplayalistofallthetags.Theuserwillseeandbeabletoselectthetagsthatareassociatedwithaspecificphoto.Theuserwillalsobeabletoaddnewtagsfromthisscreen.ThecompletedinterfacewilllooklikeFigure22.6.
Figure22.6TagsViewController
GivetheTagsViewControllerclassapropertytoreferencethePhotoStoreaswellasaspecificPhoto.Youwillalsoneedapropertytokeeptrackofthecurrentlyselectedtags,whichyouwilltrackusinganarrayofNSIndexPathinstances.classTagsViewController:UITableViewController{
varstore:PhotoStore!varphoto:Photo!
varselectedIndexPaths=[NSIndexPath]()}
Thedatasourceforthetableviewwillbeaseparateclass.AswediscussedwhenyoucreatedPhotoDataSourceinChapter20,anapplicationwhosetypeshaveasingleresponsibilityiseasiertoadapttofuturechanges.Thisclasswillberesponsiblefordisplayingthelistoftagsinthetableview.
CreateanewSwiftfilenamedTagDataSource.swift.DeclaretheTagDataSourceclassandimplementthetableviewdatasourcemethods.YouwillneedtoimportUIKitandCoreData.importFoundationimportUIKitimportCoreData
classTagDataSource:NSObject,UITableViewDataSource{
vartags:[NSManagedObject]=[]
functableView(tableView:UITableView,
WOW! eBook www.wowebook.org
numberOfRowsInSectionsection:Int)->Int{returntags.count}
functableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{
letcell=tableView.dequeueReusableCellWithIdentifier("UITableViewCell",forIndexPath:indexPath)
lettag=tags[indexPath.row]letname=tag.valueForKey("name")as!Stringcell.textLabel?.text=name
returncell}
}
OpenPhotoStore.swiftanddefineanewmethodthatfetchesallthetagsfromthemainqueue.funcfetchMainQueueTags(predicatepredicate:NSPredicate?=nil,sortDescriptors:[NSSortDescriptor]?=nil)throws->[NSManagedObject]{letfetchRequest=NSFetchRequest(entityName:"Tag")fetchRequest.predicate=predicatefetchRequest.sortDescriptors=sortDescriptors
letmainQueueContext=self.coreDataStack.mainQueueContextvarmainQueueTags:[NSManagedObject]?varfetchRequestError:ErrorType?mainQueueContext.performBlockAndWait({do{mainQueueTags=trymainQueueContext.executeFetchRequest(fetchRequest)as?[NSManagedObject]}catchleterror{fetchRequestError=error}})
guardlettags=mainQueueTagselse{throwfetchRequestError!}
returntags}
Withthedatasourceandfetchrequestcomplete,youhavefinishedtheimplementationofTagsViewController.
OpenTagsViewController.swiftandsetthedataSourceforthetableviewtobeaninstanceofTagDataSource.classTagsViewController:UITableViewController{
varstore:PhotoStore!varphoto:Photo!
varselectedIndexPaths=[NSIndexPath]()
lettagDataSource=TagDataSource()
overridefuncviewDidLoad(){super.viewDidLoad()
tableView.dataSource=tagDataSource}}
Nowfetchthemainqueuetagsandassociatethemwiththetagspropertyonthedata
WOW! eBook www.wowebook.org
source.overridefuncviewDidLoad(){super.viewDidLoad()
tableView.dataSource=tagDataSource
updateTags()}
funcupdateTags(){lettags=try!store.fetchMainQueueTags(predicate:nil,sortDescriptors:[NSSortDescriptor(key:"name",ascending:true)])tagDataSource.tags=tags}
TheTagsViewControllerneedstomanagetheselectionoftagsandupdatethePhotoinstancewhentheuserselectsordeselectsatag.
AddtheappropriateUITableViewDelegatemethodsinTagsViewController.swift.AlsoaddtheappropriateindexpathstotheselectedIndexPathsarray.overridefuncviewDidLoad(){super.viewDidLoad()
tableView.dataSource=tagDataSourcetableView.delegate=selfupdateTags()}
funcupdateTags(){lettags=try!store.fetchMainQueueTags(predicate:nil,sortDescriptors:[NSSortDescriptor(key:"name",ascending:true)])tagDataSource.tags=tags
fortaginphoto.tags{ifletindex=tagDataSource.tags.indexOf(tag){letindexPath=NSIndexPath(forRow:index,inSection:0)selectedIndexPaths.append(indexPath)}}}
overridefunctableView(tableView:UITableView,didSelectRowAtIndexPathindexPath:NSIndexPath){
lettag=tagDataSource.tags[indexPath.row]
ifletindex=selectedIndexPaths.indexOf(indexPath){selectedIndexPaths.removeAtIndex(index)}else{selectedIndexPaths.append(indexPath)}
tableView.reloadRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}
overridefunctableView(tableView:UITableView,willDisplayCellcell:UITableViewCell,forRowAtIndexPathindexPath:NSIndexPath){
ifselectedIndexPaths.indexOf(indexPath)!=nil{cell.accessoryType=.Checkmark}else{cell.accessoryType=.None}}
Currently,thetagsarenotbeingaddedorremovedfromthePhoto.Addingand
WOW! eBook www.wowebook.org
removingobjectsfromaCoreDatato-manyrelationshipisalittlebizarreduetothedynamicnatureofNSManagedObjectcombinedwiththeimmutabilityoftheSetthatCoreDatareturns.Toallowforchanges,ApplehasprovidedthemethodmutableSetValueForKey(_:).Thismethodreturnsanobjectthatisamutableset,andyouthenmakeanychangestothisobject.
OpenPhoto.swiftanddefinetwomethodsthathandleaddingandremovingTaginstancesfromthetagsset.funcaddTagObject(tag:NSManagedObject){letcurrentTags=mutableSetValueForKey("tags")currentTags.addObject(tag)}
funcremoveTagObject(tag:NSManagedObject){letcurrentTags=mutableSetValueForKey("tags")currentTags.removeObject(tag)}
ByusingthemutableSetValueForKey(_:)method,CoreDatawillbeinformedofanychangestotheto-manyrelationshipandwillallowthechangestopersist.
YoucannowusethesetwomethodsonPhototoupdatethetagsassociatedwithaphoto.
InTagsViewController.swift,updatetableView(_:didSelectRowAtIndexPath:)toaddorremovetheassociatedTagfromthePhoto.Alsosavethechangestothecontextwhentheusermakesaselection.overridefunctableView(tableView:UITableView,didSelectRowAtIndexPathindexPath:NSIndexPath){
lettag=tagDataSource.tags[indexPath.row]
ifletindex=find(selectedIndexPaths,indexPath){selectedIndexPaths.removeAtIndex(index)photo.removeTagObject(tag)}else{selectedIndexPaths.append(indexPath)photo.addTagObject(tag)}
tableView.reloadRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)
do{trystore.coreDataStack.saveChanges()}catchleterror{print("CoreDatasavefailed:\(error)")}}
Let’ssetupTagsViewControllertobepresentedmodallywhentheusertapstheTagsbarbuttonitemonthePhotoInfoViewController.
OpenMain.storyboardanddragaNavigationControllerontothecanvas.ThisshouldgiveyouaUINavigationControllerwitharootviewcontrollerthatisaUITableViewController.IftherootviewcontrollerisnotaUITableViewController,deletetherootviewcontroller,dragaTableViewControllerontothecanvas,andmakeittherootviewcontrolleroftheNavigationController.
Control-dragfromtheTagsitemonPhotoInfoViewControllertothenewNavigationControllerandWOW! eBook
www.wowebook.org
selectthePresentModallyseguetype(Figure22.7).OpentheattributesinspectorforthesegueandgiveitanIdentifiernamedShowTags.
Figure22.7Addingthetagsviewcontroller
SelecttheRootViewControllerthatyoujustaddedtothecanvasandopenitsidentityinspector.ChangeitsClasstoTagsViewController.Thisnewviewcontrollerdoesnothaveanavigationitemassociatedwithit,sofindNavigationItemintheobjectlibraryanddragitontotheviewcontroller.Double-clickthenewnavigationitem’sTitlelabelandchangeittoTags.
Next,theUITableViewCellontheTagsViewControllerinterfaceneedstomatchwhattheTagDataSourceexpects.Itneedstousethecorrectstyleandhavethecorrectreuseidentifier.
WOW! eBook www.wowebook.org
SelecttheUITableViewCell.(Itmightbeeasiertoselectinthedocumentoutline.)Openitsattributesinspector.ChangetheStyletoBasicandsettheIdentifiertoUITableViewCell(Figure22.8).
Figure22.8ConfiguringtheUITableViewCell
Now,theTagsViewControllerneedstwobarbuttonitemsonitsnavigationbar:aDonebuttonthatdismissestheviewcontrolleranda+buttonthatallowstheusertoaddanewtag.
DragabarbuttonitemtotheleftandrightbarbuttonitemslotsfortheTagsViewController.SettheleftitemtousetheDonestyleandsystemitem.SettherightitemtousetheBorderedstyleandanAddsystemitem(Figure22.9).
Figure22.9Barbuttonitemattributes
CreateandconnectanactionforeachoftheseitemstotheTagsViewController.TheDoneitemshouldbeconnectedtoamethodnameddone(_:),andthe+itemshouldbeconnectedtoamethodnamedaddNewTag(_:).ThetwomethodsinTagsViewController.swiftwillbe:@IBActionfuncdone(sender:AnyObject){
}
@IBActionfuncaddNewTag(sender:AnyObject){
}
Theimplementationofdone(_:)issimple:theviewcontrollerjustneedstobe
WOW! eBook www.wowebook.org
dismissed.Implementthisfunctionalityindone(_:).@IBActionfuncdone(sender:AnyObject){presentingViewController?.dismissViewControllerAnimated(true,completion:nil)}
Whentheusertapsthe+item,analertwillbepresentedthatwillallowtheusertotypeinthenameforanewtag.
Figure22.10Addinganewtag
SetupandpresentaninstanceofUIAlertControllerinaddNewTag(_:).@IBActionfuncaddNewTag(sender:AnyObject){letalertController=UIAlertController(title:"AddTag",message:nil,preferredStyle:.Alert)
alertController.addTextFieldWithConfigurationHandler({(textField)->VoidintextField.placeholder="tagname"textField.autocapitalizationType=.Words})
letokAction=UIAlertAction(title:"OK",style:.Default,handler:{(action)->Voidin
})alertController.addAction(okAction)
letcancelAction=UIAlertAction(title:"Cancel",style:.Cancel,handler:nil)alertController.addAction(cancelAction)
presentViewController(alertController,animated:true,completion:nil)}
UpdatethecompletionhandlerfortheokActiontoinsertanewTagintothecontext.Thensavethecontext,updatethelistoftags,andreloadthetableviewsection.letokAction=UIAlertAction(title:"OK",style:.Default,handler:{(action)->Voidin
iflettagName=alertController.textFields?.first!.text{letcontext=self.store.coreDataStack.mainQueueContextletnewTag=NSEntityDescription.insertNewObjectForEntityForName("Tag",inManagedObjectContext:context)newTag.setValue(tagName,forKey:"name")
do{tryself.store.coreDataStack.saveChanges()}catchleterror{
WOW! eBook www.wowebook.org
print("CoreDatasavefailed:\(error)")}self.updateTags()self.tableView.reloadSections(NSIndexSet(index:0),withRowAnimation:.Automatic)}})alertController.addAction(okAction)
Finally,whentheTagsbarbuttonitemonPhotoInfoViewControlleristapped,thePhotoInfoViewControllerneedstopassalongitsstoreandphotototheTagsViewController.
OpenPhotoInfoViewControllerandimplementprepareForSegue(_:).overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){ifsegue.identifier=="ShowTags"{letnavController=segue.destinationViewControlleras!UINavigationControllerlettagController=navController.topViewControlleras!TagsViewController
tagController.store=storetagController.photo=photo}}
Buildandruntheapplication.NavigatetoaphotoandtaptheTagsitemonthetoolbaratthebottom.TheTagsViewControllerwillbepresentedmodally.Tapthe+item,enteranewtag,andselectthenewtagtoassociateitwiththephoto.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
Parent-ChildContextsYoulearnedinChapter21thataninstanceofNSManagedObjectContextisassociatedwithaspecificqueue,whichisthemainqueueinthecaseofthemainQueueContext.AsinglecontextassociatedwiththemainqueueistheminimumthatyouneedforaworkingCoreDataapplication,butanyCoreDataoperationsthattakealongtimewillnoticeablyblockthemainqueue,resultinginanunresponsiveapplication.
Toaddressthisproblem,applicationsusingCoreDataoftenhavemultiplecontexts.Photoramawillhaveonecontextassociatedwiththemainqueueandanothercontextassociatedwithabackgroundqueue.
OpenCoreDataStack.swift.Addanothercontextthatisassociatedwithaprivate,orbackground,queue.lazyvarprivateQueueContext:NSManagedObjectContext={
letmoc=NSManagedObjectContext(concurrencyType:.PrivateQueueConcurrencyType)moc.parentContext=self.mainQueueContextmoc.name="PrimaryPrivateQueueContext"
returnmoc}()
AnNSManagedObjectContextcaneitherbeassociatedwithanNSPersistentStoreCoordinatororwithaparentNSManagedObjectContext.Thecontextatthe“top”ofthestackmustbeassociatedwithapersistentstorecoordinator.
Sohowdoesthiswork?Whenyousaveacontextassociatedwithapersistentstorecoordinator,thosechangesarepersistedtothestore.Whenyousaveacontextthathasaparentcontext,anychangesfromthechildcontextpropagateuponlyoneleveltotheparentcontext.Atthispoint,theparentcontextwillhavenewchanges.Thesenewchangeswillnotautomaticallybesaved,soyouwillalsoneedtosavetheparentcontext.
InCoreDataStack.swift,updatesaveChanges(_:)tosupportthisfunctionality.funcsaveChanges()throws{varerror:ErrorType?
privateQueueContext.performBlockAndWait{()->Voidinifself.privateQueueContext.hasChanges{do{tryself.privateQueueContext.save()}catchletsaveError{error=saveError}}}
ifleterror=error{throwerror}
mainQueueContext.performBlockAndWait{()->Voidinifself.mainQueueContext.hasChanges{do{tryself.mainQueueContext.save()}
WOW! eBook www.wowebook.org
catchletsaveError{error=saveError}}}
ifleterror=error{throwerror}}
First,anychangesfromtheprivateQueueContextarepropagateduptothemainQueueContext.Then,anychangestothemainQueueContextwillbepersistedusingitspersistentstorecoordinator.
Nowlet’susethisnewcontexttoimprovetheperformanceofPhotorama.Currently,thePhotoentitiesareaddedonthemainqueue,whichcanblockotheroperations.Youaregoingtoupdatetheapplicationtoaddentitiesonthebackgroundqueueinstead.
OpenPhotoStore.swiftandupdateprocessRecentPhotosRequest(data:error:)tousetheprivateQueueContext.funcprocessRecentPhotosRequest(datadata:NSData?,error:NSError?)->PhotosResult{
guardletjsonData=dataelse{return.Failure(error!)}
returnFlickrAPI.photosFromJSONData(jsonData,inContext:self.coreDataStack.mainQueueContext)
returnFlickrAPI.photosFromJSONData(jsonData,inContext:self.coreDataStack.privateQueueContext)}
NowupdatefetchRecentPhotos(completion:)togetthepermanentIDsforthephotosfromtheprivatequeuecontext,sincethatisthecontextthatthephotosarebeingaddedtonow.funcfetchRecentPhotos(completioncompletion:(PhotosResult)->Void){leturl=FlickrAPI.recentPhotosURL()letrequest=NSURLRequest(URL:url)lettask=session.dataTaskWithRequest(request,completionHandler:{(data,response,error)->Voidin
varresult=self.processRecentPhotosRequest(data:data,error:error)
ifcaselet.Success(photos)=result{letmainQueueContext=self.coreDataStack.mainQueueContextmainQueueContext.performBlockAndWait({try!mainQueueContext.obtainPermanentIDsForObjects(photos)})
letprivateQueueContext=self.coreDataStack.privateQueueContextprivateQueueContext.performBlockAndWait({try!privateQueueContext.obtainPermanentIDsForObjects(photos)})
...
Buildandruntheapplication.Althoughthebehaviorhasnotchanged,theapplicationisnolongerindangerofbecomingunresponsivewhilenewphotosarebeingadded.Asthescaleofyourapplicationsincreases,handlingCoreDataentitiessomewhereotherthanthemainqueue(likeyouhavedonehere)canresultinhugeperformancewins.
WOW! eBook www.wowebook.org
Congratulations!Overthepastfourchapters,youhaveworkedonarathercomplexapp.Photoramaisabletomakemultiplewebservicecalls,displayphotosinagrid,cacheimagedatatothefilesystem,andpersistphotodatausingCoreData.Toaccomplishthis,youusedknowledgethatyouhavegainedthroughoutthisbook,andyouappliedthatknowledgetocreateanawesomeappthatisalsorobustandmaintainable.Itwashardwork,andyoushouldbeproudofyourself.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
SilverChallenge:FavoritesAllowtheusertofavoritephotos.Becreativeinhowyoupresentthefavoritephotostotheuser.TwopossibilitiesincludeviewingthemusingaUITabBarControlleroraddingaUISegmentedControltothePhotosViewControllerthatswitchesbetweenallphotosandfavoritephotos.(Hint:youwillneedtoaddanewattributetothePhotoentity.)
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
23Afterword
Welcometotheendofthebook!Youshouldbeveryproudofallyourworkandallthatyouhavelearned.Nowthereisgoodnewsandbadnews:
Thegoodnews:ThestuffthatleavesprogrammersbefuddledwhentheycometotheiOSplatformisbehindyounow.YouareaniOSdeveloper.
Thebadnews:YouareprobablynotaverygoodiOSdeveloper.
WOW! eBook www.wowebook.org
WhattoDoNextItisnowtimetomakesomemistakes,readsomereallytediousdocumentation,andbehumbledbytheheartlessexpertswhowillridiculeyourquestions.Hereiswhatwerecommend:
Writeappsnow.Ifyoudonotimmediatelyusewhatyouhavelearned,itwillfade.Exerciseandextendyourknowledge.Now.
Godeep.Thisbookhasconsistentlyfavoredbreadthoverdepth;anychaptercouldhavebeenexpandedintoanentirebook.Findatopicthatyoufindinterestingandreallywallowinit–dosomeexperiments,readApple’sdocsonthetopic,readapostingonablogoronStackOverflow.
Connect.ThereisaniOSDeveloperMeetupinmostcities,andthetalksaresurprisinglygood.ThereareCocoaHeadschaptersaroundtheworld.Therearediscussiongroupsonline.Ifyouaredoingaproject,findpeopletohelpyou:designers,testers(AKAguineapigs),andotherdevelopers.
Makemistakesandfixthem.Youwilllearnalotonthedayswhenyousay,“Thisapplicationhasbecomeaballofcrap!I’mgoingtothrowitawayandwriteitagainwithanarchitecturethatmakessense.”Politeprogrammerscallthisrefactoring.
Giveback.Sharetheknowledge.Answeradumbquestionwithgrace.Giveawaysomecode.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
ShamelessPlugsYoucanfindusonTwitter,wherewekeepyouinformedaboutprogrammingandentertainedaboutlife:@cbkeurand@aaronhillegass.
KeepaneyeoutforotherguidesfromBigNerdRanch.Wealsoofferweek-longcoursesfordevelopers.Andifyoujustneedsomecodewritten,wedocontractprogramming.Formoreinformation,visitourwebsiteathttp://www.bignerdranch.com/.
You,dearreader,makeourlivesofwriting,coding,andteachingpossible.Sothankyouforbuyingourbook.
WOW! eBook www.wowebook.org
WOW! eBook www.wowebook.org
IndexABCDEFGHIJKLMNOPQRSTUVWX
Symbols
.xcassets(assetcatalog),ApplicationIcons
.xcdatamodeld(datamodelfile),Modelingentities//MARK:,//MARK:@IBInspectable,@IBInspectable
A
accesscontrol,NSURLComponentsaccessoryindicator(UITableViewCell),UITableViewCellsactionmethods
connectingininterfacefile,Addingacamerabutton
defining,Definingactionmethods
implementing,Implementingactionmethods
andUIControl,FortheMoreCurious:UIControl
activestate,ApplicationStatesandTransitionsaddSubview(_:),ViewsandFramesalerts,displaying,DisplayingUserAlertsalignmentrectangles,Alignmentrectangleandlayoutattributesanchors,AnchorsanimateWithDuration:animations:,BasicAnimationsanimations
animatingconstraints,AnimatingConstraints
basic,BasicAnimations
markingcompletionof,AnimationCompletion
spring-like,BronzeChallenge:SpringAnimations
timingfunctions,TimingFunctions
anti-aliasing,FortheMoreCurious:RetinaDisplayAPIReference,Archivingappend(_:),Instancemethodsapplicationbundle
explained,FortheMoreCurious:TheApplicationBundle
WOW! eBook www.wowebook.org
andinternationalization,Baseinternationalization,FortheMoreCurious:NSBundle’sRoleinInternationalization
applicationsandbox,ApplicationSandbox,FortheMoreCurious:TheApplicationBundleapplicationstates,ApplicationStatesandTransitions,FortheMoreCurious:ApplicationStateTransitionsapplicationDidBecomeActive:,ApplicationStatesandTransitions,FortheMoreCurious:ApplicationStateTransitionsapplicationDidEnterBackground(_:),NSKeyedArchiverandNSKeyedUnarchiver,FortheMoreCurious:ApplicationStateTransitionsapplicationDidEnterBackground:,ApplicationStatesandTransitionsapplications
(seealsoapplicationbundle,debugging,projects)
building,Runningonthesimulator,Localization
cleaning,Localization
datastorage,ApplicationSandbox
directoriesin,ApplicationSandbox
iconsfor,ApplicationIcons
launchimagesfor,LaunchScreen
multiplethreadsin,TheMainThread
runningoniPad,CreatinganXcodeProject
runningonsimulator,Runningonthesimulator
applicationWillEnterForeground:,ApplicationStatesandTransitions,FortheMoreCurious:ApplicationStateTransitionsapplicationWillResignActive:,ApplicationStatesandTransitions,FortheMoreCurious:ApplicationStateTransitionsarchiving
vs.CoreData,CoreData
described,Archiving
implementing,Archiving
withNSKeyedArchiver,NSKeyedArchiverandNSKeyedUnarchiver
arrays
about,Collectiontypes
append(_:),Instancemethods
count,Properties
reverse(),Instancemethods
subscripting,Literalsandsubscripting
WOW! eBook www.wowebook.org
andtraps,Literalsandsubscripting
writingtofilesystem,FortheMoreCurious:ReadingandWritingtotheFilesystem
assetcatalogs(Xcode),ApplicationIconsassistanteditor(Xcode),Addingacamerabuttonattributes(CoreData),EntitiesAutoLayout
(seealsoconstraints(AutoLayout),InterfaceBuilder)
alignmentrectangles,Alignmentrectangleandlayoutattributes
autoresizingmasksand,FortheMoreCurious:NSAutoresizingMaskLayoutConstraint
dynamiccellheights,DynamicCellHeights
introductionto,AbriefintroductiontoAutoLayout
layoutattributes,Alignmentrectangleandlayoutattributes
purposeof,TheAutoLayoutSystem
autoresizingmasks,ProgrammaticConstraints,FortheMoreCurious:NSAutoresizingMaskLayoutConstraintawakeFromInsert,NSManagedObjectandsubclasses
B
backgroundstate,ApplicationStatesandTransitions,FortheMoreCurious:ApplicationStateTransitionsBaseinternationalization,Baseinternationalizationbaselines,Alignmentrectangleandlayoutattributesbasicanimations,BasicAnimationsbecomeFirstResponder,DismissingthekeyboardBool,NumberandBooleantypesbooleantypes,NumberandBooleantypesbundles
application(seeapplicationbundle)
NSBundle,FortheMoreCurious:NSBundle’sRoleinInternationalization,FortheMoreCurious:TheApplicationBundle
buttons
addingtonavigationbars,Addingbuttonstothenavigationbar
camera,Addingacamerabutton
C
CALayer,TheViewHierarchycallbacks,Delegation
WOW! eBook www.wowebook.org
(seealsodelegation,target-actionpairs)
camera
(seealsoimages)
takingpictures,Addingacamerabutton
cancelsTouchesInView,UIPanGestureRecognizerandsimultaneousrecognizerscanPerformAction(_:withSender:),FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActionscells
(seealsoUITableViewCell)
addingpaddingto,ContentInsets
changingcellclass,CreatingItemCell
customizinglayoutof,CustomizingtheLayout
dynamiccellheights,DynamicCellHeights
prototype,ReusingUITableViewCells,CreatingaCustomUICollectionViewCell
reusing,ReusingUITableViewCells
CGPoint,ViewsandFramesCGRect,ViewsandFramesCGSize,ViewsandFramesclosures,Closures,ConstructingafileURLcollectionview
customizinglayoutof,CustomizingtheLayout
displaying,DisplayingtheGrid
downloadingimagedata,DownloadingtheImageData
layoutobject,CollectionViews
navigatingto/displayingphotos,NavigatingtoaPhoto
settingdatasource,CollectionViewDataSource
colors
background,ViewsandFrames,DisplayingtheGrid
customizing,Customizingthelabels
commonancestor,Activatingconstraintsconcurrency,TheMainThreadconditionals
if-let,Optionals
switch,EnumerationsandtheSwitchStatement
WOW! eBook www.wowebook.org
connections(inInterfaceBuilder),Makingconnectionsconnectionsinspector,Summaryofconnectionsconsole
printingto,Usingadelegate
viewinginplayground,Optionals
constants,UsingStandardTypesconstraints(AutoLayout)
activatingprogrammatically,Activatingconstraints
addingtolabels,Preparingforlocalization
animating,AnimatingConstraints
clearing,AbriefintroductiontoAutoLayout,Addingmoreconstraints
collectionview,DisplayingtheGrid
creatingexplicitconstraints,Explicitconstraints
creatinginInterfaceBuilder,Constraints
creatingprogrammatically,ProgrammaticConstraints
implicit,Implicitconstraints
nearestneighborand,Constraints
overview,Constraints
pin,AddingconstraintsinInterfaceBuilder
resolvingunsatisfiable,FortheMoreCurious:NSAutoresizingMaskLayoutConstraint
specifying,AbriefintroductiontoAutoLayout
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:,Explicitconstraintscontentcompression,ContentcompressionresistanceprioritiescontentMode(UIImageView),DisplayingImagesandUIImageViewcontentView(UITableViewCell),UITableViewCellscontrolevents,FortheMoreCurious:UIControlcontrollerobjects,Model-View-Controllercontrols,programmatic,ProgrammaticControlsCoreData
vs.archiving,CoreData
attributes,Entities
fetchrequests,Fetchrequestsandpredicates
persistentstoreformats,NSPersistentStoreCoordinator
relationshipmanagementwith,CoreDataRelationships
WOW! eBook www.wowebook.org
roleof,Entities
subclassingNSManagedObject,NSManagedObjectandsubclasses
transformingvalues,Transformableattributes
CoreGraphics,FortheMoreCurious:RetinaDisplaycount(arrays),PropertiescurrentLocale,Formatters
D
datasourcemethods,Implementingdatasourcemethods,DesignPatterns,CollectionViewDataSource,UpdatingtheDataSourcedatastorage
(seealsoarchiving,CoreData)
forapplicationdata,ApplicationSandbox
binary,WritingtotheFilesystemwithNSData,FortheMoreCurious:ReadingandWritingtotheFilesystem
withNSData,WritingtotheFilesystemwithNSData
dataSource(UITableView),UITableViewController,UITableView’sDataSource,Implementingdatasourcemethodsdebugging
(seealsodebuggingtools,exceptions)
debuggingtools
issuenavigator,BuildingtheFinishedApplication
Xcode,Optionals
declarations
protocol,Conformingtoaprotocol
default:(switchstatement),EnumerationsandtheSwitchStatementdelegation
designpattern,DesignPatterns
forUIImagePickerController,Settingtheimagepicker’sdelegate
forUITableView,UITableViewController
overview,Delegation
UICollectionViewDelegate,DownloadingtheImageData
deleteRowsAtIndexPaths(_:withRowAnimation:),DeletingRowsdependencyinjection,Givingthecontrolleraccesstothestoredependencyinversionprinciple,Givingthecontrolleraccesstothestore
WOW! eBook www.wowebook.org
designpatterns,DesignPatternsDetailViewController,WrappingUpImageStore,SizeClassesdeveloperdocumentation,Archivingdevices
checkingforcamera,Settingtheimagepicker’ssourceType
displayresolution,ViewsandFrames
Retinadisplay,ApplicationIcons,FortheMoreCurious:RetinaDisplay
dictionaries
(seealsoJSONdata)
about,Collectiontypes
accessing,Subscriptingdictionaries
subscripting,Subscriptingdictionaries
using,CreatingandUsingKeys
writingtofilesystem,FortheMoreCurious:ReadingandWritingtotheFilesystem
directories
application,ApplicationSandbox
Documents,ApplicationSandbox
Library/Caches,ApplicationSandbox
Library/Preferences,ApplicationSandbox
lproj,Baseinternationalization,FortheMoreCurious:NSBundle’sRoleinInternationalization
temporary,ApplicationSandbox
displayresolution,ViewsandFramesdocumentoutline(InterfaceBuilder),InterfaceBuilderdocumentation
developer,Archiving
opening,ControllingAnimations
forSwift,ExploringApple’sSwiftDocumentation
Documentsdirectory,ApplicationSandboxDouble,NumberandBooleantypesdrawing(seeviews)drill-downinterface,UINavigationControllerDynamicType,DynamicType
E
WOW! eBook www.wowebook.org
editButtonItem,Addingbuttonstothenavigationbarediting(UITableView,UITableViewController),EditingModeeditorarea(Xcode),InterfaceBuilderencodeInteger(_:forKey:),ArchivingencodeObject(_:forKey:),ArchivingencodeWithCoder(_:),NSKeyedArchiverandNSKeyedUnarchiverencodeWithCoder:,ArchivingendEditing(_:),Dismissingbytappingelsewhereentities
creating,UpdatingItems
defined,Entities
modeling,Modelingentities
relationshipsbetween,Relationships
savingchangesto,Savingchanges
enumerate(),LoopsandStringInterpolationenums(enumerations)
defined,EnumerationsandtheSwitchStatement
overviewof,Enumerationsandassociatedvalues
andrawvalues,Enumerationsandrawvalues
andswitchstatements,EnumerationsandtheSwitchStatement
errorhandling,ErrorHandling,DownloadingtheImageDataerrors
dealingwith,Optionals
inplaygrounds,UsingStandardTypes
traps,Literalsandsubscripting
eventhandling,Eventhandlingbasicsevents
control,ProgrammaticControls,FortheMoreCurious:UIControl
touch,Eventhandlingbasics,TouchEvents
(seealsotouchevents)
exceptions
vs.errorhandling,FortheMoreCurious:ReadingandWritingtotheFilesystem
internalinconsistency,AddingRows
Swiftvs.otherlanguages,FortheMoreCurious:ReadingandWritingtotheFilesystem
expressions,stringinterpolationand,LoopsandStringInterpolationWOW! eBook
www.wowebook.org
extensions,Extensions
F
fallthrough(switchstatement),EnumerationsandtheSwitchStatementfetchrequests,Fetchrequestsandpredicatesfileinspector,LocalizationfileURLs,retrieving,ConstructingafileURLfilesystem,writingto,WritingtotheFilesystemwithNSData,FortheMoreCurious:ReadingandWritingtotheFilesystemfirstresponder
becoming,Dismissingthekeyboard
andnil-targetedactions,FortheMoreCurious:UIControl
overview,DismissingtheKeyboard
resigning,Dismissingthekeyboard,DismissingbypressingtheReturnkey,Dismissingbytappingelsewhere
andresponderchain,FortheMoreCurious:TheResponderChain
andUIMenuController,UIMenuController
Flickr,WebServicesFloat,NumberandBooleantypesfloating-pointtypes,NumberandBooleantypes,Initializersfonts
changingpreferredsize,DynamicType
customizingsize,Customizingthelabels
for-in,LoopsandStringInterpolationforcedunwrapping(ofoptionals),Optionalsframe(UIView),ViewsandFramesframeworks
CoreData(seeCoreData)
definitionof,ViewsandFrames
linkingmanually,SettingtheInitialViewController
functions
(seealsoindividualfunctionnames)
callback,Delegation
G
genstrings,NSLocalizedStringandstringstables
WOW! eBook www.wowebook.org
gesturerecognizer(seeUIGestureRecognizer)gestures
(seealsoUIGestureRecognizer,UIScrollView)
discretevs.continuous,UILongPressGestureRecognizer
longpress,UILongPressGestureRecognizer
panning,UILongPressGestureRecognizer,UIPanGestureRecognizerandsimultaneousrecognizers
taps,DetectingTapswithUITapGestureRecognizer
GUIDs,CreatingandUsingKeys
H
Hashable,Collectiontypesheaderview(UITableView),EditingModehierarchies,view,TheViewHierarchyHomepwnerapplication
addinganimagestore,CreatingImageStore
addingdrill-downinterface,UINavigationController,UINavigationController
addingitemimages,Camera
adjustingviewpropertiespersizeclass,AnotherSizeClass
applicationsandbox,NSKeyedArchiverandNSKeyedUnarchiver
creatingnestedstackviews,StackViews
enablingediting,EditingUITableView
objectdiagrams,UITableView’sDataSource,UINavigationController
storingimages,WritingtotheFilesystemwithNSData
horizontalambiguity,AddingconstraintsinInterfaceBuilderHTTPprotocol,FortheMoreCurious:HTTP
I
IBAction,Definingactionmethods,Addingacamerabutton@IBInspectable,@IBInspectableIBOutlet,Declaringoutlets,HookingUptheContenticons
(seealsoimages)
application,ApplicationIcons
WOW! eBook www.wowebook.org
assetcatalogsfor,ApplicationIcons
camera,Addingacamerabutton
if-let,Optionalsimagepicker(seeUIImagePickerController)imageNamed:,FortheMoreCurious:RetinaDisplayimagePickerController:didFinishPickingMediaWithInfo:,Settingtheimagepicker’sdelegateimagePickerControllerDidCancel:,Settingtheimagepicker’sdelegateimages
(seealsocamera,icons,UIImageView)
accessingfromthecache,CreatingandUsingKeys
caching,WritingtotheFilesystemwithNSData
displayinginUIImageView,DisplayingImagesandUIImageView
downloadingimagedata,DownloadingandDisplayingtheImageData,DownloadingtheImageData
fetching,GivingViewControllersAccesstotheImageStore
modelingPhotoclass,ModelingthePhoto
navigatingtophotos,NavigatingtoaPhoto
forRetinadisplay,FortheMoreCurious:RetinaDisplay
saving,Savingtheimage
savingtodisk,SavingImagestoDisk
storing,CreatingImageStore
wrappinginNSData,WritingtotheFilesystemwithNSData
imageWithContentsOfFile(_:),WritingtotheFilesystemwithNSDataimplementationfiles,navigating,FortheMoreCurious:NavigatingImplementationFilesimplicitconstraints,Implicitconstraintsinactivestate,ApplicationStatesandTransitionsinequalityconstraints,Preparingforlocalizationinit(coder:),Archivinginit(frame:),ViewsandFramesinit?(contentsOfFile:encoding:error:),FortheMoreCurious:ReadingandWritingtotheFilesysteminitialviewcontroller,SettingtheInitialViewControllerinitializers
about,Initializers
convenience,Custominitializers
custom,Custominitializers
WOW! eBook www.wowebook.org
designated,Custominitializers
free,Custominitializers
member-wise,Structs
forstandardtypes,Initializers
initWithCoder:,Archivinginspectors(Xcode)
connections,Summaryofconnections
file,Localization
instancevariables,NSKeyedArchiverandNSKeyedUnarchiver
(seealsopointers,properties)
instances,InitializersInt,NumberandBooleantypesintegertypes,NumberandBooleantypesInterfaceBuilder
(seealsoXcode)
addingconstraintsin,AddingconstraintsinInterfaceBuilder
attributesinspector,ViewsandFrames
canvas,InterfaceBuilder
connectingobjects,Makingconnections
connectingwithsourcefiles,ExposingthePropertiesofItemCell
documentoutline,InterfaceBuilder
modifyingviewattributes,@IBInspectable
andproperties,ExposingthePropertiesofItemCell
scene,InterfaceBuilder
settingoutletsin,Settingoutlets,HookingUptheContent
settingtarget-actionin,Settingtargetsandactions
sizeinspector,ViewsandFrames
interfacefiles
badconnectionsin,HookingUptheContent
Baseinternationalizationand,Baseinternationalization
connectingwithsourcefiles,Addingacamerabutton
makingconnectionsin,Addingacamerabutton
WOW! eBook www.wowebook.org
internalinconsistencyexception,AddingRowsinternationalization,Localization,FortheMoreCurious:NSBundle’sRoleinInternationalization
(seealsolocalization)
intrinsiccontentsize,Intrinsiccontentsizeinverserelationships,RelationshipsiOSsimulator
runningapplicationson,Runningonthesimulator
sandboxlocation,NSKeyedArchiverandNSKeyedUnarchiver
savingimagesto,Presentingtheimagepickermodally
viewingapplicationbundlein,FortheMoreCurious:TheApplicationBundle
iPad
(seealsodevices)
applicationiconsfor,ApplicationIcons
runningiPhoneapplicationson,CreatinganXcodeProject
isEmpty(strings),PropertiesisSourceTypeAvailable:,Settingtheimagepicker’ssourceTypeissuenavigator,BuildingtheFinishedApplication
J
JSONdata,JSONData
K
key-valuepairs
indictionaries,Collectiontypes
inJSONdata,JSONData
inwebservices,FormattingURLsandrequests
keyboard
attributes,Keyboardattributes
dismissing,Dismissingthekeyboard,DismissingtheKeyboard
numberpad,BronzeChallenge:DisplayingaNumberPad
keys
creating/using,CreatingandUsingKeys
indictionaries,Collectiontypes
WOW! eBook www.wowebook.org
L
labels
adding,ViewsandFrames
addingadditional,AnotherLabel
addingconstraintsto,Preparingforlocalization
addingtotabbar,Tabbaritems
customizing,Customizingthelabels
updatingpreferredtextsize,Respondingtouserchanges
languagesettings,Localization
(seealsolocalization)
launchimages,LaunchScreenlayers(ofviews),TheViewHierarchylayoutattributes,Alignmentrectangleandlayoutattributeslayoutguides,Layoutguides,SilverChallenge:LayoutGuideslazyloading,TheViewofaViewController,LoadedandAppearingViewslet,UsingStandardTypeslibraries
(seealsoframeworks)
object,Creatingviewobjects
Library/Cachesdirectory,ApplicationSandboxLibrary/Preferencesdirectory,ApplicationSandboxliteralvalues,LiteralsandsubscriptingloadView,TheViewofaViewController,CreatingaViewProgrammaticallylocalvariables,LoopsandStringInterpolationlocalization
Baseinternationalizationand,Baseinternationalization
internationalization,Localization,FortheMoreCurious:NSBundle’sRoleinInternationalization
lprojdirectories,Baseinternationalization,FortheMoreCurious:NSBundle’sRoleinInternationalization
NSBundle,FortheMoreCurious:NSBundle’sRoleinInternationalization
stringstables,NSLocalizedStringandstringstables
usersettingsfor,Localization
XLIFFdatatype,FortheMoreCurious:ImportingandExportingasXLIFF
locationInView:,MultipleGestureRecognizers
WOW! eBook www.wowebook.org
loops
examininginValueHistory,LoopsandStringInterpolation
for,LoopsandStringInterpolation
for-in,LoopsandStringInterpolation
inSwift,LoopsandStringInterpolation
low-memorywarnings,Savingtheimagelprojdirectories,Baseinternationalization,FortheMoreCurious:NSBundle’sRoleinInternationalization
M
mainbundle,Baseinternationalization,FortheMoreCurious:NSBundle’sRoleinInternationalization
(seealsoapplicationbundle)
maininterface,SettingtheInitialViewControllermainthread,TheMainThreadmainBundle,FortheMoreCurious:TheApplicationBundlemargins,Margins,Explicitconstraints//MARK:,//MARK:member-wiseinitializers,Structsmemorymanagement
memorywarnings,Savingtheimage
UITableViewCell,ReusingUITableViewCells
menus(UIMenuController),UIMenuController,FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActionsmessages
(seealsomethods)
action,FortheMoreCurious:UIControl,UIGestureRecognizerSubclasses,UIMenuController
log,Handlingmultipletouches
UIResponder,TouchEvents
methods
(seealsoindividualmethodnames)
action,Definingactionmethods,FortheMoreCurious:UIControl
datasource,Implementingdatasourcemethods
defined,TypesinSwift,Instancemethods
protocol,Moreonprotocols
static,TypesinSwift
WOW! eBook www.wowebook.org
minimumPressDuration,UILongPressGestureRecognizermodalviewcontroller,DisplayingUserAlerts,Presentingtheimagepickermodallymodellayer,Model-View-ControllerModel-View-Controller(MVC),Model-View-Controller,UITableViewController,DesignPatternsmulti-threading,TheMainThreadmultipleTouchEnabled(UIView),Handlingmultipletouchesmultitouch,enabling,HandlingmultipletouchesmutableSetValueForKey(_:),AddingTagstotheInterfaceMVC(Model-View-Controller),Model-View-Controller,UITableViewController,DesignPatterns
N
namingconventions
cellreuseidentifiers,ReusingUITableViewCells
delegateprotocols,Conformingtoaprotocol
navigationcontrollers(seeUINavigationController)navigationItem(UIViewController),UINavigationBarnavigators(Xcode)
defined,CreatinganXcodeProject
issue,BuildingtheFinishedApplication
project,CreatinganXcodeProject
nearestneighbor,ConstraintsnextResponder,FortheMoreCurious:TheResponderChainnil-targetedactions,FortheMoreCurious:UIControlNSBundle,FortheMoreCurious:NSBundle’sRoleinInternationalizationNSCoder,ArchivingNSCodingprotocol,ArchivingNSData,WritingtotheFilesystemwithNSDataNSDate,FortheMoreCurious:ReadingandWritingtotheFilesystemNSDateFormatter,FormattersNSFetchRequest,FetchrequestsandpredicatesNSIndexPath,CreatingandretrievingUITableViewCells,DeletingRowsNSJSONSerialization,NSJSONSerializationNSKeyedArchiver,NSKeyedArchiverandNSKeyedUnarchiverNSKeyedUnarchiver,LoadingfilesNSLocale,FormattersNSLocalizedString(),NSLocalizedStringandstringstablesNSManagedObject,NSManagedObjectandsubclasses,RelationshipsNSNumber,FortheMoreCurious:ReadingandWritingtotheFilesystemNSString
conversionto,Archiving
WOW! eBook www.wowebook.org
propertylistserializable,FortheMoreCurious:ReadingandWritingtotheFilesystem
NSTemporaryDirectory,ApplicationSandboxNSURL,NSURLComponentsNSURLRequest,SendingtheRequest,FortheMoreCurious:HTTPNSURLSession,SendingtheRequestNSURLSessionDataTask,NSURLSession,ParsingJSONdata,TheMainThreadNSURLSessionTask,FortheMoreCurious:HTTPNSUserDefaults,ApplicationSandboxNSUUID,CreatingandUsingKeysNSValueTransformer,Transformableattributesnumberformatters,Numberformatters,Formattersnumberpad,BronzeChallenge:DisplayingaNumberPad
O
objectgraphs,ObjectGraphsobjectlibrary,Creatingviewobjectsobjects
(seealsomemorymanagement)
propertylistserializable,FortheMoreCurious:ReadingandWritingtotheFilesystem
optional,Moreonprotocolsoptionalbinding,Optionalsoptionalmethods(protocols),Moreonprotocolsoptionals
about,Optionals
anddictionarysubscripting,Subscriptingdictionaries
forcedunwrappingof,Optionals
if-let,Optionals
andoptionalbinding,Optionals
syntaxfor,Optionals
unwrapping,Optionals
outlets
autogenerating/connecting,HookingUptheContent
connectingconstraintsto,AnimatingConstraints
connectingwithsourcefiles,ExposingthePropertiesofItemCell
defined,Makingconnections
setting,Makingconnections
WOW! eBook www.wowebook.org
settinginInterfaceBuilder,HookingUptheContent
P
padding,ContentInsetsparallelcomputing,TheMainThreadparent-childcontexts,Parent-ChildContextsPhotoramaapplication
addingpersistenceto,ObjectGraphs
addingtagstophotos,CoreDataRelationships
collectionview,CollectionViews
downloadingimagedata,DownloadingandDisplayingtheImageData
webservicerequests,StartingthePhotoramaApplication
photos(seecamera,images)pixels,ViewsandFramesplaygrounds(Xcode),UsingStandardTypes
errorsin,UsingStandardTypes
ValueHistory,LoopsandStringInterpolation
viewingconsolein,Optionals
pointers
inInterfaceBuilder(seeoutlets)
points(vs.pixels),ViewsandFramespredicates,Fetchrequestsandpredicatespreferences,ApplicationSandbox
(seealsoDynamicType,localization)
prepareForSegue:sender:,PassingDataAroundpresentViewController:animated:completion:,Presentingtheimagepickermodallypreviewassistant,Preparingforlocalizationprojectnavigator,CreatinganXcodeProjectprojects
cleaningandbuilding,Localization
creating,CreatinganXcodeProject
targetsettingsin,FortheMoreCurious:TheApplicationBundle
properties
creatinginInterfaceBuilder,ExposingthePropertiesofItemCell
defined,Properties
WOW! eBook www.wowebook.org
propertylistserializableobjects,FortheMoreCurious:ReadingandWritingtotheFilesystempropertyobserver,ImplementingtheTemperatureConversionprotocol,Conformingtoaprotocolprotocols
conformingto,Conformingtoaprotocol
declaring,Conformingtoaprotocol
delegate,Conformingtoaprotocol
NSCoding,Archiving
optionalmethodsin,Moreonprotocols
requiredmethodsin,Moreonprotocols
structureof,Conformingtoaprotocol
UIApplicationDelegate,ApplicationStatesandTransitions
UICollectionViewDataSourceprotocol,CollectionViewDataSource
UICollectionViewDelegate,DownloadingtheImageData
UIGestureRecognizerDelegate,UIPanGestureRecognizerandsimultaneousrecognizers
UIImagePickerControllerDelegate,Settingtheimagepicker’sdelegate,Savingtheimage
UINavigationControllerDelegate,Savingtheimage
UIResponderStandardEditActions,FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActions
UITableViewDataSource,UITableViewController,Implementingdatasourcemethods,CreatingandretrievingUITableViewCells,DeletingRows,MovingRows
UITableViewDelegate,UITableViewController
UITextFieldDelegate,Conformingtoaprotocol,DismissingtheKeyboard
pseudolanguage,Preparingforlocalization
Q
Quartz,FortheMoreCurious:RetinaDisplay(seeCoreGraphics)queryitems,FormattingURLsandrequestsQuickHelp,ArchivingQuickHelp(Xcode),InferringtypesQuizapplication,CreatinganXcodeProject
R
Range,LoopsandStringInterpolationrawValue(enums),Enumerationsandrawvalues
WOW! eBook www.wowebook.org
referencepages,Archivingreferencetypes,Valuetypesvs.referencetypesregionsettings,Localizationreorderingcontrols,MovingRowsrequiredmethods(protocols),MoreonprotocolsrequireGestureRecognizerToFail(_:),MoreonUIGestureRecognizerresignFirstResponder,Dismissingthekeyboard,DismissingbypressingtheReturnkeyresources
assetcatalogsfor,ApplicationIcons
defined,ApplicationIcons,FortheMoreCurious:TheApplicationBundle
responderchain,FortheMoreCurious:TheResponderChainresponders(seefirstresponder,UIResponder)Retinadisplay,ApplicationIcons,FortheMoreCurious:RetinaDisplayreuseidentifiers,DisplayingtheGridreuseIdentifier(UITableViewCell),ReusingUITableViewCellsreverse(),Instancemethodsrootviewcontroller(UINavigationController),UINavigationControllerrows(UITableView)
adding,AddingRows
deleting,DeletingRows
moving,MovingRows
S
sandbox,application,ApplicationSandbox,FortheMoreCurious:TheApplicationBundleschemes,Runningonthesimulatorsections(UITableView),Implementingdatasourcemethods,EditingModesegues,SeguessendAction(_:to:from:forEvent:),FortheMoreCurious:UIControlsendActionsForControlEvents(_:),FortheMoreCurious:UIControlsetEditing:animated:,EditingMode,Addingbuttonstothenavigationbarsets,Collectiontypes,Initializerssettings(seepreferences)Settingsapplication,ApplicationSandboxsimulator
runningapplicationson,Runningonthesimulator
sandboxlocation,NSKeyedArchiverandNSKeyedUnarchiver
savingimagesto,Presentingtheimagepickermodally
viewingapplicationbundlein,FortheMoreCurious:TheApplicationBundle
sizeclasses,SizeClassesWOW! eBook
www.wowebook.org
sortdescriptors(NSFetchRequest),FetchrequestsandpredicatessourceType(UIImagePickerController),TakingPicturesandUIImagePickerControllerstackviews,StackViewsstates,application,ApplicationStatesandTransitionsstaticmethods,TypesinSwiftString
internationalizing,NSLocalizedStringandstringstables
writingtofilesystem,WritingtotheFilesystemwithNSData
stringinterpolation,LoopsandStringInterpolationstrings
(seealsoNSString)
initializersfor,Initializers
interpolation,LoopsandStringInterpolation
isEmpty,Properties
literal,Literalsandsubscripting
stringstables,NSLocalizedStringandstringstablesstructs,Structssubscripting
arrays,Literalsandsubscripting
dictionaries,Subscriptingdictionaries
subviews,TheViewHierarchy,Accessingsubviewssuperview,ViewsandFramessuspendedstate,ApplicationStatesandTransitionsSwift
about,TheSwiftLanguage
documentationfor,ExploringApple’sSwiftDocumentation
enumerationsandswitchstatement,EnumerationsandtheSwitchStatement
extensionsin,Extensions
loopsandstringinterpolation,LoopsandStringInterpolation
optionaltypesin,Optionals,ErrorHandling
typesin,TypesinSwift
usingstandardtypes,UsingStandardTypes
valuetypes,Valuetypesvs.referencetypes
switch,EnumerationsandtheSwitchStatementswitchstatements,EnumerationsandtheSwitchStatement
WOW! eBook www.wowebook.org
T
tabbarcontrollers(seeUITabBarController)tabbaritems,Tabbaritemstableviewcells(seeUITableViewCell)tableviewcontrollers(seeUITableViewController)tableviews(seeUITableView)tables(database),EntitiestableView,ContentInsetstableView(_:commitEditingStyle:forRowAtIndexPath:),DeletingRowstableView(_:moveRowAtIndexPath:toIndexPath:),MovingRowstableView:cellForRowAtIndexPath:,Implementingdatasourcemethods,CreatingandretrievingUITableViewCellstableView:numberOfRowsInSection:,Implementingdatasourcemethodstags
addingtophotos,AddingTagstotheInterface
addingtotheinterface,AddingTagstotheInterface
creatingrelationshipsbetween,Relationships
target-actionpairs
defined,Definingactionmethods,DesignPatterns
settingprogrammatically,Addingbuttonstothenavigationbar
andUIControl,FortheMoreCurious:UIControl
andUIGestureRecognizer,UIGestureRecognizerSubclasses
targets,buildsettingsfor,FortheMoreCurious:TheApplicationBundletemplates(Xcode),StyleChoicestext
(seealsoAutoLayout)
aligning,TextEditing
compressionof,Contentcompressionresistancepriorities
customizingappearance,Customizingthelabels,TextEditing
dynamicstylingof,DynamicType
input,TextInputandDelegation
textFieldShouldReturn:,DismissingtheKeyboardthreads,TheMainThreadtimingfunctions,TimingFunctionstmpdirectory,ApplicationSandboxto-manyrelationships,Relationshipsto-onerelationships,Relationships
WOW! eBook www.wowebook.org
toggleEditingMode:,EditingModetoolbars
adding,Addingacamerabutton
addingbuttonsto,Addingacamerabutton
addingconstraintsto,Addingacamerabutton
anchoring,AddingTagstotheInterface
topViewController(UINavigationController),UINavigationControllertouchevents
basicsof,TouchEvents
defined,Eventhandlingbasics
enablingmultitouch,Handlingmultipletouches
andresponderchain,FortheMoreCurious:TheResponderChain
andtarget-actionpairs,FortheMoreCurious:UIControl
andUIControl,FortheMoreCurious:UIControl
touchesBegan(_:withEvent:),TouchEventstouchesCancelled(_:withEvent:),TouchEventstouchesEnded(_:withEvent:),TouchEventstouchesMoved(_:withEvent:),TouchEventsTouchTrackerapplication
creating,CreatingtheTouchTrackerApplication
drawinglines,CreatingtheLineStruct
recognizinggestures,UIGestureRecognizerandUIMenuController
transformableattributes(CoreData),TransformableattributestranslationInView(_:),UIPanGestureRecognizerandsimultaneousrecognizerstraps,Literalsandsubscriptingtuples,LoopsandStringInterpolationtypeinference,Inferringtypestypes
boolean,NumberandBooleantypes
floating-point,NumberandBooleantypes,Initializers
hashable,Collectiontypes
inferenceof,Inferringtypes
instancesof,Initializers
integer,NumberandBooleantypes
sets,Collectiontypes,InitializersWOW! eBook
www.wowebook.org
specifying,Specifyingtypes
tuples,LoopsandStringInterpolation
U
UIthread,TheMainThreadUIAlertController,DisplayingUserAlertsUIApplication
andevents,TouchEvents
andresponderchain,FortheMoreCurious:TheResponderChain,FortheMoreCurious:UIControl
UIApplicationDelegate,ApplicationStatesandTransitionsUIBarButtonItem,UINavigationBar,AddingacamerabuttonUICollectionViewCell,CreatingaCustomUICollectionViewCellUICollectionViewDataSourceprotocol,CollectionViewDataSourceUICollectionViewDelegateprotocol,DownloadingtheImageDataUICollectionViewFlowLayout,CollectionViewsUIColor,ViewsandFramesUIControl,FortheMoreCurious:UIControlUIControlEvent.TouchUpInside,FortheMoreCurious:UIControlUIControlEvents,ProgrammaticControlsUIGestureRecognizer
actionmessagesof,UIGestureRecognizerSubclasses,UILongPressGestureRecognizer
cancelsTouchesInView,UIPanGestureRecognizerandsimultaneousrecognizers
chainingrecognizers,MoreonUIGestureRecognizer
delayingtouches,MoreonUIGestureRecognizer
described,UIGestureRecognizerandUIMenuController
detectingtaps,DetectingTapswithUITapGestureRecognizer
enablingsimultaneousrecognizers,UIPanGestureRecognizerandsimultaneousrecognizers
implementingmultiple,MultipleGestureRecognizers,UIPanGestureRecognizerandsimultaneousrecognizers
interceptingtouchesfromview,UIGestureRecognizerSubclasses,UIPanGestureRecognizerandsimultaneousrecognizers
locationInView:,MultipleGestureRecognizers
longpress,UILongPressGestureRecognizer
panning,UILongPressGestureRecognizer,UIPanGestureRecognizerandsimultaneousrecognizers
WOW! eBook www.wowebook.org
state(property),UILongPressGestureRecognizer,UIPanGestureRecognizerandsimultaneousrecognizers,MoreonUIGestureRecognizer
subclasses,Dismissingthekeyboard,UIGestureRecognizerSubclasses,MoreonUIGestureRecognizer
subclassing,MoreonUIGestureRecognizer
translationInView(_:),UIPanGestureRecognizerandsimultaneousrecognizers
andUIRespondermethods,UIPanGestureRecognizerandsimultaneousrecognizers
UIGestureRecognizerDelegate,UIPanGestureRecognizerandsimultaneousrecognizersUIImage,WritingtotheFilesystemwithNSData
(seealsoimages,UIImageView)
UIImageJPEGRepresentation,WritingtotheFilesystemwithNSDataUIImagePickerController
instantiating,TakingPicturesandUIImagePickerController
presenting,Presentingtheimagepickermodally
UIImagePickerControllerDelegate,Settingtheimagepicker’sdelegate,SavingtheimageUIImageView,DisplayingImagesandUIImageViewUIInterpolatingMotionEffect,BasicAnimationsUIKit,ViewsandFramesUILongPressGestureRecognizer,UILongPressGestureRecognizerUIMenuController,UIMenuController,FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActionsUINavigationBar,UINavigationControllerUINavigationController
(seealsoviewcontrollers)
addingviewcontrollersto,AppearingandDisappearingViews
described,UINavigationController
instantiating,UINavigationController
managingviewcontrollerstack,UINavigationController
rootviewcontroller,UINavigationController
instoryboards,Segues
topViewController,UINavigationController
andUINavigationBar,UINavigationBar
view,UINavigationController
viewControllers,UINavigationController
viewWillAppear:,AppearingandDisappearingViews
WOW! eBook www.wowebook.org
viewWillDisappear:,AppearingandDisappearingViews
UINavigationControllerDelegate,SavingtheimageUINavigationItem,UINavigationBarUIPanGestureRecognizer,UILongPressGestureRecognizer,UIPanGestureRecognizerandsimultaneousrecognizersUIResponder
menuactions,FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActions
andresponderchain,FortheMoreCurious:TheResponderChain
andtouchevents,TouchEvents
UIResponderStandardEditActions(protocol),FortheMoreCurious:UIMenuControllerandUIResponderStandardEditActionsUIScrollView,ContentInsetsUIStackView,UsingUIStackViewUIStoryboardSegue,SeguesUITabBarController
implementing,UITabBarController
vs.UINavigationController,UINavigationController
view,UITabBarController
UITabBarItem,TabbaritemsUITableView,UITableViewandUITableViewController
(seealsoUITableViewCell,UITableViewController)
addingrowsto,AddingRows
deletingrowsfrom,DeletingRows
editingmodeof,EditingMode,SubclassingUITableViewCell,Addingbuttonstothenavigationbar
editingproperty,EditingMode
footerview,EditingMode
headerview,EditingMode
movingrowsin,MovingRows
populating,UITableView’sDataSource
sections,Implementingdatasourcemethods,EditingMode
view,SubclassingUITableViewController
UITableViewCell
cellstyles,UITableViewCells
contentView,UITableViewCells
WOW! eBook www.wowebook.org
editingstyles,DeletingRows
retrievinginstancesof,CreatingandretrievingUITableViewCells
reusinginstancesof,ReusingUITableViewCells
subclassing,SubclassingUITableViewCell
UITableViewCellStyle,UITableViewCells
UITableViewCellEditingStyleDelete,DeletingRowsUITableViewController
(seealsoUITableView)
addingrows,AddingRows
datasourcemethods,Implementingdatasourcemethods
dataSource,UITableView’sDataSource
deletingrows,DeletingRows
described,UITableViewController
editingproperty,EditingMode
movingrows,MovingRows
returningcells,CreatingandretrievingUITableViewCells
subclassing,SubclassingUITableViewController
tableView,ContentInsets
UITableViewDataSource(protocol),UITableViewController,Implementingdatasourcemethods,CreatingandretrievingUITableViewCells,DeletingRows,MovingRowsUITableViewDelegate,UITableViewControllerUITapGestureRecognizer,Dismissingthekeyboard,DetectingTapswithUITapGestureRecognizerUITextField
asfirstresponder,DismissingtheKeyboard,FortheMoreCurious:UIControl
andkeyboard,DismissingtheKeyboard
settingattributesof,BronzeChallenge:DisplayingaNumberPad
textediting,TextEditing
UITextFieldDelegate,Conformingtoaprotocol,DismissingtheKeyboardUIToolbar,UINavigationBar,AddingacamerabuttonUITouch,TouchEvents,TurningTouchesintoLines,HandlingmultipletouchesUIView
(seealsoUIViewController,views)
animationdocumentation,ControllingAnimations
defined,ViewsandtheViewHierarchy
WOW! eBook www.wowebook.org
frame,ViewsandFrames
instantiating,ViewsandFrames
superview,ViewsandFrames
UIViewController
(seealsoUIView,viewcontrollers)
loadView,TheViewofaViewController,CreatingaViewProgrammatically
navigationItem,UINavigationBar
tabBarItem,Tabbaritems
view,TheViewofaViewController,FortheMoreCurious:TheResponderChain
viewDidLoad,Accessingsubviews
viewWillAppear:,Accessingsubviews,Savingtheimage
UIWindow
purposeof,TheViewHierarchy
andresponderchain,FortheMoreCurious:TheResponderChain
unarchiveObjectWithFile(_:),LoadingfilesURLForResource(_:withExtension:),FortheMoreCurious:NSBundle’sRoleinInternationalizationURLs,FormattingURLsandrequests
(seealsoNSURL)
useralerts,displaying,DisplayingUserAlertsuserinterface
(seealsoAutoLayout,views)
drill-down,UINavigationController
keyboard,DismissingtheKeyboard
usersettings(seepreferences)UUIDs,CreatingandUsingKeys,NSManagedObjectandsubclasses
V
valuetypes,Valuetypesvs.referencetypesvar,UsingStandardTypesvariables,UsingStandardTypes
(seealsoinstancevariables,localvariables,pointers,properties)
view(UIViewController),TheViewofaViewControllerviewcontrollers
WOW! eBook www.wowebook.org
(seealsoUIViewController,views)
allowingaccesstoimagestore,GivingViewControllersAccesstotheImageStore
interactingwith,InteractingwithViewControllersandTheirViews
lazyloadingofviews,TheViewofaViewController,LoadedandAppearingViews
modal,DisplayingUserAlerts,Presentingtheimagepickermodally
navigatingbetween,Segues
presenting,UITabBarController
reloadingsubviews,Savingtheimage
root,UINavigationController
settinginitial,SettingtheInitialViewController
andviewhierarchy,TheViewofaViewController,CreatingaViewProgrammatically
viewhierarchy,TheViewHierarchy,CreatingaViewProgrammaticallyviewControllers(UINavigationController),UINavigationControllerviewDidLoad,ViewsandFrames,Accessingsubviewsviews
(seealsoAutoLayout,touchevents,UIView,viewcontrollers)
addingtowindow,TheViewHierarchy,CreatingaViewProgrammatically
animating,ControllingAnimations
appearing/disappearing,AppearingandDisappearingViews
contentcompressionresistancepriorities,Contentcompressionresistancepriorities
contenthuggingpriorities,Contenthuggingpriorities
creatingprogrammatically,CreatingaViewProgrammatically
defined,ViewsandtheViewHierarchy
drawingtoscreen,TheViewHierarchy
inhierarchy,TheViewHierarchy
layersand,TheViewHierarchy
lazyloadingof,TheViewofaViewController,LoadedandAppearingViews
misplaced,Misplacedviews
modalpresentationof,Presentingtheimagepickermodally
inModel-View-Controller,Model-View-Controller
removingfromstoryboard,ProgrammaticViews
rendering,TheViewHierarchy
WOW! eBook www.wowebook.org
resizing,DisplayingImagesandUIImageView
scroll,DisplayingtheGrid
sizeandpositionof,ViewsandFrames
stackview,StackViews,AnotherSizeClass
andsubviews,TheViewHierarchy
viewWillAppear:,Accessingsubviews,AppearingandDisappearingViews,SavingtheimageviewWillDisappear:,AppearingandDisappearingViews
W
webservices
andHTTPprotocol,FortheMoreCurious:HTTP
withJSONdata,JSONData
andNSURLSession,SendingtheRequest
overview,WebServices
requestingdatafrom,BuildingtheURL
wildcardAnyWidth/Heightlayout,SizeClassesworkspaces(Xcode),CreatinganXcodeProjectWorldTrotterapplication
addingtabbarcontroller,UITabBarController
configuring,CreatingaNewProject
implementingtemperatureconversion,ImplementingtheTemperatureConversion
interfacelayout,ViewsandFrames
localizing,Internationalization
multipleviewcontrollersfor,ViewControllers
programmaticviewsin,ProgrammaticViews
textinput,TextInputandDelegation
writeToURL(_:atomically:),WritingtotheFilesystemwithNSDatawriteToURL(_:atomically:encoding:error:),FortheMoreCurious:ReadingandWritingtotheFilesystem
X
.xcassets(assetcatalog),ApplicationIcons
.xcdatamodeld(datamodelfile),ModelingentitiesXcode
WOW! eBook www.wowebook.org
(seealsodebuggingtools,InterfaceBuilder,projects,iOSsimulator)
APIReference,Archiving
assetcatalogs,ApplicationIcons
assistanteditor,Addingacamerabutton
creatingprojectsin,CreatinganXcodeProject
documentation,ControllingAnimations
editorarea,InterfaceBuilder
fileinspector,Localization
issuenavigator,BuildingtheFinishedApplication
navigatorarea,CreatinganXcodeProject
navigators,CreatinganXcodeProject
objectlibrary,Creatingviewobjects
organizingmethodswith//MARK:,//MARK:
playgrounds,UsingStandardTypes
QuickHelp,Inferringtypes,Archiving
schemes,Runningonthesimulator
sourceeditorjumpbar,FortheMoreCurious:NavigatingImplementationFiles
versions,CreatinganXcodeProject
workspaces,CreatinganXcodeProject
XLIFFdatatype,FortheMoreCurious:ImportingandExportingasXLIFFXMLpropertylists,FortheMoreCurious:ReadingandWritingtotheFilesystem
WOW! eBook www.wowebook.org