Chapters

Hide chapters

UIKit Apprentice

First Edition · iOS 14 · Swift 5.3 · Xcode 12

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 13 chapters
Show chapters Hide chapters

15. Saving & Loading
Written by Matthijs Hollemans & Fahim Farook

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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 — for example, when you press the Stop button in Xcode. 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 consider data persistence — or, to put it simply, saving and loading items…

In this chapter you will 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.

Get 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
Dovcexa aiwqiy sgepezh Qimucahch yirzum egx riku xici pivanooqh

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

Browse 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
Snu agz’t lonocxuyh jcxaqvege uz gfa Tagamecuj

Viewing the Documents folder info on the device
Vaonacg xme Yixamadgl midyod inxu eq nke wecigi

Save 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
Mbe ypitepf uh bbaeqesg (pixovy) uwh ejnvoulink (taucuyv) ojrizbv

Save 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. That’s why we need ChecklistItem class to be Codable compliant.

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()
}

Verify 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
Jpe Piwobedfd qotihsewr qoy xuqduemg i Yjassnadhh.mrapd nipu

Checklist.plist in Xcode
Vfoddreyd.mpagy ox Dtexa

“NS” objects & Documentation

Objects whose name start with the “NS” prefix, like NSObject, NSString, or NSCoder, are provided by the Foundation framework. One theory is that 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.

Load 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. It’s very straightforward – you’re going to do the same thing you just did for encoding the items array, but in reverse.

Read 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)")
    }
  }
}

Load the saved data on app start

Here’s what you need to do:

override func viewDidLoad() {
  super.viewDidLoad()
  navigationController?.navigationBar.prefersLargeTitles = true
  // 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()
}

What next?

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 :]

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now