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