0
1
00:00:00,560 --> 00:00:07,790
So our app is looking pretty nice, but up till now, there's always been a really serious bug that we haven't
1

2
00:00:07,790 --> 00:00:10,220
yet discovered in our app.
2

3
00:00:10,220 --> 00:00:15,590
Now, if you've been playing around with the app quite a bit, you might have discovered this bug already.
3

4
00:00:15,590 --> 00:00:18,090
But if you haven't, let me show you what it is.
4

5
00:00:18,260 --> 00:00:25,030
Now, I'm going to add a whole bunch of new items into our itemArray, so that when I run my app, I'm going
5

6
00:00:25,030 --> 00:00:27,750
to have more than three cells.
6

7
00:00:27,800 --> 00:00:33,110
In fact, I'm going to have more cells than the table view can possibly display.
7

8
00:00:33,110 --> 00:00:37,370
So when I scroll, you can see that even though the table view is at "j,"
8

9
00:00:37,430 --> 00:00:40,250
if I keep going, it goes all the way down to "o," right?
9

10
00:00:40,820 --> 00:00:48,680
So, now if I click on the first cell and it checks the first cell, and I scroll down to the bottom,
10

11
00:00:48,680 --> 00:00:55,460
you can see that for some bizarre reason, our cell that's called "n" is also checked.
11

12
00:00:55,460 --> 00:01:01,360
And if I uncheck it and I scroll back, my first cell is no longer checked.
12

13
00:01:01,370 --> 00:01:07,520
So what on earth is going on here and what is happening with all this strange behavior?
13

14
00:01:08,120 --> 00:01:15,290
Well, the answer to this lies in the fact that table view cells
14

15
00:01:15,290 --> 00:01:24,880
gets reused, and that's what this line means when we're calling dequeueReusableCell,
15

16
00:01:24,950 --> 00:01:25,670
right?
16

17
00:01:25,790 --> 00:01:32,780
We're saying that go and find that prototype cell code TodoItemsCell inside Main.storyboard and
17

18
00:01:32,780 --> 00:01:36,890
generate a whole bunch of it that we can reuse.
18

19
00:01:36,950 --> 00:01:44,810
So that means when the first cell gets rolled off the table view and it's no longer visible, it actually
19

20
00:01:45,260 --> 00:01:52,990
goes all the way around and comes back underneath as a new reusable cell.
20

21
00:01:53,000 --> 00:01:55,540
So how can we prove that this is true?
21

22
00:01:55,550 --> 00:02:01,960
Do you remember the last time when I asked you as a challenge, why is it that we need this
22

23
00:02:01,970 --> 00:02:11,360
tableView.dequeueReusableCell method, instead of something like let cell equals a new object of type UITableViewCell.
23

24
00:02:11,360 --> 00:02:20,900
and we say the style is, let's say, default, reuseIdentifier is the same as we've got here, so that reusable
24

25
00:02:20,900 --> 00:02:25,580
prototype cell that we had. I'm just going to comment that out and let's run it.
25

26
00:02:25,580 --> 00:02:35,640
Now, if I check the first object and I scroll my table view until my cell disappears off the screen and
26

27
00:02:35,660 --> 00:02:43,200
I pull it back, you'll see that that checkmark has disappeared, and that's because the checking or
27

28
00:02:43,200 --> 00:02:51,470
unchecking, I'm setting a property on the cell. And once the cell disappears off the screen, it gets deallocated
28

29
00:02:51,500 --> 00:02:52,810
and destroyed.
29

30
00:02:52,880 --> 00:02:58,670
And when I scroll it back, I'm actually getting a brand-new cell that's getting this message put in.
30

31
00:02:58,820 --> 00:03:08,300
Now, on the other hand, say, if I use dequeueReusableCell, then in this case, when I check the first cell, I scroll
31

32
00:03:08,300 --> 00:03:16,100
it up and at the point when that cell disappears off the screen, it comes around the table view and gets
32

33
00:03:16,160 --> 00:03:25,790
reinitialized at the bottom as a new table view cell. And because it's being reused, that property that
33

34
00:03:25,790 --> 00:03:32,000
checked accessory is still present here. And that's why we're getting this rather odd behaviors because
34

35
00:03:32,000 --> 00:03:34,850
we're reusing our cells.
35

36
00:03:34,910 --> 00:03:41,720
So we found the problem and the problem is that we need to associate a property, not with the cell, but
36

37
00:03:41,720 --> 00:03:43,280
with our data.
37

38
00:03:43,610 --> 00:03:50,840
So in order for us to assign a checkmark or no checkmark to each of these items, we need a way to be
38

39
00:03:50,840 --> 00:03:56,510
able to associate each item with a property checked or unchecked.
39

40
00:03:56,510 --> 00:03:58,470
Now, how can we do that?
40

41
00:03:58,520 --> 00:04:01,970
We certainly can't do it using just a plain old array.
41

42
00:04:01,970 --> 00:04:11,150
But if you pause the video and have a think about how you can associate a property with each of these
42

43
00:04:11,210 --> 00:04:19,740
items, and see if you can come up with the solution.
43

44
00:04:19,980 --> 00:04:20,340
All right.
44

45
00:04:20,350 --> 00:04:25,630
So some of the solutions that you guys might have thought of were maybe instead of using an array, it's
45

46
00:04:25,630 --> 00:04:34,480
maybe to use a dictionary where you can have key-value pairs, or at a much better solution, would be creating
46

47
00:04:34,690 --> 00:04:35,680
a model,
47

48
00:04:35,680 --> 00:04:42,490
and we've spoken about MVC in the past when we did Quizzler. And now this is another scenario where you
48

49
00:04:42,490 --> 00:04:51,820
start to see the beauty and the reasons why we need to start separating our models, our views, and our controllers.
49

50
00:04:51,820 --> 00:04:58,900
So let's go ahead and build a data model where each of our items are objects that can have properties
50

51
00:04:58,930 --> 00:05:05,380
including the title of the item, and also a done or not done Boolean.
51

52
00:05:05,380 --> 00:05:13,060
So this is a good time point to pause the video and build a new class called item which we can use to
52

53
00:05:13,060 --> 00:05:20,740
store two properties, one property is a string called title which is where we're going to save the titles
53

54
00:05:20,860 --> 00:05:27,310
of our to-dos, and the other one is called done which is a Boolean property where we're going to save
54

55
00:05:27,490 --> 00:05:30,310
whether if the item was checked off or not.
55

56
00:05:30,310 --> 00:05:35,190
So pause the video and give that a go.
56

57
00:05:35,290 --> 00:05:35,770
All right.
57

58
00:05:35,770 --> 00:05:43,120
So let's go over to File, New File, and let's create a brand-new Swift File.
58

59
00:05:43,120 --> 00:05:51,130
Next, we're gonna call our file Item and we're gonna put our Item into its own folder,
59

60
00:05:51,160 --> 00:06:02,230
so New Group from Selection. And the folder is going to be called Data Model. And we'll make a New Group
60

61
00:06:02,770 --> 00:06:08,380
with our TodoListViewController and we'll call it Controllers, and we'll also while we're at it,
61

62
00:06:08,380 --> 00:06:13,660
might as well put Main.storyboard into a Views folder.
62

63
00:06:13,660 --> 00:06:14,290
All right.
63

64
00:06:14,290 --> 00:06:21,880
And finally, I'm just gonna put the Assets and the LaunchScreen together as a group, and I'll call it
64

65
00:06:22,240 --> 00:06:30,620
Supporting Files. That just gets rid of it and declutters and makes our structure a little bit more clear.
65

66
00:06:30,640 --> 00:06:38,470
All right, let's head into Item and we're going to create a new class that's called Item.
66

67
00:06:38,470 --> 00:06:45,640
Now, of course, you don't actually have to name your class the same as the file name, 
67

68
00:06:45,640 --> 00:06:48,150
but in 99.999 recurring cases,
68

69
00:06:48,340 --> 00:06:53,600
everybody does this, and it will be very, very odd if these two are not the same,
69

70
00:06:53,620 --> 00:06:54,790
and it's very confusing.
70

71
00:06:54,790 --> 00:06:58,290
So try to keep these two exactly the same.
71

72
00:06:58,330 --> 00:06:58,550
Okay.
72

73
00:06:58,590 --> 00:07:05,760
So we'll have a property called title and this is going to be a string.
73

74
00:07:05,940 --> 00:07:11,690
And by default, we'll say it's just an empty string. And then we're gonna create another property called
74

75
00:07:11,800 --> 00:07:12,640
done
75

76
00:07:12,880 --> 00:07:22,260
and this is going to be a Boolean. And by default, all items will start off as being not done,
76

77
00:07:22,260 --> 00:07:24,780
so we'll set that as false.
77

78
00:07:24,870 --> 00:07:31,500
So let's go ahead and hit save and go back to our TodoListViewController, and we can start creating
78

79
00:07:31,500 --> 00:07:32,860
some of these items.
79

80
00:07:32,880 --> 00:07:42,810
So I'm just going to common out this line and I'm going to create a new item and I'm going to use our
80

81
00:07:42,900 --> 00:07:45,000
Item class to do that.
81

82
00:07:45,360 --> 00:07:55,170
And our new Item is a new object of the type item and newItem.title is going to be equal to this.
82

83
00:07:56,190 --> 00:08:02,240
And instead of having an array of hardcoded string objects, I'm going to turn this itemArray into an
83

84
00:08:02,240 --> 00:08:04,690
array of Item objects.
84

85
00:08:04,890 --> 00:08:10,580
So none of what we've done so far should be new to you because we've done all of this and more inside
85

86
00:08:10,580 --> 00:08:11,110
Quizzler.
86

87
00:08:11,420 --> 00:08:17,600
So if you're at all confused about MVC or models-views-controllers, then be sure to have a look back
87

88
00:08:17,600 --> 00:08:21,320
at the Quizzler module where we go into this in much more detail.
88

89
00:08:21,320 --> 00:08:30,950
So, now we can say itemArray.append and we're going to pend the newElement which is called
89

90
00:08:30,980 --> 00:08:31,520
newItem.
90

91
00:08:31,790 --> 00:08:38,210
So I'm just gonna go and copy and paste this for times' sake and I'm going to amend the code so that
91

92
00:08:38,210 --> 00:08:43,830
we end up with newItem2 and newItem3. In addition to that, in order for our code to work,
92

93
00:08:43,850 --> 00:08:50,630
we need to fix it in a couple of places because we're changing from an array of strings to an array
93

94
00:08:50,720 --> 00:08:52,430
of item objects.
94

95
00:08:52,430 --> 00:08:58,550
So if we go down here, itemArray.count is still valid because we have an array that has this property
95

96
00:08:58,550 --> 00:08:59,570
.count.
96

97
00:08:59,570 --> 00:09:02,750
It doesn't matter if it's an array of strings or array of objects.
97

98
00:09:02,750 --> 00:09:08,840
But this part, where we set the cell.textLabel.text property, we need to change because itemArray
98

99
00:09:08,840 --> 00:09:13,190
at indexPath.row is going to return an item object.
99

100
00:09:13,190 --> 00:09:19,070
And what we need to do is we actually need to tap into its title property using the dot notation and
100

101
00:09:19,070 --> 00:09:22,890
we also need to change the code where we're adding new items.
101

102
00:09:22,940 --> 00:09:29,660
So here where we're saying self.itemArray.append(textField.text!) That doesn't really work.
102

103
00:09:29,660 --> 00:09:38,180
Instead we have to create a newItem that is of the class Item, and we're going to say newItem.title
103

104
00:09:38,690 --> 00:09:49,820
= textField.text, and then we can say self.itemArray. append, and the thing that we append is
104

105
00:09:49,820 --> 00:09:52,640
now going to be our newItem.
105

106
00:09:52,700 --> 00:09:55,920
And now if we hit run, let's see what happens.
106

107
00:09:55,940 --> 00:10:03,140
So our app is now working using item objects, instead of a hardcoded array, but we still haven't fixed
107

108
00:10:03,230 --> 00:10:07,670
our checkmark problem because we haven't yet changed the done property
108

109
00:10:07,670 --> 00:10:13,960
when we toggle the checkmark. Also, we've not updated the code for our add button yet.
109

110
00:10:14,000 --> 00:10:17,650
In fact, if you click on the add button now, the Apple crash.
110

111
00:10:17,840 --> 00:10:23,240
But let's do one thing at a time, let's fix the done property first, and we'll tackle the add button in
111

112
00:10:23,240 --> 00:10:25,090
one of the upcoming lessons.
112

113
00:10:25,160 --> 00:10:28,400
So inside didSelectRowAt indexPath,
113

114
00:10:28,400 --> 00:10:36,320
let's set the property of the selected item. So we can tap into itemArray and we can tap into the item
114

115
00:10:36,410 --> 00:10:37,790
at indexPath.row,
115

116
00:10:37,790 --> 00:10:45,590
i.e., the current item object that's selected, and we can set the done property to equal the opposite
116

117
00:10:45,950 --> 00:10:47,440
of what it used to be.
117

118
00:10:47,450 --> 00:10:52,670
So, if it used to be false, then we want to change it to true, and if it's used be true, then we want to change
118

119
00:10:52,670 --> 00:10:53,740
it to false.
119

120
00:10:53,780 --> 00:11:03,440
Now, the long way of doing this is by saying if itemArray [indexPath.row].done = false, then
120

121
00:11:03,440 --> 00:11:11,860
in that case, we want to set the itemArray [indexPath.row].done to true.
121

122
00:11:12,080 --> 00:11:23,180
Otherwise, we're going to say itemArray [indexPath.row].done = false, and that's what's
122

123
00:11:23,210 --> 00:11:24,340
at the toggling, right?
123

124
00:11:24,770 --> 00:11:30,530
And we can add a bit of code to our selfForRowAt indexPath which if you remember is the delegate method
124

125
00:11:30,530 --> 00:11:39,410
that figures out how we should display each of the cells. And we can say if itemArray[indexPath.row]
125

126
00:11:39,830 --> 00:11:51,950
.done = true, then in that case, we're going to set the cell.accessoryType
126

127
00:11:52,280 --> 00:11:52,950
= checkmark,
127

128
00:11:53,330 --> 00:12:00,030
and otherwise, we're going to set the cell.accessoryType = .none.
128

129
00:12:00,080 --> 00:12:03,870
So that means we can get rid of these three lines of code.
129

130
00:12:03,890 --> 00:12:10,250
Now, our code is incredibly wordy at the moment, but it's good to have it like this when we can test it
130

131
00:12:10,310 --> 00:12:16,130
and see if it works before we start refactoring and making things look a bit simpler and cutting down
131

132
00:12:16,190 --> 00:12:17,490
our lines of code.
132

133
00:12:17,510 --> 00:12:25,010
So, now when we select our cells, you can see that we've actually broken our app because while it's selecting
133

134
00:12:25,010 --> 00:12:29,800
and deselecting, it's not changing the checkmark one bit.
134

135
00:12:29,810 --> 00:12:32,840
Now, why might that be?
135

136
00:12:32,840 --> 00:12:39,320
The reason is because cellForRowAt indexPath is called initially when the table view gets loaded up.
136

137
00:12:39,440 --> 00:12:43,520
And at that time point, none of our items are done,
137

138
00:12:43,520 --> 00:12:46,120
so none of them get an accessory mark.
138

139
00:12:46,320 --> 00:12:54,380
So if we change one of these, let's make the first one, for example, newItem.done = true,
139

140
00:12:54,380 --> 00:13:01,430
then you can run your app and confirm to yourself that your code is actually working, this part,
140

141
00:13:01,520 --> 00:13:06,770
or we say, if itemArray[indexPath.row].done, then make the checkmark.
141

142
00:13:06,890 --> 00:13:11,510
It's working because you can see when we first load up our app, you see that checkmark there.
142

143
00:13:12,050 --> 00:13:20,720
So the problem that we have to solve then is how do we get this delegate method to trigger again once
143

144
00:13:20,720 --> 00:13:23,710
we've changed the items done property.
144

145
00:13:23,750 --> 00:13:31,400
So the magical method here is tableView.reloadData. And we've seen this before and we've used
145

146
00:13:31,400 --> 00:13:33,110
it before in FlashChat.
146

147
00:13:33,320 --> 00:13:40,880
But what it basically does is that it forces the table view to call its Datasource Methods again, so
147

148
00:13:40,880 --> 00:13:44,330
that it reloads the data that's meant to be inside.
148

149
00:13:44,540 --> 00:13:54,770
So if we go ahead and put a print statement here, "cellForRowAt indexPath Called," you can see that
149

150
00:13:54,770 --> 00:14:02,330
it got called three times iterating through our array of items when the table view first loaded up.
150

151
00:14:02,480 --> 00:14:09,560
And now when I select one of these cells and I trigger the didSelectRow delegate method and we've
151

152
00:14:09,560 --> 00:14:16,350
set our done properties and we get to this line where it says tableView.reloadData, then we're
152

153
00:14:16,350 --> 00:14:18,590
going to see it called a couple more times.
153

154
00:14:18,830 --> 00:14:24,840
So watch as I select one of these and you can see that we've got three more printouts of
154

155
00:14:24,860 --> 00:14:30,560
cellForRowAt indexPath because the table view is refreshed and it's called that delegate method cellForRowAt indexPath
155

156
00:14:30,830 --> 00:14:38,500
three more times for every single item we have in our array. And now our checkmarks also work,
156

157
00:14:38,600 --> 00:14:39,760
right?
157

158
00:14:39,770 --> 00:14:40,130
Brilliant.
158

159
00:14:40,400 --> 00:14:46,380
So, now let's check and make sure that we've solved that bug that we had earlier on.
159

160
00:14:46,400 --> 00:14:53,510
So if we go underneath the three items that we carefully initialized and appended, I'm going to paste
160

161
00:14:53,510 --> 00:15:00,560
in just a whole bunch of appends of our newItem3 to the end of our itemArray, and that just adds
161

162
00:15:00,620 --> 00:15:05,090
way more items so that we can actually fill up our table view.
162

163
00:15:05,240 --> 00:15:11,900
So, now, say, if I check these first two cells and I scroll down to the bottom, you can see that we no longer
163

164
00:15:11,900 --> 00:15:17,930
have that cell reuse problem, and it's because in every single one of these cells, were checking to see
164

165
00:15:17,930 --> 00:15:25,550
what is the done property for each item, and we're displaying the checkmark depending on what that property
165

166
00:15:25,550 --> 00:15:27,110
is whether if it's true or false.
166

167
00:15:27,110 --> 00:15:33,720
So, now that we've confirmed we've solved that bug, let's go back and refactor our code. And I'm gonna
167

168
00:15:33,740 --> 00:15:35,750
get rid of all of this.
168

169
00:15:35,810 --> 00:15:40,550
Let's go back and refactor our code so that everything is more succinct.
169

170
00:15:40,550 --> 00:15:46,460
So the first thing that I want to change is all of this and this is very, very inelegant, and I'll show
170

171
00:15:46,460 --> 00:15:55,740
you why. Because instead of doing all of this, what I could have said is itemArray[indexPath.row]
171

172
00:15:55,820 --> 00:16:05,810
.done property equals the opposite of !itemArray[indexPath.row]
172

173
00:16:06,810 --> 00:16:14,010
.done. So that single line replaces all of these,
173

174
00:16:14,020 --> 00:16:16,230
what, five lines of code,
174

175
00:16:16,360 --> 00:16:18,750
and it does exactly the same thing.
175

176
00:16:18,820 --> 00:16:26,710
It sets the done property on the current item in the itemArray to the opposite of what it is right
176

177
00:16:26,710 --> 00:16:27,110
now.
177

178
00:16:27,160 --> 00:16:33,450
If it's true, it becomes false. If it's false, it becomes true. And it's all using this NOT operator,
178

179
00:16:33,520 --> 00:16:38,670
basically reversing what it used to be. Because Booleans can only have one or two states,
179

180
00:16:38,680 --> 00:16:39,670
true or false,
180

181
00:16:39,670 --> 00:16:42,950
so if we put the NOT true, then it becomes false.
181

182
00:16:42,970 --> 00:16:47,830
If we put NOT false, then it becomes true. So we can go ahead and delete all of that.
182

183
00:16:47,950 --> 00:16:53,020
Now, the next thing that I want to address is I'm going to delete these two lines because we don't need
183

184
00:16:53,020 --> 00:16:53,880
it anymore.
184

185
00:16:54,010 --> 00:17:01,630
And here, instead of using itemArray[indexPath.row many times, I'm just going to create a new
185

186
00:17:01,630 --> 00:17:08,100
constant code item and I'm going to set it to equal itemArray[indexPath.row].
186

187
00:17:08,110 --> 00:17:13,210
So this item is now the item that we're currently trying to set up for the cell.
187

188
00:17:13,210 --> 00:17:20,490
So then instead of writing this, I can simply write item.title. Instead of writing this,
188

189
00:17:20,530 --> 00:17:27,760
I can simply write item.done. But we can go even further than that to make this bit of code a lot
189

190
00:17:27,760 --> 00:17:29,730
shorter and a lot more elegant.
190

191
00:17:30,040 --> 00:17:34,450
And in order to do that, we're going to use the ternary operator.
191

192
00:17:34,450 --> 00:17:38,740
And this is, again, something that's not unique to Swift.
192

193
00:17:38,740 --> 00:17:44,500
It's seen in a lot of programming languages, but it's a really neat way of cutting down some code when
193

194
00:17:44,500 --> 00:17:46,320
we don't want to be so wordy.
194

195
00:17:46,360 --> 00:17:50,830
So to learn all about the Swift ternary operator, I will see you on the next lesson.
