Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

15. Saving & Loading
Written by Eli Ganim

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

You now have full to-do item management functionality working for Checklists — you can add items, edit them, and even delete them. However, any new to-do items that you add to the list cease to exist when you terminate the app (by pressing the Stop button in Xcode, for example). And when you delete items from the list, they keep reappearing after a new launch. That’s not how a real app should behave!

So, it’s time to consdider data persistence — or, to put it simply, saving and loading items…

In this chapter you wil cover the following:

  • The need for data persistence: A quick look at why you need data persistence.
  • The documents folder: Determine where in the file system you can place the file that will store the to-do list items.
  • Save checklist items: Save the to-do items to a file whenever the user makes a change such as: add a new item, toggle a checkmark, delete an item, etc.
  • Load the file: Load the to-do items from the saved file when the app starts up again after termination.

The need for data persistence

Thanks to the multitasking nature of iOS, an app stays in memory when you close it and go back to the home screen or switch to another app. The app goes into a suspended state where it does absolutely nothing and yet, still hangs on to its data.

During normal usage, users will never truly terminate an app, just suspend it. However, the app can still be terminated when iOS runs out of available working memory, as iOS will terminate any suspended apps in order to free up memory when necessary. And if they really want to, users can kill apps by hand or restart/reset their entire device.

Just keeping the list of items in memory is not good enough because there is no guarantee that the app will remain in memory forever, whether active or suspended.

Instead, you will need to persist this data in a file on the device’s long-term flash storage. This is no different than saving a file from your word processor on your desktop computer, except that iOS apps should take care of this automatically.

The user shouldn’t have to press a Save button just to make sure unsaved data is safely placed in long-term storage.

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

So let’s get crackin’ on that data persistence functionality!

The documents folder

iOS apps live in a sheltered environment known as the sandbox. Each app has its own folder for storing files but cannot access the directories or files belonging to any other app.

Getting the save file path

Let’s look at how this works in code.

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

func dataFilePath() -> URL {
  return documentsDirectory().appendingPathComponent(
                                  "Checklists.plist")
}
override func viewDidLoad() {
  . . . 
  items.append(item5)
  // Add the following
  print("Documents folder is \(documentsDirectory())")
  print("Data file path is \(dataFilePath())")
}
Console output showing Documents folder and data file locations
Dibfuxu oonpat xdiyops Buvufinqx xuryum ihp zuwu qucu tacigiiyn

Documents folder is file:///var/mobile/Applications/FDD50B54-9383-4DCC-9C19-C3DEBC1A96FE/Documents

Data file path is file:///var/mobile/Applications/FDD50B54-9383-4DCC-9C19-C3DEBC1A96FE/Documents/Checklists.plist

Browsing the documents folder

For the rest of this app, run the app on the Simulator instead of a device. That makes it easier to look at the files you’ll be writing into the Documents folder. Because the Simulator stores the app’s files in a regular folder on your Mac, you can easily examine them using Finder.

The app’s directory structure in the Simulator
Fpi ivf’l vekujxepc xgxoqpefe om qpe Gupaqozoj

Viewing the Documents folder info on the device
Qeowafx rsa Nimugeqhb nehfur aflo en mga yicojo

Saving checklist items

In this section you are going to write code that saves the list of to-do items to a file named Checklists.plist when the user adds a new item or edits an existing item. Once you are able to save the items, you’ll add code to load this list again when the app starts up.

.plist files

So what is a .plist file?

The process of freezing (saving) and unfreezing (loading) objects
Bzu fdehozr it swaamost (jinexf) ihj ujkviewigj (fuojilc) ixzubzr

Saving data to a file

➤ Add the following method to ChecklistViewController.swift:

func saveChecklistItems() {
  // 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)")
  }
}

The Codable protocol

Swift arrays — as well as most other standard Swift objects and structures — conform to the Codable protocol. However, in the case of array, the objects contained in the array should also support Codable if you want to serialize the array. So is our ChecklistItem class Codable compliant? Probably not.

class ChecklistItem: NSObject, Codable {

Using the new method

You have to call the new saveChecklistItems() method whenever the list of items is modified.

func itemDetailViewController(
                 _ controller: ItemDetailViewController, 
         didFinishAdding item: ChecklistItem) {
  . . .
  saveChecklistItems()
}
func itemDetailViewController(
                 _ controller: ItemDetailViewController, 
        didFinishEditing item: ChecklistItem) {
  . . .
  saveChecklistItems()
}
override func tableView(
                  _ tableView: UITableView,
          commit editingStyle: UITableViewCellEditingStyle,
           forRowAt indexPath: IndexPath) {
  . . .
  saveChecklistItems()
}
override func tableView(_ tableView: UITableView,
           didSelectRowAt indexPath: IndexPath) {
  . . .
  saveChecklistItems()
}

Verifying the saved file

➤ Run the app now and do something that results in a save, such as tapping a row to flip the checkmark, or deleting/adding an item.

The Documents directory now contains a Checklists.plist file
Dve Wabayaxdp wozotcizf jud wemxoifx e Nnodvxihss.cquzt tizo

Checklist.plist in Xcode
Cboryxakl.mhoyc as Qrefo

“NS” objects

Objects whose name start with the “NS” prefix, like NSObject, NSString, or NSCoder, are provided by the Foundation framework. NS stands for NextStep, the operating system from the 1990’s that later became Mac OS X and which also forms the basis of iOS.

Loading the file

Saving is all well and good, but pretty useless by itself. So, let’s also implement the loading of the Checklists.plist file.

Reading data from a file

➤ Switch to ChecklistViewController.swift and add the following new method:

func loadChecklistItems() {
  // 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)
    } catch {
      print("Error decoding item array: \(error.localizedDescription)")
    }
  }
}

Loading the saved data on app start

Here’s what you need to do:

override func viewDidLoad() {
  super.viewDidLoad()
  // Load items
  loadChecklistItems()
}

Initializers

Methods named init are special in Swift. They are only used when you’re creating new objects, to make those new objects ready for use.

let item = ChecklistItem()
init() {
  // Put values into your instance variables and constants.
  
  super.init()

  // Other initialization code, such as calling methods, goes here.
}
var checked = false
var checked: Bool
init() {
  checked = false
  super.init()
}

Where to go from here?

Checklists is currently at a good spot — you have a major bit of functionality completed and there are no bugs. This is a good time to take a break, put your feet up, and daydream about all the cool apps you’ll soon be writing. It’s also smart to go back and repeat those parts you’re still a bit fuzzy about. Don’t rush through these chapters — there are no prizes for finishing first. Rather than going fast, take your time to truly understand what you’ve been doing.

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