Day 48 - 100 Days of Swift
Project 12 (part 1)
Day 48 is the first part of the twelfth project. It is another technique project and the technique this time is saving data to the disk. The API you look at for saving data today is UserDefaults
. You conform the Person
class to NSCoding
, and then use NSKeyedArchiver
and NSKeyedUnarchiver
as the middle man to get from a state that can be saved in UserDefaults
and one that can be used by the app. Finally, you save and load the data in places that are appropriate.
First, you make Person
conform to NSCoding
:
1
2
3
4
5
6
7
8
9
10
11
12
class Person: NSObject, NSCoding {
//... at the bottom of the class
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
image = aDecoder.decodeObject(forKey: "image") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(image, forKey: "image")
}
Then you add a method in ViewController
to get the people array into a savable format, and save it:
1
2
3
4
5
6
7
8
private func save() {
if let saveData =
try? NSKeyedArchiver.archivedData(withRootObject: people,
requiringSecureCoding: false) {
let defaults = UserDefaults.standard
defaults.set(saveData, forKey: "people")
}
}
And a corresponding method to get the data from the disk and back into a format that the app can use:
1
2
3
4
5
6
7
8
private func loadPeople() {
let defaults = UserDefaults.standard
guard let savedPeople = defaults.object(forKey: "people") as? Data else { return }
guard let decodedPeople =
try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(savedPeople) as? [Person] else { return }
people = decodedPeople
}
Then all you need to do is sprinkle those methods in wherever they make sense. For saving, that means any time after you change the data and for loading that means when this view loads:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// At the end of imagePickerController(_:didFinishPickingMediaWithInfo:)
people.append(person)
collectionView.reloadData()
save()
// In the deleteAction closure of presentCellOptionAlert
selfVC.people.remove(at: indexPath.row)
selfVC.collectionView.deleteItems(at: [indexPath])
selfVC.save()
// In the saveAction closure of renamePerson
person.name = newName
self?.collectionView.reloadItems(at: [indexPath])
self?.save()
// In viewDidLoad
loadPeople()
With that, the app works. It looks exactly the same as it did before, but now it actually saves the people that you add to the app:

You can find my version of this project at the end of day 48 on Github here.
Reflections
Adding persistence to the app is another big step towards making something actually useful. This is obviously a very simple implementation, and not really how we should be storing the app’s main data, but it works (assuming a relatively small number of people) so that is pretty cool. It was also a good introduction to NSCoding
and NSKeyedArchiver
, which I’ve never really used before.