Now, in the last lesson, we've managed to brighten up our categor's view controller by assigning some 1
random colors to the background of the cells. 2
And we're persisting this data, so that every single time we open up our app or we add a new category, 3
the color stays consistent for each of the categories. 4
So the next thing I want to do is I want to go to the items or the TodoListViewController and I 5
want to create a slightly different effect. 6
I want to create a gradient effect so that, maybe, at the top, we've got one color say, blue, right? And it's 7
light blue. 8
And then through a gradient, all the items get progressively darker and darker and darker, and it'll look 9
like a nice gradient transition. 10
So let's try and see if we can figure out how to do that. 11
Now, back in the Chameleon Framework Documentation, you can see that they have some methods that allow 12
you to color a shade lighter or darker, so that will probably help us to achieve our goals. 13
So let's go into our TodoListViewController and go to the place where it makes the most sense 14
to change the background color of the cell which is, of course, inside cellForRowAt indexPath again. 15
Now, the place where we change the cell properties here is inside this "if-let" and we change the 16
textLabel's text 17
and we're also going to change the cell's background color. 18
So we're going to set the background color depending on the current indexPath. If the indexPath is, 19
say, 0, like the first cell, then we're not going to darken the color by very much. But if the indexPath 20
is much higher, say, at, you know, the tenth row, then we're going to darken it a lot more. 21
So it makes sense if we can change that darkening percentage based on our item number in all of the 22
todoItems. 23
So, say, if we had 10 todoItems, then we want the first item to be darkened by 10 percent. 24
The fifth item to be darkened by 50 percent and the last item to be darkened by, maybe, say, 100 percent. 25
So that way we get a nice gradient transition from the top item all the way down to the bottom item. 26
And we can start doing that by setting the sales background color to-- 27
Let's just pick one from Chameleon Framework, maybe something like Flat Sky Blue, right? 28
It looks pretty nice. 29
So we can say that cell's backgroundColor = flatSkyBlue. And in order to get this color out of 30
Chameleon Framework, we, of course, have to remember to import it. 31
So there's a FlatSkyBlue. And we're going to say darken 32
this color by a percentage. And the percentage is going to take a CGFloat, 33
so that's a floating point or decimal number. 34
And the way that this works is it takes a number between 0 and 1, where one is basically 100 percent 35
and 0 is zero percent, and everything in between is your percentage as a CGFloat orCore Graphics floating 36
point number. 37
So the percentage that we want to darken our background color depends on two things. 38
One is which item are we currently trying to color up. 39
And the second thing is how many items are they in our todoList. 40
So let's delete this placeholder and give ourselves a bit of space in order to write out the maths. 41
So the percentage that we're going to darken our cell is going to be the current indexPath.row. 42
Say, if we're currently on the fifth row, right? Let's just imagine one particular situation, rather than 43
trying to plan for all the scenarios. We're currently on row number, number five, right? 44
And there's a total of 10 items in todoItems. So, now we're on 5 out of 10. 45
So we want to darken our background color by, basically, 50 percent, so close to 0.5, 46
and we can get that number by dividing the indexPath.row by the total number of items, right? 47
So todoItems.count. 48
Now, indexPath.row is going to be an integer 49
and todoItems.count is also going to be an integer. 50
So integer divided by integer, 51
that's perfectly valid. 52
But we need to get it out as a CGFloat in order to satisfy the requirements of this particular parameter. 53
So we can cast it into a CGFloat 54
or you might think we can. This line of code will make sense to the largest number of people. But I'm 55
going to show you what happens when we do this 56
and you might already be able to predict if you are good with the logic, and you've been keeping up with 57
what I've been saying. But no matter, 58
this line makes sense to us, right? 59
Take the current indexPath.row which is five, divided by 10, 60
you get 0.5. 61
And 0.5 works as a floating point percentage to darken our FlatSkyBlue color. 62
Now, all of this code, remember, gets called for every single cell in the tableView. 63
So when we are on row number one out of 10 items, then this is going to be 0.1. 64
And so we're only going to darken our backgroundColor by 10 percent. 65
Now, the problem as Xcode is suggesting to us, is that this darkoen byPercentage method returns an optional 66
UIColor, but this needs a definite UIColor. 67
So what can we do? 68
Well, we can, of course, use our handy "if-let," and you can see that we are now building a optional binding 69
pyramid. And this pattern, unfortunately, is a bit too common in Swift. 70
So if let colour equal all of this, 71
so let's cut it and paste it. 72
Now, we can, of course, shorten this if we don't want all of that extra commenting. 73
You can see that we can actually make it all fit into one line. At this point, it's telling us that your 74
optionally chaining todoItems here using this question mark. 75
So this is saying get the count if todoItems is not nil. 76
Now, we know that todoItems can't be nil 77
inside this current if-let because it's only going to get called if todoItems an indexPath.row 78
is not nil. 79
So this is a place where it's actually safe to simply force unwrap, force unwrap todoItems. 80
So if like let colour equal this darker color, then what we should do is we should set the cell's 81
backgroundColor to this color. 82
And now, if we run our app, you'll see something slightly peculiar happened. 83
So let's go into our Home category and let's add some items, shall we? 84
Buy Food. Eat food. 85
Enjoy Food. 86
I'm getting a peek into my mindset here. 87
All right, so-- 88
Oh, there's still the separators over here, right? 89
So let's get rid of the separators by putting it inside viewDidLoad, tableView.separateStyle 90
= .none. 91
So we'll run that change a little bit later on. 92
But the more pertinent problem is, why on earth do we have the same colors, even though we told our code 93
to darken this FlatSkyBlue color by a percentage depending on which row we're on. 94
So we're expecting a gradient here, 95
right? 96
That would be nice. 97
So here is a fundamental logical mathematical issue with type conversions. 98
So I'm going to print two things here. 99
I'm going to print, say, "version 1" and version 1 is going to be what we've got here. 100
indexPath.row / todoItems.count, and then the result of that division turned into 101
a CGFloat. 102
And the second version or "version 2" is going to be slightly different. 103
What if we first turn indexPath.row into a CGFloat by casting it, and then after we've turned 104
it into a float, do we divide it by todoItems.count, 105
And because we can only do division between similar types, 106
right, 107
as it says, "Binary operator / cannot be applied to different types," 108
then we're also going to convert todoItems.count into a CGFloat as well. 109
So the important difference between version 1 and version 2, is that version 1 110
we're doing whole number divided by a whole number, and the result gets turned into a CGFloat, 111
version 2 is whole number gets turned into a CGFloat, whole number gets turned into a CGFloat, 112
then CGFloats are divided by CGFloats. 113
So let's go ahead and hit run and see what we get printed out for every single row in our table view. 114
So let's, again, go into Home where we've already got three items, 115
and let's take a look over here. 116
So version 1 for the first row, indexPath is 0, 0 divided by 3 is still going to be 0, 117
so both of them are 0.0. 118
Now, the differences come in the second row where the index top row is equal to 1. 1 divided by 3 is a 119
third or 0.3 recurring. 120
But that only gets shown in our second version, not our first version. Our first version is still 0. 121
Now why is that? 122
Well, when you divide a whole number by a whole number, the output here gets rounded into a whole number, 123
and then that rounded whole number gets turned into a floating point. 124
So you can try it yourself inside Swift Playgrounds, but this is, essentially, the reason why our code 125
does not work. 126
And this is a very, very simple reason why sometimes when you work with floats and doubles, and trying to 127
use variables that are integers, you might get unexpected problems. 128
So this is something that happens to a lot of people and we're simply going to replace that inside here 129
where it says byPercentage. 130
Okay. So, now I'm going to comment out my print statements because we no longer need them. And I'm going 131
to run our app 132
and we are going to hope for the best. 133
All right. 134
So let's go inside Home, and dun-dud-dun, 135
Isn't that cool? 136
And as we add more items-- 137
I don't know. "Eat more food." 138
"I'm so hungry." 139
"Feed me." 140
You can see the gradient starts building up, and depending on which road we're on, it will darken it by 141
the percentage determined by which row it is in the total number of items. 142
So this is looking pretty neat, right? 143
Now, there's just one problem, and the problem is that this part gets so dark that we can't actually see 144
the text anymore. 145
Yeah, it looks pretty neat and it's pretty. 146
But as we always say, you should choose function over form, 147
right? 148
Unless we can have both. 149
So what do we need to happen in order for our app to work well? 150
Well, we will need the text for the darker backgrounds to be white instead of black, and the text for 151
the lighter backgrounds to be black instead of white, 152
so how can we do this? 153
Well, we can either Google for it and look through custom solutions or why don't we just see if Chameleon 154
Framework already has a method that does it. 155
If it doesn't, we'll build a custom solution. But if it does, let's not reinvent the wheel. 156
And if you scroll through that documentation, you'll come across this neat little thing called contrasting 157
text. And it means that you no longer have to worry about the text 158
standing out from the background color because it's going to take care of it for you. 159
So let's try and figure out where it allows us to do that. 160
So I'm just going to go for command F to find contrasting text. Contrasting Text. Contrasting colors. 161
Okay. So this contrasting color feature returns either a dark color or light color depending on what 162
the Chameleon algorithm believes is a better choice. 163
And we can use it by using UIColor(contrastingBlackOrWhiteColorOn:UIColor, isFlat, 164
true or false, 165
or we can just use the shorthand contrast color passing in the current background color and specifying 166
whether it should be flat or not flat. 167
So let's go ahead and implement that in our code. 168
So inside the same place where we did the optional binding, where we specify the background color, we're 169
going to change the cell's textLabel.textColor. And that textColor is going to be equal to 170
ContrastColorOf backgroundColor. 171
So we're going to pass in our backgroundColor that we generated up here and return a flat color or not 172
a flat color, 173
we're going to set equal 174
true. 175
And now let's run our app and see the magic happen. 176
Nice, right? 177
Looks pretty sweet. 178
Now, you can see that they're all white because the flat sky blue is a little bit dark of a color by 179
itself, and the white stands out quite nicely against it. 180
So this algorithm behind the scenes is trying to figure out whether it's white or black is the more 181
optimal choice. 182
So if we went for a slightly lighter color from the palette, instead of flat sky blue, say, something like, I don't know, 183
a flat white, 184
right? 185
Let's try that one, FlatWhite. 186
This is making me want coffee now. 187
Okay, so let's go ahead and hit run. And you can see that, automatically, the lighter colors at the top were 188
given black text colors, and the darker colors at the bottom were given white text colors. 189
So it's pretty neat, right? Now, it's all very well and good 190
specifying a color and getting it darkened by percentage. 191
Wouldn't it be nice if you could carry through the color of the category, so the user will select Home 192
and this will be a gradient of green colors similar to our Home color. 193
So what you'll need to do is to, somehow, change this to the color that we persisted using Realm. 194
So pause the video and try and see if you can complete this challenge. 195
All right. 196
So this one shouldn't be too hard because if you remember we passed in the current category using the 197
selectedCategory property, and this is a optional category that contains all the pieces of data in here, 198
so the name of the category, as well as the color of the category as a string. 199
So if we go in here and replace this FlatWhite using something like UIColor, using hexString, 200
and the string is going to come from our selectedCategory.colour property. 201
Now, we've got some problems here because if you click on the error, it says that selectedCategory is-- 202
It says that--it tells us that selectedCategory, 203
the first little highlight, is an optional category and it's not unwrapped. 204
The other problem is that the output or the return of this initializer create a UIColor using 205
hexString is also a optional UIColor, and it's also not unwrapped. 206
So there's two ways of solving this. 207
You can either go down a pretty steep if-let pyramid, 208
if let color equals this, 209
then if let blah-blah-blah. 210
And then if let, the next one, 211
then if let, the next one, unwrapping each property as you go along. 212
But let's go back to first principles 213
where inside this if let 214
at the moment, and checking to see if todoItems at indexPath.row is or is not nil. 215
Now, if this is not nil, then that means that we manage to have items inside todoItems, then that means 216
todoItem exists. And todoItems comes from our selectedCategory which must mean that selectCategory 217
can't be nil either. 218
So sometimes having these warnings forces you, as a human developer, to think and see if this is safe 219
to be unwrapped. 220
Now we've unwrapped selectedCategory. 221
The next thing we need to tackle is, what if we can't actually get a color from this hextString? 222
What if I just entered some piece of, you know, mumbo-jumbo of a hexString, and you can't actually get a 223
hex color? 224
And this is a problem you always have when you allow strings because strings can be free form. 225
It doesn't have to conform to a particular format. 226
Then I could have put in any old string in here. 227
And of course, you can't just generate a color from any string. 228
That's not how it works. 229
It has to be a valid hexString. 230
Now, we don't have any validation for that. 231
But what we can say is if this is nil, then don't go forwards with the next method, 232
i.e., darkened by percentage. 233
So we're saying optionally chain this. 234
So if this is not nil, i.e., we got a valid hex code creating a valid UIColor and it's not nil, then go 235
forwards and try to darken it. Because if we try to darken a nil value, then this is where our app is going 236
to crash. 237
So we're using optional chaining to make sure that all of this is not nil before we continue with our 238
code block. 239
So let's run our app and see if we manage to carry forwards that color from the category to our 240
TodoListViewController. 241
All right. 242
So here's our Home category which is green and if we click on it-- Oh, look. Isn't that nice? 243
We've got a gradient of green items 244
and we've got text that's contrasting with the background color, and it looks pretty neat to me at least. 245
So did you manage to get that to work? 246
If so, then great. 247
If not, then you might need to watch some of the past videos 248
a few more times. 249
All right. 250
In the next lesson, we're going to tidy everything up and we're going to change our nav bar to make it 251
reflect the colors that we've got in our 252
TodoListViewController. 253
So all of that and more, we'll see you on the next lesson.