0
1
00:00:00,840 --> 00:00:08,580
So previously, we got up to the point where we used and NSUserDefaults to save our data, and then we
1

2
00:00:08,580 --> 00:00:15,090
created a data model that represented each and every to-do list item so that we can associate each item
2

3
00:00:15,240 --> 00:00:20,830
with not only just a title, but also whether if they have been done or not done.
3

4
00:00:21,060 --> 00:00:27,300
So this way, every single time we reload the table view, it will always be up to correctly assign a checkmark
4

5
00:00:27,390 --> 00:00:31,120
or no checkmark to each and every to-do list item.
5

6
00:00:31,200 --> 00:00:40,140
Now previously, where our app fell down was when we tried to save data that is of a custom data type to our
6

7
00:00:40,200 --> 00:00:41,720
to our to-do list.
7

8
00:00:41,760 --> 00:00:47,940
And the reason we mention was because we're using NSUserDefaults which can only take standard data
8

9
00:00:47,970 --> 00:00:53,390
types, not any custom types or custom objects that we have created.
9

10
00:00:53,400 --> 00:01:00,540
So in this lesson, we're going to address this by using a different method for saving our data and persisting
10

11
00:01:00,540 --> 00:01:02,040
it on the device.
11

12
00:01:02,220 --> 00:01:07,290
So the first thing that we need to do is to delete this print statement inside our AppDelegate
12

13
00:01:07,290 --> 00:01:12,480
didFinishLaunchingWithOptions so that we don't get confused between our print statements. And then if we're
13

14
00:01:12,480 --> 00:01:15,460
going to go into our TodoListViewController
14

15
00:01:15,720 --> 00:01:19,810
and we're going to create a file path to the Documents folder,
15

16
00:01:19,920 --> 00:01:22,020
so just super.viewDidLoad,
16

17
00:01:22,050 --> 00:01:30,970
we're going to create a constant called dataFilePath and this is going to be set to equal FileManager,
17

18
00:01:31,360 --> 00:01:35,540
which is the object that provides an interface to the file system,
18

19
00:01:35,800 --> 00:01:41,380
and then we're going to use the default FileManager which is a shared FileManager object.
19

20
00:01:41,380 --> 00:01:48,580
So this, you should now recognize, is a singleton. And this singleton contains a whole bunch of URLs and they're
20

21
00:01:48,640 --> 00:01:52,270
organized by directory and domain mask.
21

22
00:01:52,270 --> 00:01:57,100
So the SearchPathDirectory we need to tap into is the documentDirectory.
22

23
00:01:57,100 --> 00:02:00,590
Now, be careful here, you're not looking for the documentationDirectory,
23

24
00:02:00,670 --> 00:02:02,140
that's something entirely different.
24

25
00:02:02,140 --> 00:02:04,370
We're looking for the documentDirectory.
25

26
00:02:04,690 --> 00:02:07,980
So have a scroll through what other things you can tap into.
26

27
00:02:08,230 --> 00:02:14,410
And once you're done, go ahead and enter .documentDirectory. And the location where we're going to
27

28
00:02:14,410 --> 00:02:18,040
look for it is inside the userDomainMask.
28

29
00:02:18,040 --> 00:02:23,890
So this is the user's home directory, the place where we're going to save their personal items associated
29

30
00:02:23,890 --> 00:02:25,460
with this current app.
30

31
00:02:25,540 --> 00:02:32,080
And because this is an array, we're going to grab the first item, and then we're going to print this
31

32
00:02:32,080 --> 00:02:32,980
dataFilePath.
32

33
00:02:33,010 --> 00:02:37,280
So let's go ahead and run our app and see what we get back.
33

34
00:02:37,330 --> 00:02:44,260
Now, if we have a look inside our debug console, we can see that we've got this optional printed which doesn't
34

35
00:02:44,260 --> 00:02:49,990
really matter because we haven't unwrapped the optional URL, but this is what we want.
35

36
00:02:50,020 --> 00:02:52,100
This is the file path that we need.
36

37
00:02:52,120 --> 00:02:55,180
So let's go into Finder and navigate to this location.
37

38
00:02:55,180 --> 00:03:02,200
So if you want to navigate up the directory, you can always use command up button and you can go up the
38

39
00:03:02,200 --> 00:03:06,500
hierarchy until you get to the root which is your Macintosh hard drive.
39

40
00:03:06,610 --> 00:03:13,360
Now, starting from here, we're going to go into Users, then my current user which is angelayu, and then
40

41
00:03:13,360 --> 00:03:19,630
we're going to go into Library, and we're going to go into Developer. And inside developer, we're going
41

42
00:03:19,630 --> 00:03:30,880
to go into CoreSimulator, and we're going to look for the device that is 8D49. So you can always click
42

43
00:03:30,880 --> 00:03:35,020
on Date Modified, so that it sorts by last open or last modified.
43

44
00:03:35,020 --> 00:03:42,630
So here we go, 8D49, and then inside data, inside Containers, inside another folder called Data,
44

45
00:03:42,640 --> 00:03:50,720
there's a folder called Application, and we finally get to our application which is this one 53A.
45

46
00:03:50,800 --> 00:03:57,880
And you can see here we've got a plist that represents our NSUserDefaults.
46

47
00:03:57,880 --> 00:04:04,870
Now, inside our app, instead of using NSUserDefaults and grabbing that singleton and saving our data
47

48
00:04:04,870 --> 00:04:08,770
to that address, we're going to create our very own p list file.
48

49
00:04:08,770 --> 00:04:15,370
So all we need to do is go to the part where we created the file path to our Document's directory, and
49

50
00:04:15,460 --> 00:04:21,710
then we just need to use the dot notation and tap into the appendingPathComponent method.
50

51
00:04:21,820 --> 00:04:27,760
And here, we can add a path component, you can call it anything you want, but it's good to name it something
51

52
00:04:27,760 --> 00:04:28,660
that makes sense.
52

53
00:04:28,690 --> 00:04:33,690
For example, in this case, maybe we'll call it items.plist.
53

54
00:04:33,700 --> 00:04:36,310
Now, if I run the app and chase down that file path,
54

55
00:04:39,130 --> 00:04:45,150
and you'll see that we haven't yet got the items on plist. We've merely created a path to this new
55

56
00:04:45,150 --> 00:04:47,430
plist that we are going to create right now.
56

57
00:04:47,680 --> 00:04:49,800
So let's head back into Xcode.
57

58
00:04:49,930 --> 00:04:57,130
So let's head back into Xcode and let's modify our code. So instead of using use a default, we're going
58

59
00:04:57,130 --> 00:05:00,680
to create our own plist at the location of our data file path.
59

60
00:05:00,700 --> 00:05:08,110
So the first thing that we have to change is this line where we set our itemArray for the key
60

61
00:05:08,110 --> 00:05:10,900
TodoListArray into our UserDefaults.
61

62
00:05:10,900 --> 00:05:17,320
And because our itemArray is an array of our custom Item object, then this line fails
62

63
00:05:17,500 --> 00:05:19,840
and this is when our app crashes.
63

64
00:05:19,840 --> 00:05:21,270
Now, instead of doing this,
64

65
00:05:21,280 --> 00:05:23,290
so I'm just going to delete that line,
65

66
00:05:23,320 --> 00:05:29,650
I'm going to now create something called an encoder and the encoder is going to be a new object of the
66

67
00:05:29,650 --> 00:05:34,990
type propertyListEncoder and I'm going to initialize it.
67

68
00:05:34,990 --> 00:05:40,050
Now, what I can do is I can say, let data = encoder
68

69
00:05:40,210 --> 00:05:46,060
so that's that property listing code that I just created which will encode our data namely our
69

70
00:05:46,060 --> 00:05:49,200
itemArray into a property list.
70

71
00:05:49,360 --> 00:05:55,150
So we're going to use our encoder and we're going to use the method encode, and the value that we're
71

72
00:05:55,150 --> 00:05:57,880
going to encode is our itemArray.
72

73
00:05:57,880 --> 00:06:03,970
So once I hit enter, you'll see a couple of problems, namely that the encode method can throw an error.
73

74
00:06:04,120 --> 00:06:05,860
So we know what to do.
74

75
00:06:05,920 --> 00:06:12,030
We, first, have to create a do block, and then we need a catch block.
75

76
00:06:12,450 --> 00:06:21,060
And finally, we need to mark this method with a try. And because we are inside a closure, i.e., the part
76

77
00:06:21,060 --> 00:06:28,020
that gets triggered once the user presses the Add Item button, then we have to mark all our properties
77

78
00:06:28,380 --> 00:06:30,000
with a self.
78

79
00:06:30,000 --> 00:06:37,220
Now, once we've encoded our data, the next step is to write the data to our dataFilePath,
79

80
00:06:37,290 --> 00:06:40,100
and this is also a method that can throw errors,
80

81
00:06:40,140 --> 00:06:41,780
so we have to mark it with a try also.
81

82
00:06:41,780 --> 00:06:45,050
So try data
82

83
00:06:45,180 --> 00:06:45,980
.right,
83

84
00:06:46,020 --> 00:06:52,550
so writing the data to a location and the location is, of course, going to be a dataFilePath.
84

85
00:06:52,770 --> 00:06:59,230
So in order for us to access it there, we have to put it out here as a global constant.
85

86
00:06:59,340 --> 00:07:06,920
And so now we can say dataFilePath. And inside the catch block, we're going to print "Error encoding
86

87
00:07:07,340 --> 00:07:13,660
item array" and we're going to print out the error that triggered it.
87

88
00:07:13,700 --> 00:07:18,210
And finally, we have to mark this with a self because we are, again, inside a closure.
88

89
00:07:18,230 --> 00:07:24,290
So the last thing we have to do before this is going to work is we have to go into our Item class and
89

90
00:07:24,290 --> 00:07:30,500
we have to mark our class as conforming to the protocols
90

91
00:07:30,520 --> 00:07:31,710
Encodable.
91

92
00:07:32,110 --> 00:07:40,660
And this means that the Item type is now able to encode itself into a plist or into a JSON. And for
92

93
00:07:40,660 --> 00:07:46,830
a class to be able to be Encodable, all of its properties must have standard data types.
93

94
00:07:46,870 --> 00:07:52,570
So things like strings, Booleans, arrays, dictionaries, they all work, but not if you have a custom class
94

95
00:07:52,600 --> 00:07:53,420
in here.
95

96
00:07:53,800 --> 00:07:56,230
So let's hit save and let's fix that.
96

97
00:07:56,230 --> 00:08:01,800
one last issue. And let's just comment out this line so that we can address it later.
97

98
00:08:01,810 --> 00:08:04,660
Now, if we run app, let's see what happens.
98

99
00:08:04,660 --> 00:08:07,620
All right, so let's add a brand-new item.
99

100
00:08:07,810 --> 00:08:14,560
So all three of these items got initialized because we hardcoded them and added them to the itemArray
100

101
00:08:14,890 --> 00:08:17,860
which is what we're loading up inside our tableView.
101

102
00:08:17,980 --> 00:08:24,580
Now, in order to test out our code and see if our encoding worked, we have to press the add button and
102

103
00:08:24,580 --> 00:08:28,670
we're going to create a new item.
103

104
00:08:28,870 --> 00:08:34,990
And once I hit add item, you can see that I've got no errors, my debug console didn't pop up telling me that
104

105
00:08:34,990 --> 00:08:38,020
my app has crashed, and my app is continuing to work.
105

106
00:08:38,110 --> 00:08:41,550
and it now contains that brand-new list item.
106

107
00:08:41,890 --> 00:08:47,270
So if we go back to Finder and check out that previous location that we found,
107

108
00:08:47,440 --> 00:08:51,730
but remember last time when we checked in here, there was nothing inside, it was completely empty?
108

109
00:08:51,910 --> 00:08:54,130
Well, now that we've actually saved something.,
109

110
00:08:54,250 --> 00:09:00,700
but now that we've actually uncoded itemArray and wrote that data to that dataFilePath, we now have
110

111
00:09:00,790 --> 00:09:01,910
this plist.
111

112
00:09:02,080 --> 00:09:07,660
And if we have a look at it by double clicking, you can see that we've got a whole bunch of items that
112

113
00:09:07,660 --> 00:09:12,380
have been organized and encoded automatically using our Swift encoder.
113

114
00:09:12,400 --> 00:09:17,470
Now, it's important to note at this point that, previously, when we were talking about UserDefaults and
114

115
00:09:17,470 --> 00:09:25,720
I explained why it is that we're unable to save an array of custom item objects, it's because the plist
115

116
00:09:25,990 --> 00:09:31,630
can only accept a limited number of data types, and that pretty much the standard or foundation types.
116

117
00:09:31,630 --> 00:09:39,610
But if you take a look over here at our encoded Items.plist, you can see that, firstly, it's an Array
117

118
00:09:39,700 --> 00:09:46,360
with four items, whereas UserDefaults is a dictionary or a bunch of key-value pairs.
118

119
00:09:46,390 --> 00:09:48,370
And that's what we always said about UseDefaults.
119

120
00:09:48,430 --> 00:09:56,620
But if we expand this route, we can see that our encoder has very cleverly encoded each of the properties
120

121
00:09:56,860 --> 00:10:03,410
into a dictionary, and the individual properties still conform to all of the basic data types.
121

122
00:10:03,430 --> 00:10:11,490
And this is why even though that both plists are Items.plist is able to save our custom item objects,
122

123
00:10:11,510 --> 00:10:16,220
but the NSUserDefaults is not, and it's all down to the encoder.
123

124
00:10:16,480 --> 00:10:26,770
And if I have this live next to my app, if I add another new item-- That's really really imaginative of
124

125
00:10:26,770 --> 00:10:29,810
me. You can see that it shows up here,
125

126
00:10:29,830 --> 00:10:32,500
but it also shows up here, right?
126

127
00:10:32,620 --> 00:10:38,820
But this one bug, when we tried to check our items off or uncheck it, this done
127

128
00:10:38,830 --> 00:10:43,040
property is not updated in our Items.plist.
128

129
00:10:43,060 --> 00:10:45,580
So let's have a look at why that might be.
129

130
00:10:45,960 --> 00:10:51,340
So I'm going to stop the app and we're going to look at the part where we try to save that property.
130

131
00:10:51,430 --> 00:10:54,950
So you can see here, this is where we've got that Delegate Method,
131

132
00:10:55,020 --> 00:10:56,830
didSelectRowAt indexPath.
132

133
00:10:57,040 --> 00:11:02,580
And inside this method, we toggle the item at the current index path,
133

134
00:11:02,590 --> 00:11:06,310
it's done property to the opposite of what it used to be.
134

135
00:11:06,310 --> 00:11:10,480
So this is only reflected inside our item of range.
135

136
00:11:10,540 --> 00:11:15,090
So how can we save this information to itemArray.
136

137
00:11:15,340 --> 00:11:23,410
Well, we have to use the same process, i.e., let encoder = PropertyListEncoder to encode the data,
137

138
00:11:23,740 --> 00:11:27,160
and try and write the data to that dataFilePath,
138

139
00:11:27,280 --> 00:11:31,440
and if there's an error, print an error, and then reload the data.
139

140
00:11:31,790 --> 00:11:36,290
Now, instead of copying all of this and pasting it over there,
140

141
00:11:36,430 --> 00:11:40,600
this is a good time to think about creating a save data method.
141

142
00:11:40,660 --> 00:11:47,030
So let's go ahead and create a new function code saveItems underneath our IBAction.
142

143
00:11:47,050 --> 00:11:52,190
And inside this function, we're going to copy all of this code.
143

144
00:11:52,240 --> 00:11:53,090
We're going to cut it
144

145
00:11:53,120 --> 00:11:55,470
and we're going to paste it inside save items.
145

146
00:11:55,470 --> 00:12:02,410
So, now because we're no longer inside a closure, we can get rid of these selfs in front of our itemArray
146

147
00:12:02,410 --> 00:12:12,310
and dataFilePath, and we can also simply call saveItems, both when we add a new item using
147

148
00:12:12,310 --> 00:12:16,820
the AlertController, but also when we toggle the checkmark.
148

149
00:12:19,540 --> 00:12:23,470
And we can delete this as well because it exists inside saveItems.
149

150
00:12:23,570 --> 00:12:27,880
So, now let's hit run and see if our app works.
150

151
00:12:27,890 --> 00:12:34,490
And now if we check on the first cell which is "Find Mike," you can see that this automatically updates
151

152
00:12:34,580 --> 00:12:39,430
inside our Items.plist and all the changes are reflected live.
152

153
00:12:39,620 --> 00:12:45,860
But there's still a bit of an issue here, namely, where are the items that we saved earlier on? Where's the "Save
153

154
00:12:45,860 --> 00:12:49,560
the World" item? Where's the "Add a New Item" item?
154

155
00:12:49,610 --> 00:12:51,990
Why is it not showing up in our to-do list?
155

156
00:12:52,070 --> 00:12:57,110
Well, that's because even though we've written the saving part of the code which encodes our itemArray,
156

157
00:12:57,320 --> 00:13:04,090
we haven't yet written the code to decode our items.plist in order to populate our table view.
157

158
00:13:04,360 --> 00:13:06,180
So that, we're going to tackle on the next lesson.
