In this lesson, we're going to add the finishing touches to our BMI Calculator. 1
We're going to apply the MVC design pattern to add some additional functionality to our app. By the end 2
of this lesson, 3
our Apple display a piece of advice text underneath the BMI value, and also show a different background 4
color depending on whether if the BMI that was calculated happens to be underweight or overweight. 5
In the process, we'll learn how to work with color laterals and also get more practice working with optionals 6
in Swift. 7
Speaking of optionals, prior to our Optionals Deep Dive, we were looking at our BMI property in our struct. 8
We transformed this property into an optional and we were wondering how to best work with it in the 9
getBMIValue function. So coming back to our projects, 10
having learned about all the ways that we can handle optional, let's think about how we can apply those 11
methods that we learned about to our optional value here. 12
The first thing that we learned about is we could simply do a "if BMI is not equal to nil," 13
well then in that case, and only in that case, do we actually trigger this block. 14
Now, once we reindent our code, and then we go ahead and unwrap this bmiVvalue safely within the check 15
of our "if" statement. Now, that error goes away, but we get a new error. It's telling us that we're missing 16
a return in a function expected to return "String." Because, yeah, when we created this function 17
getBMIValue, we promised that we would give a string as an output by the end of this function. 18
But you can see that if the BMI was indeed nil, then this entire block of code is going to be skipped 19
and we're gonna land right here without having any value to give back. 20
So what can we do? 21
Well, we could provide a "else" statement. So we could say that, well, in the case where BMI is nil, then let's 22
simply just return "0.0." 23
So that's one way of looking at it. The other way is using are optional binding, 24
so where we say, if let safeBMI = bmi. 25
Well, in that case, as long as this has a value, then we should be able to use this safeBMI as an actual 26
floating point number. 27
So here we don't have to do any unwrapping anywhere, and we can just simply tap into the safeBMI. 28
But, again, in this case, we still need that "else" statement because if the BMI is nil, then, again, this block will 29
be skipped and it will jump to the "else" statement. 30
Now, if we wanted this code where we're using the BMI value if it has a value, otherwise, providing a default 31
value, well, then this whole blogger code could actually be vastly simplified by simply deleting the "if" 32
and the "else" statement, and using our nil coalescing operator. 33
So in this case, we're going to say that, well, if there is a value in BMI, well, then use that value, 34
but otherwise, let's just use 0.0 as the value that we format. So out of those three options, 35
I would actually prefer this approach. The syntax with the nil coalescing operator is both more clear 36
and more expressive. 37
So now that we've figured out the bmiValue, the next step is getting hold of an interpretation for the 38
bmiValue and assigning it a piece of advice as well as a color. 39
Now in that case, we should probably end up grouping our bmiValue with its color and advice together 40
in the same data type. 41
So does that remind you of something? 42
Well, it means that we should probably create a model for our BMI. 43
That's right. 44
We're going to build our app to be more in line with the MVC design pattern. 45
So in our Models folder, let's go ahead and create a new file, and it's gonna be a Swift File, and let's 46
go ahead and name it BMI, and make sure it goes into the Models folder. In this BMI file, 47
I'm going to create a new struct called BMI, and it's simply going to have three properties: a value which 48
is going to be a float, a piece of advice which is going to be a string, 49
and finally, a color which is going to be a UIColor. 50
Now, notice how it's not coming up in my autosuggest here. 51
And that should be a good hint to you that we actually are using something that we don't currently have 52
access to, 53
and this says just as much because we need to switch our foundation to UIKit, so that the UIColor which 54
comes from UIKit can be found, and we can use it. 55
So it's a pretty simple struct for BMI. 56
It's going to be a custom data type that grouped together the value which is the number, 57
and then the advice text as well as the color. 58
Now, we can head back into our CalculatorBrain. And instead of having our BMI being a float, I'm going 59
to change its data type to being that new BMI struct that we created. 60
But again, it's going to be an optional. 61
So now that we've changed the data type, we have to do a few fixes in our code. 62
And the first thing is that we should change this which is no longer a value or a floating point number, 63
it's now a BMI struct, 64
instead it should become a bmi.value, so that we can tap into that floating point number. 65
But notice how as soon as I type the word "value," what gets automatically inserted for me is that question 66
mark there. And from the last lesson when we did our Deep Dive into optionals, you should be able to recognize 67
that this is effectively changing our BMI optional. 68
So because the value is a normal floating point number, 69
but the BMI is, of course, the optional here, 70
in order to reach into the BMI to get a hold of that value, we first have to check to make sure that the 71
BMI is not nil like it is here. 72
And if it is indeed not nil, well, then we can get hold of the value that lives inside. 73
But if it is nil, then we're simply going to use "0.0" as of value. 74
So, that's our first part fixed. 75
Now down here, though, we can't simply just write BMI value and use optional chaining to try to assign 76
this to our value property in the BMI struct. Because remember that the value was set as a "let" constant. 77
Once we create this BMI with the value, advice, and color, all we want to do is just pass it around. 78
We don't want to change it. 79
It's only ever going to have one value. 80
And so that means what we actually want is to be able to create a BMI struct from scratch. 81
So instead of writing bmi.value, let's create a temporary constant. Let's call it let bmiValue = height, 82
and then we can create our bmi by using that BMI struct, and then using the initializers to fill 83
in each of these values. 84
So the BMI value that we calculated, of course, goes into the value placeholder, and then we need a piece 85
of advice and a piece of color, but these two things are worked out based on what value we got. Because 86
the reason why the BMI is such a useful indicator for health professionals is because it takes into 87
account not just somebody's weight, but also their height. A really tall person has the same weight as 88
somebody who's really short, then the shorter person probably is more overweight. 89
So in the BMI chart, you can see these different bands which classify the BMI values, where below 18.5, 90
they are underway between 18.5 and 24.9, they are healthy. 91
And then above that, they are overweight, obese, and extremely obese. 92
The National Institute of Health classify some of these categories and we're going to use these categories 93
to create an "if" statement in our code. 94
So as a challenge, I want you to look at the BMI value that we calculated from dividing the height squared 95
by the weight, and then I want you to print underweight, normal, or overweight based on what that value 96
is equal to. For this challenge, 97
just print to the console for now. 98
We'll be adding the functionality to show the interpretation inside the app in a bit. 99
So take a look at this breakdown. 100
Pause the video and try to complete this challenge 101
All right, so let's first comment out 102
this line of code which we don't have any values for, 103
and then we're going to use our bmiValue which gets calculated, and we're going to check to see if the 104
bmiValue is less than 18.5. 105
Well, in that case, then we should print "underweight." 106
But the next category for the normal weight is for people who are between 18.5 107
and 24.9. 108
So how can we express that range? 109
Well, we could use an "else if" which only gets triggered, remember, if the "if" statement, this check, fails. 110
Well, in that case, if it's not less than 18.5, well, then, is the bmiValue less than 111
24.9. 112
Well, in this case, we should be printing "normal weight." And finally, if the BMI value is not less than 113
18.5, and is not less than 24.9, well, then we can catch everything else 114
above 24.9 with the "else" statement, and we can print "overweight." 115
So this is one solution to our problem. 116
Now, you might have done it slightly differently. You might have checked to see if the BMI value was greater 117
than 18.5, and it was less than 24.9 to catch that particular range, 118
and that would be the case if you only had an if statement. But if you have an "else" statement, remember that 119
it's already checked the previous condition, it's already checked and made sure that the bmiValue is 120
not less than 18.5. 121
And that's the point at which the "else" statement actually gets triggered, 122
so we don't actually have to check the lower end, we just have to check the upper bound which is 123
24.9. And that's the solution to the challenge. 124
Now, let's make use of this logic. 125
So now instead of printing "underweight," "normal weight," and "overweight," let's instead create our BMI struct 126
when we have these different categories. 127
So if the bmiValue was less than 18.5, well, then maybe the advice is we should tell them 128
to "Eat more pies," and the color that we will use to show that they're underweight is maybe we could just 129
use the UIColor.blue. 130
Now, notice how UIColor, again, comes from UIKit. And without importing UIKit, we won't be able 131
to use it. 132
But there's another way that we could do this by using something called a Color Literal and we're not 133
going to use UIColor anymore. 134
Instead, we're going to start typing Color Literal. And once that hint comes up, we're going to hit enter. 135
And now in this little square, we can double click on it and out pops a little color palette that we 136
can pick from. 137
Now, if these colors aren't enough for you, then you can click on Other, and you get the full choice of 138
RGB Sliders or the rainbow, or the crayons, whichever one you prefer. 139
But in my case, I'm simply just going to choose this light blue color for people who are underweight. 140
And then for normal weight, I'm going to say, maybe, "Fit as a fiddle!" and let's add an exclamation mark 141
here as well to express our enthusiasm. 142
And let me just fix my typo there. 143
Now, let's change this color to green for normal weight. And finally, for overweight, 144
we're going to say, maybe, "Eat less pies!" because that's what everybody eats, just pies all day. 145
And I'm going to change the color to red. All right. 146
So I've got blue, green, red, and I've got a piece of advice for everybody from each category. And now we've 147
calculated the BMI based on the values that were passed in. 148
We've interpreted them and created a separate BMI struct for each value category. 149
So let's tie everything that you've learned so far together with a challenge. 150
Head back over to the CalculateViewController. And down here where we have access to this destinationVC, 151
which is the ResultViewController that's going to be shown up on screen, 152
I want you to type two more lines of code. 153
So we're going to tap into the destinationVC and we're going to set a property called advice, 154
and we're going to set it equal to the calculatorBrain.getadvice. And then we're also going to tap 155
into the destinationVC, and set another property codecolor which is going to be coming from the 156
calculatorBrain as always. 157
And it's going to be called getColor. 158
So using what you've learned about optionals, about MVC, and struct, I want you to complete this challenge 159
by getting rid of the errors here. 160
Now, you're not allowed to touch anything else inside the CalculateViewController and you have to fix 161
this code and make it work, so that when you run the app and you change the height to some extraordinary 162
number and click calculate, it should calculate a low BMI which means a blue background, which means eat 163
more pies. 164
And if you had a normal-ish sort of height and a normal-ish sort of weight, 165
then you should end up with a green background with a "Fit as a fiddle" as the advice text. 166
And finally, if you have a really low height and a normal weight, then you might get a higher BMI result 167
with a different advice and a red background. 168
So, essentially, these two lines of code will allow us to pass on the advice that we get out of our 169
calculatorBrain, the color that we got out of our calculatorBrain, and make it display in the ResultViewController. 170
So there's a couple of things that you have to fix and you have to write, and it requires all of your 171
knowledge that you've learned so far. 172
Pause the video and try to complete this challenge. 173
All right. 174
So there's a few ways that you could have solved this challenge. 175
But first of all, we have to get rid of these errors. And through the code that I've written, it requires 176
that we have a property code advice and a property code color in our destination VC. 177
So that is, of course, going to be our ResultViewController. 178
So here we've already got a bmiValue, which is a optional string, because when we create the result VC, 179
we don't yet know these values. 180
We have to pass it over because the segue creates that view controller, at which point, we don't know 181
any of these values and it's only when we cast it down to a ResultViewController, and then we can set 182
its properties. 183
So we need to create advice and color over here. 184
So I'm going to create a variable code advice and this is also going to be an optional string, and 185
then I'm going to create a variable code color which is going to be a optional UIColor. 186
So now that should get rid of these two errors and that's no longer highlighted in red. 187
So let's go ahead and address the next part, our getAdvice and getColor methods. 188
So we currently don't yet have those things in our CalculatorBrain. 189
So let's go ahead and create it. 190
Let's create a function called getAdvice and this should probably return a String because the advice 191
text is a string, and then let's create a function called getColor, and this will return a UIColor. So if 192
you haven't converted your foundation into a UIKit, then you'll have to do that now in order for Xcode 193
to know about this data type. 194
So now it's saying that we're missing a return in a function expected to return. 195
So in this case, we need to get hold of the BMI's advice property. 196
So let's go ahead and try to return bmi.advice. 197
Now, it automatically chains our BMI which currently is still a optional, 198
and then it tries to tap into the advice property as long as it exists. But, of course, if BMI was nil, 199
then advice would also be nil, 200
so therefore, this entire bit of code is actually still an optional string. 201
So how do we turn it into an actual string? 202
Well, we could use our optional binding. 203
We could use our nil checking, or more simply, and to keep it short and sweet, 204
we could also just give it a default value of "No advice" in the case where our BMI is actually nil. 205
Now, in pretty much all circumstances, this would never get triggered because our prepare for segue is 206
always going to be triggered after perform Segway which, of course, lives in the calculatePressed IBAction 207
which already calls calculateBMI. 208
Now, we know this but our code doesn't know, and there might be a case in the future where we've forgotten 209
all about this. 210
So to catch that optional being nil, we provide a default value which should alert us that something 211
has gone wrong. 212
So let's go ahead and do the same in our getColor. 213
So I'm going to return BMI chained with the .ccolor property. In this case, I'm just going to provide 214
a default color, 215
say, UIColor.white, or if you want to keep it consistent with the rest of these, then it might 216
be a case of turning this into a Color Literal. But, of course, when there are errors, the Color Literal 217
doesn't really pop up very easily in Xcode. 218
So I find it's easier to just copy it from somewhere else and paste it in, 219
and then we can double click on it to change it to white. 220
All right. 221
So now that we've created our getAdvice and getColor methods, if we go back to our CalculateViewController 222
and hit command B to hit our code with its stick, then that should get rid of all of those errors. 223
But now you might realize that if we run our app, it's still not enough to get the advice and color being 224
displayed on screen because while we have access to them in these properties now, we're not using them 225
yet. 226
So in our viewDidLoad, in addition to setting the bmiLabel also gonna set the adviceLabel's text property 227
to equal the advice string that we passed over. 228
And notice how I'm not unwrapping these optionals, 229
and the reason is because the bmiLabel.text property is actually expecting an optional string because 230
it can be nil. 231
You could have a label with no text and that's perfectly fine. 232
The label can handle that. 233
So in this case, these two data types match and that's why we're not getting any errors. 234
But how do we set the background color? 235
Well, remember that in the UIViewController, we have a property called view, and that view, of course, 236
comes from the UIViewController. And it refers to this view that we see right here which is, of course, 237
the background of this screen. 238
Well, that sounds pretty much like what we need to change in order to change the background. And notice 239
how at the moment we've got this blue background showing up, 240
but it could also be yellow or it could be red, or it could be any other color, basically. 241
So we're going to tap into this view which we always have access to whenever we create a new view controller 242
and it's already linked to our code with a property called a view by default. 243
So every single time you drag on a new UIViewController from the object library, and you put it onto 244
the screen, and you link it up with a UIViewController class, then you should have access to that view 245
property. And that will determine what the background looks like. 246
So let's go ahead and change the view.background color property, and we could set it to the color 247
that was passed over. 248
So now let's go ahead and run our code. And you should see that when we change our weight and height, 249
our BMI gets calculated. And depending on the result, we get a different interpretation and a different 250
background. 251
So have a mess around with that and see what you get. And if you didn't manage to complete the challenge 252
in its entirety, be sure to review your code and try to fix it, so that it works. And if optionals were confusing, 253
then take a look at the Optionals Deep Dive. If this view property tripped you up, then be sure to 254
take a look at the lessons where we talked about classes and inheritance. 255
Now, that's it for this module. And I hope to see you on the next one.