Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Getting Started with SwiftUI

Section 1: 8 chapters
Show chapters Hide chapters

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

14. Saving and Loading
Written by Joey deVilla

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Checklist is now a fully CRUD app: The user can create, report on, update and delete checklist items. It would be a fully-functional basic checklist app if not for two issues. The first is that the app always starts with the five default items, some of which are already checked.

The second issue is that the app will “forget” any changes to the checklist if the app terminates for any reason, whether the user does it manually or if they restart the device.

It’s time to make some changes so that the app behaves in the following ways:

  • When the user launches Checklist for the first time, they’ll start with an empty checklist instead of the five default checklist items that the app’s had since the beginning.
  • The app will remember the state of its checklist after it terminates. When the user reopens the app, they’ll see the same checklist as the last time they used it. The contents of the checklist should persist over time.

In this chapter, you’ll cover the following topics:

  • Data persistence: In most cases, apps need to remember something from the last time you used them.
  • The Documents folder: Each app has its own place where it can store data. You’ll learn how to find this place and use it to store checklist items.
  • Saving checklist items: “Save early, save often,” the saying goes; you’ll set up Checklist so it does just that.
  • Loading checklist items: Now that the app saves checklist items, you’ll need to set it up so it loads the checklist when it launches.

Data persistence

Modern smartphone operating systems are technological wonders. While today’s desktop operating systems still slow to a crawl when running too many apps, both iOS and Android are so efficient at juggling apps that you never have to deliberately terminate an app.

When you switch from one app to another, the app that you switch from goes into a suspended state where it does absolutely nothing and yet still hangs on to its data. When you switch back to that app, it “remembers” the state it was in before you switched away from it and you can continue using it as if nothing ever happened.

However, it’s not a perfect world, and sometimes an app will terminate. There are still users who remember the early days of smartphones and close apps manually out of habit. Apps and operating systems can also crash, requiring a restart. And sometimes a device will run out of power before you can recharge it.

Since it isn’t a perfect world, you can’t rely on the app staying in memory and never terminating. Instead, you need to take advantage of the storage space on the user’s device to hold user data between sessions. It’s not just for cat pictures and videos!

This is no different from saving a file from your word processor on your desktop computer, except that users don’t have to press a “Save” button. Most users expect mobile apps to save their data continuously and automatically.

Saving data between app launches is called data persistence. You’ll add this feature to Checklist in this chapter.

Apps need to persist data just in case the app is terminated
Apps need to persist data just in case the app is terminated

If you were working on a traditional desktop app, implementing data persistence might take a fair bit of code. However, because you’re working with Swift and iOS, you’ll be pleasantly surprised how little code it takes.

It’s time to start working on that data persistence functionality! The first step is to figure out where you’ll store the data.

The Documents directory

Unlike desktop apps, which mostly have unfettered access to the computer’s hard drive, each iOS app goes into a sandbox when installed. This means that each app has its own slice of the device’s storage, which only that app can access.

Finding the Documents directory to save checklist data

➤ Add the following methods to Checklist.swift after the moveListItem(whichElement:destination:) method:

func documentsDirectory() -> URL {
  let paths = FileManager.default.urls(for: .documentDirectory, 
                                       in: .userDomainMask)
  return paths[0]
}

func dataFilePath() -> URL {
  return documentsDirectory().appendingPathComponent("Checklist.plist")
}
init() {
  print("Documents directory is: \(documentsDirectory())")
  print("Data file path is: \(dataFilePath())")
}
Console output showing Documents folder and data file locations
Nilyawo aagjed hbasemb Wobekubty yodmes ocn pupa ziko fetapuicx

Documents directory is: file:///var/mobile/Containers/Data/Application/5F4CB154-1CAD-4F54-8673-4ADCCEF98D78/Documents/
Data file path is: file:///var/mobile/Containers/Data/Application/5F4CB154-1CAD-4F54-8673-4ADCCEF98D78/Documents/Checklist.plist

Browsing the Documents directory

For the rest of this chapter, run the app on the simulator instead of a device. This will make it easier to look at the files you’ll write in the Documents folder. That’s because the simulator stores the app’s files in a regular folder on your Mac, which you can easily examine using the Finder.

The 'Go to the folder:' dialog box
Vmo 'Go pu gzi sovhac:' woapil qiq

The 'Go to the folder:' dialog box with the 'Documents' directory pasted in
Ljo 'Lo de cvu derwav:' xooyiz reg satl yha 'Geqilesgd' qesicxojb zecnin us

The app’s directory structure in the Simulator
Zpe urt’b penojbibp gjkiygihu iq mnu Jarojeles

The Documents folder on the device
Dbo Pequzepnn motluf ox gpi qigebe

Saving checklist items

For your next step, you’ll write the code that will save the list of to-do items. These items will save to a file named Checklist.plist, which you’ll find in the app’s Documents directory, whenever the user makes a change to the checklist’s contents. Once the app can save these items, you’ll add code to load the saved data when the app launches.

.plist files

You probably looked at the name of the checklist data file, Checklist.plist and wondered, “What’s a .plist file?”

The Codable protocol, encoding, and decoding

In the past few chapters, you’ve had so much new information thrown at you that you might have forgotten what a protocol is — at least in the Swift sense. A protocol is a set of properties and methods that an object promises to have to provide a certain feature.

Memory vs. plist
Cutitb jy. dyisz

Saving data to a file

Now that you have a method that determines where the app will write Checklist.plist, it’s time to write a method to save that file.

func saveListItems() {
  // 1
  let encoder = PropertyListEncoder()
  // 2
  do {
    // 3
    let data = try encoder.encode(items)
    // 4
    try data.write(to: dataFilePath(),
                   options: Data.WritingOptions.atomic)
    // 5
  } catch {
    // 6
    print("Error encoding item array: \(error.localizedDescription)")
  }
}
‘do’ and ‘catch blocks, illustrated’
‘be’ utk ‘wekmd lsabym, ukyoyjduhos’

A closer look at the Codable protocol

Swift arrays — as well as most other standard Swift objects and data types — already conform to the Codable protocol. This means that they have the built-in ability to save their data to and load their data from the file system.

struct ChecklistItem: Identifiable, Codable {

Putting saveListItems() to use

Now that you have the saveListItems() method, you need to be able to call it from the places in the code where the user can modify the list of items.

func deleteListItem(whichElement: IndexSet) {
  items.remove(atOffsets: whichElement)
  printChecklistContents()
  saveListItems()
}

func moveListItem(whichElement: IndexSet, destination: Int) {
  items.move(fromOffsets: whichElement, toOffset: destination)
  printChecklistContents()
  saveListItems()
}
Button(action: {
  var newChecklistItem = ChecklistItem(name: self.newItemName)
  self.checklist.items.append(newChecklistItem)
  self.checklist.printChecklistContents()
  self.checklist.saveListItems()
  self.presentationMode.wrappedValue.dismiss()
}) {
struct EditChecklistItemView: View {

  @Binding var checklistItem: ChecklistItem

  var body: some View {
    Form {
      TextField("Name", text: $checklistItem.name)
      Toggle("Completed", isOn: $checklistItem.isChecked)
    }
  }

}
.onAppear() {
  self.checklist.printChecklistContents()
}
.onAppear() {
  self.checklist.printChecklistContents()
  self.checklist.saveListItems()
}

Verifying the saved file

➤ Run the app now and do something that results in a save. This could be tapping a row to change an item’s name or checked status, rearranging the items, adding a new item or deleting an existing one.

The updated checklist
Bxa esdilag nporqdorb

The Documents directory now contains a Checklist.plist file
Kko Kowisuyht caricfall cov qurteulg e Nnurgjewz.cnodx xori

The plist file, as seen in Visual Studio Code
Fpe svibn dave, uk maev av Feduir Qvegai Baxu

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>id</key>
		<string>FA9D1B56-7BB9-4233-83AC-8761C9F7A19E</string>
		<key>isChecked</key>
		<false/>
		<key>name</key>
		<string>Walk the cat</string>
	</dict>
	<dict>
		<key>id</key>
		<string>3C397BFB-575E-4B84-912A-379F5CB1DD98</string>
		<key>isChecked</key>
		<true/>
		<key>name</key>
		<string>Learn iOS development</string>
	</dict>
	<dict>
		<key>id</key>
		<string>5279F8E8-F25D-46FB-85DD-B5DD7FF0B69F</string>
		<key>isChecked</key>
		<true/>
		<key>name</key>
		<string>Eat ice cream</string>
	</dict>
	<dict>
		<key>id</key>
		<string>23395DAA-F08C-468B-AED2-771534ADB308</string>
		<key>isChecked</key>
		<true/>
		<key>name</key>
		<string>Soccer practice</string>
	</dict>
</array>
</plist>
The plist file with the items closed, as seen in Xcode
Jse hluwc besa bavr sbo obibk globeh, er roud ey Qgipo

The plist file with the items closed, as seen in Xcode
Pde qzepp lowe pajf bnu ugefk nyikac, az ruuf iq Wbapo

Loading the file

Saving is all well and good, but it’s only half of what the app needs. It also needs to load the data from the Checklist.plist file.

Reading data from a file

➤ Open Checklist.swift and add the following new method, just after saveListItems():

func loadListItems() {
  // 1
  let path = dataFilePath()
  // 2
  if let data = try? Data(contentsOf: path) {
    // 3
    let decoder = PropertyListDecoder()
    do {
      // 4
      items = try decoder.decode([ChecklistItem].self,
                                 from: data)
      // 5
    } catch {
      print("Error decoding item array: \(error.localizedDescription)")
    }
  }
}

Putting loadListItems() to use

You now have the loadListItems() method, which restores the app’s data from Checklist.plist.

init() {
  print("Documents directory is: \(documentsDirectory())")
  print("Data file path is: \(dataFilePath())")
  loadListItems()
}
The updated checklist
Kdi elposix gnaxznecl

The updated checklist
Hxo eblavah pgaspcuqq

A closer look at initializers

Methods named init are special in Swift. You use them only when you create new struct or class instances, to make those new objects ready for use.

let checklist = Checklist()
struct ChecklistItem: Identifiable, Codable {

  let id = UUID()
  var name: String
  var isChecked: Bool = false

}
init() {
  print("Documents directory is: \(documentsDirectory())")
  print("Data file path is: \(dataFilePath())")
  loadListItems()
}
var checked = false
var checked: Bool
init() {
  checked = false
}

Removing the default checklist items

Since Checklist now remembers its checklist items between sessions, it no longer needs its default checklist items.

@Published var items = [
  ChecklistItem(name: "Walk the dog", isChecked: false),
  ChecklistItem(name: "Brush my teeth", isChecked: false),
  ChecklistItem(name: "Learn iOS development", isChecked: true),
  ChecklistItem(name: "Soccer practice", isChecked: false),
  ChecklistItem(name: "Eat ice cream", isChecked: true),
]
@Published var items: [ChecklistItem] = []
An empty checklist
Ik oclbx gjuynrokp

The checklist with a newly-created item
Kbo qlatgyomr xazv o bohlq-jreazux aqem

The saved checklist reappears when you restart the app
Vbo pevut jjodtretm houmxoord wzex cei gufquyp zti omz

Next steps

iOS should start to make sense by now. You’ve written an entire app from scratch! Alreaedy, you’ve touched on several advanced topics, and hopefully you were able to follow along. Kudos for sticking with it until the end!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now