All right. 1
So we're going to continue building our calculator but, firstly, make sure that you've deleted that 2
Test.swift file if you are following along with me and you created it as well. 3
We don't need it and we definitely don't want it 4
modifying our isFinishedTypingNumber variable. 5
So, currently, our app works, namely, that it can accumulate numbers, but it doesn't actually do anything, 6
right? 7
When I say something like, you know, 12+3 = Nothing happens. It doesn't do any calculations 8
yet because we haven't told it to. 9
So let's go ahead and add in that logic 10
and that functionality to make our calculator work. 11
So if I pull up the calculator that's on my Mac, you can see that we've got three buttons up here. 12
And the first button simply clears the board. 13
It turns everything to zero. 14
The second button will turn whatever it is that I've got in the display to the negative version of it. 15
So if it was 2, then it becomes minus 2. 16
But if it was minus 2, then it becomes 2. 17
And the third one turns everything into a percentage, 18
so it divides it by 100. 19
So 90 will become 0.9, et cetera. 20
So these are a little bit simpler because they only need one number to be able to execute the functionality, 21
whereas these four are a little bit more complicated because we need to be able to say 5, then we hold 22
that number in memory, and we press one of these buttons 5+ 23
then now we've got somewhere which has got five stored. 24
We've got the operation plus stored and we now have to add another number 2, and then we press the equal 25
sign, and it does all of that calculation. 26
So these four are a little bit more complicated than these three. 27
So let's first tackle these three. 28
So the place that we want to do this is inside our calcButtonPressed IBAction because what we want 29
to happen is to, say, for example, if I typed 10, and then I pressed the percentage sign, then this IBAction 30
will get triggered, and I want to be able to turn that 10 into 0.1, so divided by 100. 31
So first things first, I need to get the value that's inside my label here as a number, as a double, ideally, 32
because we'll be working with decimal places later on as well. 33
So let's say let number = displayLabel.text, 34
so now I'm taking whatever is currently in the display label 35
when I pressed one of these buttons and I'm saving it to a constant code number. 36
Now, this number is a local variable at the moment already. 37
So it's only accessible inside the scope of the calcButtonPressed, 38
so I don't have to also add a private in front of it. 39
However, if I was creating this in the global scope, 40
so if you're confused about global and local, make sure you have a look back at the video on scope. 41
We spoke about this in more detail. But, essentially, we don't have to turn any local variables into private 42
vars because it's already limited in scope and only accessible inside the block where it was declared. 43
So, now that I've got that number saved, I want to be able to turn it into a double. Because, currently, 44
if I hold down alt and click on number, you can see it's just a string, 45
right? 46
Because it's set to the text that's inside the displayLabel. 47
So this will be currently equal to, essentially, the string 3 48
and I want to turn it into the double 3.0. So I can do that by just doing a simple cast. So I can say Double 49
and then I can wrap whatever it is that I want to cast into a double inside some parentheses. 50
So, now if I check number, again, you can see it's now a Double? 51
But it's not just a regular double, it's actually an optional double. 52
And the reason for that is because if I'm turning text into number, there's a lot of cases where that's 53
not possible. 54
So, for example, if I tried to turn the string "A" into a Double, then what is that going to be? 55
I have no idea and nor does the computer. 56
So that's a case where this could fail this casting process. 57
So that's why when you cast a string to a double, it will always produce a optional Double. 58
Now, the opposite case, 59
Let's say, we try to create a string, right? 60
And we set that to equal String casting, something that was a number, say, 12, this is going to produce 61
a normal String, a bog-standard string because when you cast from number to string, it's always possible. 62
All it does is add some quotation marks around it and, bam, you've got a String. 63
But the opposite fails occasionally. 64
So, now we have this optional number which makes it very difficult to work with. 65
So we want to either unwrap it or we have to do something with it, right? 66
Now, one option is to simply unwrap this by putting a exclamation mark at the end of this group. 67
So, now what this says is that I'm definitely sure that there is text inside the displayLabe, and that 68
makes sense, 69
right? 70
Because even if we don't put in numbers, it's still got a zero in there. 71
So that's a valid unwrap. 72
So next, we're turning it into a double and we're saying that it's always going to work, namely whatever 73
is in here is always going to be able to be turned into a double. 74
And this is also a valid assertion because where does the number here come from, 75
well, it comes from whatever I do in here. 76
And all of this code only tries to put the numValue which is the current title of the sender, 77
so one of the number buttons, any of these ones into here. And they can all be turned into a number. 78
So if you were to force unwrap this, it's also not exactly wrong, because the logic is kind of valid. 79
Now, the only problem is if you screw up on your linkages, 80
so your IBOutlets and your IBActions. 81
So there's many ways in which this can actually fail. 82
Now, failing and crashing your app is not necessarily a bad thing because it allows you to figure out 83
what other things are unexpected, and for you to test your app, and also to address all of these unexpected 84
scenarios. 85
Now, what you don't want is to, say, use an "if let" and just not catch this error 86
if it does happen because it'll skip the code instead. 87
So let's say that if instead of linking all of the number buttons to this numButtonPressed, I accidentally 88
also linked one of the text buttons to it. 89
So let's just delete the current linkage to calcButtonPressed and let's add in a new link to 90
numButtonPressed instead. 91
And now this IBAction is linked to all of the numbers as well as this button. 92
So that means that when I press this button, it's going to try and put that into the displayLabel, and 93
then, later on, it's going to try and turn that text into a Double which will fail. 94
So let me show you what will happen. 95
So at this stage when I type numbers and I press any of these calc buttons, nothing happens. 96
It's all working fine. But if I press this button and the string gets put into the display label, when 97
I press any of these calc buttons, our app is going to crash and it's going to show us that the line 98
that caused this crash is this one, 99
and the problem is a "Fatal error: Unexpectedly found nil while unwrapping an optional." 100
Now, the optional here is this one Double question mark, and it's unwrapped, because we assumed that it 101
would always be possible to turn this into a Double. 102
Now, because of what I've just done just now, my little mishap, 103
well, that's not possible anymore. 104
Plus, minus does not become a number. 105
So you might think, well, you know that means that we should never force unwrap anything because it's 106
always bad, 107
and instead, we should use maybe something like optional binding, right? Use an "if let." 108
Well, I would argue that actually using an "if let" is not always the best option 109
and sometimes you do want your code to crash, especially when you're testing it and when you're developing 110
it. 111
The reason for that is because when it crashes, it alerts you and you are forced to figure out why it 112
crash and what caused it. 113
Now, let's say I didn't want to force unwrap this. 114
What are the other options that I have in my arsenal? 115
Well, let's see. 116
Let's try and use this optional, right? 117
Let's try number + 2 118
and I'm going to use command B to build. 119
Now, I'm going to get a build failed and if I click on this little button here, it tells me that value 120
of optional type 'Double?' must be unwrapped to a normal double 121
in order to use it, for example, in this calculation. 122
Now, there's two options that it's giving me. 123
One is to force unwrap using the exclamation mark which is what we did previously, 124
and if you click on this fix, you can see that it will force unwrap this number, or you can also force 125
on unwrap over here. 126
Now, the other option that is giving me is to use what's called a nil-coalescing operator using two 127
question marks to provide a default when the optional value contains nil. 128
So in this case, what I can do is if I can turn this string into a Double, then I can put two question 129
marks and say, well, in that case, instead of giving back nil, let's just give 0. 130
So now let's retest our app. 131
And, again, I'm going to put the +/- sign into the displayLabel. 132
I'm going to press one of the calculation buttons and you can see nothing happens. 133
My app doesn't crash and it just continues, and it just continues along as if nothing happened because 134
I managed to set that number to zero. 135
So I would argue that this is actually worse than if you actually let your app crash when it's doing 136
something unexpected. 137
Now, when we leave this as a forced unwrap, it can be a little bit difficult to figure out why it crashed 138
when our string can't be converted into a double. 139
So an alternative here is to instead use a "guard." 140
So we're saying guard let number = Double. 141
And if this conversion process from string to double can't happen, then we're going to say, else throw 142
a fatalError. 143
And there's two types of fatalError where you've got the simple fatalError and it stops your app from 144
executing, namely crashes your app, 145
or you can have the one where you can put in your own message. 146
So that's what we're going to use. 147
So if we can't convert our displayLabel.text to a Double successfully and turn this into 148
a nonoptional Double, then we're going to say, "Cannot convert display label text to a Double." 149
So, now when our app crashes, we will get a far more explicit error message, 150
and this means that three months down the line when we can't actually remember when anything does in our 151
app any more, then if we typed this into the displayLabel and tried to convert it into a double, then 152
our app crashes, 153
and also we get a "Fatal error: Cannot convert display label text to a Double" 154
which should hint to us that, oh, this must be related to this part, right? 155
So, now that we've tested this and we fixed it, 156
let's make sure that we don't persist this error. 157
So let's delete that connection to numButtonPressed, and instead, make sure that we connect it again 158
by holding down the control, and clicking and dragging, and we're going to connect it to calcButtonPressed, 159
so that it'll be addressed in the calcButtonPressed IBAction.