In the previous lessons, we've been learning a lot about inheritance and managing our screens using a 1
UIViewController. 2
Now, it's finally time to learn about segue, so that we can navigate between our screens in the storyboard. 3
Now, when we created our SecondView Controller class entirely from scratch, it gave us a lot of control 4
and allowed us to show you how we would inherit from UIViewController, 5
How we could override the viewDidLoad method that came from the UIView Controller, as well as to happen 6
to properties that come from the UIViewController as well. 7
And we got to see the class, as well as the object created from the class in their purest forms. 8
But you saw how much work it was just to create a single label. 9
Now, imagine if we had to create the entire User Interface. 10
It would require over 50 lines of code. But luckily for us, there's a much easier way of presenting another 11
screen. So, let's go ahead and delete our SecondViewController file, and instead, we're going to go to 12
File, New File. And this time, instead of choosing a Swift file, we're going to choose a Cocoa Touch Class. 13
Now, Cocoa Touch refers to the framework that Apple has created that include UIKit and a whole bunch 14
of other libraries that allows us to piggyback off their code, and make our lives a little bit easier. 15
In this case, the class that we're going to create is gonna be called the ResultsViewController and 16
it's going to be a SubClass of our UIViewController. 17
Now, go ahead and click Next, 18
and then make sure that it gets grouped with the Controllers, and then click Create. 19
So now you should end up with a new file that already has UIKit imported that already has UIViewController 20
as the SuperClass that we're inheriting from, and it should also have created the class code 21
ResultViewController that's inside a file called ResultViewController.swift. 22
It also got the viewDidLoad already implemented and all we have to do is to delete these comments 23
and add our own custom code. Just as we have done before where we have our IBOutlets, IBActions, and we link 24
our design with our code, 25
I'm going to use this ResultViewController as the code file that's going to control and manage this 26
screen. 27
So we've already designed the screen for you. But in order to link it up with this new file, go ahead, 28
and select it either with this little yellow button here, 29
alternatively, in the document outline. And then, we're gonna pop open the right-hand pane and go to the 30
identity inspector which is this one. 31
Now, we're going to set the class that controls the screen and we're going to start typing results, and 32
it should, hopefully, pick up on the name of our class here which is ResultViewController, so you can 33
go ahead and hit enter 34
once it finds that. You can also click on the dropdown list and try to scroll through this list to find 35
it as well, 36
but I find it easier just typing. So now that this is linked up, when we go into the Assistant View, we 37
should be able to start linking up some IBOutlets, and some IBActions. 38
Now, occasionally, Xcode doesn't quite pick up on this link as fast as it should do. 39
So when you select each of these view controllers in the main.Storyboard, it should be automatically 40
pulling up the linked file. 41
So in this case, it's the ViewController.swift. But in this case, it's actually hasn't clocked on 42
yet. 43
So what we have to do is go to Automatic and select the ResultViewController. 44
So in your case, you might not have this confusion, and X code is always changing, always trying to improve. 45
It might be that you don't have this and you see this ResultViewController come up straight away 46
when you go into assistant view. But at least on my copy at the moment, 47
this is what I'm seeing. 48
So this is how you can fix it. 49
So now that we've got our code file and our design file side by side, let's go ahead and create some 50
IBOutlets and IBActions. 51
So the first one that I need a reference to is the place where I'm going to display the BMI value, so 52
I'm going to create an Outlet here and I'll call that the bmiLabel, and then I'm going to create another 53
Outlet from this label here which I'll just place below and I'll call that the adviceLabel. 54
And finally, we're going to create a IBAction from our recalculate button right at the bottom 55
after viewDidLoad. 56
So this action is going to be of type UIButton, 57
so that's the sender data type, and then the name is going to be recalculatePressed. 58
So that's it. 59
And we can go into our full screen of ResultViewController. 60
And now we've got our two IBOutlets and an IBAction and we're able to use the design that we've created 61
in our Interface Builder. 62
Now, this might be a good time to also rename our ViewController.swift because it's probably better 63
if the name tells us straight away which screen its managing in the Main.storyboard. So you can see 64
that when you select this View Controller Scene and you go into the Identity Inspector, you can see that 65
this is where that class is showing up. 66
Now, you could manually go and change the file name, change the class name, and change the name of it in 67
the Identity Inspector on the Main.storyboard as well, but that's quite a lot of work. 68
A much easier way of doing this is simply to right-click on the name of the class, View Controller, go 69
to refactor and rename, 70
and now Xcode should be able to pick up on all the places where we're using that name View Controller, 71
including the file name, the commenting, the class name, as well as in the Main..storyboard. 72
So right here, we're going to change this to the CalculateViewController, and then go ahead and click 73
rename for all of these places to be changed and updated. 74
Now, occasionally, Xcode might trip up when you try to rename the name of the file and it doesn't actually 75
change it over. 76
So what you can do is you can go to Controller and Show in Finder, and inside that folder, make sure that 77
the name of the file is CalculateViewController.swift. If not, you can simply just click and then 78
pause one second, and then click again. 79
If not, then you can simply just change its name here as well. 80
And that should fix that problem. 81
But otherwise, we've now got our two View Controllers each linked to a code file that inherits from 82
UIViewController. 83
And this ResultViewController is gonna be the second screen that we want to display onto our app. 84
Previously, we had to initialize a View Controller manually using our code. 85
If we go into Main.storyboard, there's actually a much easier way of navigating to the next screen. 86
We can simply create a segue. 87
So if you select the CalculateViewController, either in the document outline or this little yellow 88
circle here in the Main.storyboard and you hold down your control key, then you can click and drag 89
and point to the ResultViewController, and then let go. And now you're going to select a Segue and 90
we're going to select Present modally. 91
Now, the segue is, basically, going to initiate a transition to the next screen. 92
It's going to display the transition in an animated way and you can choose your animation by simply 93
selecting that little arrow that is representing the segue, and then change the Transition type, but 94
we're going to keep everything as the default, and we're going to give this segue an identifier, so a 95
way of being able to identify it through our code. 96
And I'm going to call this goToResult. 97
And now we have a segue which will transition to the next screen and we can trigger it by using that 98
identifier goToResult. So now if we go ahead and copy this identifier, we don't make any typos in our 99
code, and we go to our CalculateViewController.swift. 100
Now, here we're going to delete this line where we create our secondViewController because that doesn't 101
really exist anymore, 102
and I'm also going to delete these two lines as well. Instead of all of that, 103
what we're going to do is we're going to use a method that every UIViewController has, 104
and we've managed to inherit it by inheriting from UIViewController. So we can tap into our current 105
CalculateViewController. 106
And from this view controller, we can trigger the method that's inherited code performSegue. 107
So it's this one. 108
And if we go ahead and hit enter, it should insert all of the parameters that we need to fill out. 109
So in this case, in order to perform a segue, we have to put in the identifier and that's, of course, the 110
identifier of the segue which you should have copied over, and you need to make sure that it's spelt 111
exactly the same as what you had in the Main.storyboard right here. And then, we're going to specify 112
the sender. 113
So who is going to be the initiator of the segue? 114
And it's, of course, our CalculateViewController, 115
so that is, of course, our "self," our CalculateViewController, 116
the current class. And now if we run our app, 117
and we click on calculate, you should see that it navigates to the next screen which is our 118
ResultViewController. 119
So now it's just a matter of passing over the BMI value. Inside our ResultViewController, 120
I'm going to create a new variable. 121
So this is going to be a property of my ResultViewController and I'm going to call it bmiValue and 122
I'm going to set it as a optional string because, initially, when I create my ResultViewController, 123
I don't know what the BMI value is going to be. 124
It's only when I've calculated the BMI, then can I actually pass it over. But now that the segue is responsible 125
for initializing our ResultViewController and I no longer have to initialize it manually, 126
well, how can we set the bmiValue property? If you take a look inside the ResultViewController, 127
notice that when we created it, the template code also included a navigation section that's been commented 128
out. 129
You can see here it says that in a storyboard-based application, you'll often want to do a little preparation 130
before navigation. And this is the method that you need to override in order to run some code just before 131
the segue initiates, 132
so in preparation for the segue. And in here, it has a comment that tells us if you want to get hold 133
of a reference to the new view controller, 134
well, you can simply use segue.destination. So you can either cut this and copy it over to our 135
CalculateViewController or you can simply just write "prepare for," and find 136
"prepare for segue method," and this is going to be a method that we inherit from you UIViewController 137
which we're going to override. 138
So inside here, we can tap into the sender.destination, and the sender.destination is going to 139
be the view controller that will be initialized when the segue gets triggered. In a multiscreen app, 140
it's more than possible that you could have multiple screens that all originate from one screen. 141
So let's say that I was to find another view controller and I was to drag it onto the screen, and I also 142
have a segue from my CalculateViewController to this other view controller. 143
Well, now I've kind of got two segues, 144
right? 145
I've got one that goes here and one that goes here, and they both start from the CalculateViewController. 146
And when either of those segues are performed, the prepare for segue method will get triggered. 147
So to be able to differentiate between our segues or at least in a potential future where there 148
might be a clash, we should first check to see if the segue.identifier is equal to the identifier 149
that we assigned to the one that goes to the result screen. 150
This one. So let's check to see if segue.identifier is equal to "goToResult, spelled the exactly the same 151
way again. 152
And then, in that case, 153
well, we know that we've narrowed down on the actual correct segue. 154
So now what we can do is we can create a reference to that new view controller that's going to be initialized 155
when the segue gets performed. 156
So let's create a constant code destinationVC 157
and we're gonna set it to equal segue.destination. 158
Now, this destination is the view controller that will be initialized when the segue gets performed, 159
and it has a type of UIViewController. 160
So even though we know that for a fact when the goToResult Segue gets triggered, then it's definitely 161
going to be this one which will initialize a ResultViewController. But if we tried to say 162
destinationVC.bmiValue, well, we're going to get an error because it says, 'UIViewControllers' don't have 163
'bmiValue' and, indeed, that is true. 164
So why is it not picking up on this property that we have in our ResultViewController? 165
Well, it's because that the segue.destination has a data type of UIViewController and, therefore, 166
our destinationVC is also a UIViewController, 167
but the bmiValue only exists within our ResultViewController. 168
So in order to narrow it down to say that the segwue destination is actually a ResultViewController. 169
We can say "as!" ResultsViewController. And as soon as we do that, then it recognizes 170
the bmiValue, and our destinationVC turns into a ResultViewController data type. 171
So now we can set this bmiValue to "0.0" and all of our code is now error-free. 172
Now, why is it that we had to do this? And what does it mean when we write "as"? Well, remember that we're 173
overriding this "prepare for segue" method, and that when Apple created this method, they didn't know that 174
we were going to create a ResultViewController. We didn't know about a bmiValue that we needed to 175
set. 176
So what they've done is that the segue will initialize a box standard UIViewController. 177
Now, it's our job to narrow down the data type and specify the exact data type that the destination will 178
be. 179
So by using this "as" keyword, we are effectively performing something called downcasting. At the moment, 180
we have a UIViewController which, of course, is the SuperClass of our ResultViewController, 181
we can cast it down to a ResultViewController by writing the keyword "as." Now, the exclamation mark here 182
says this is going to be a forced downcast, 183
so just as when we use the exclamation mark to unwrap optionals, this expresses our certainty that when 184
the segue is identified its goToResult, then the destination view controller that gets created is 185
definitely going to be a ResultViewController. So now can you use what you've learned and what you 186
know to try and get the bmiValue that we calculated inside calculatePressed over into our 187
prepare for segue, so that we can actually set the destinationVC.bmiValue to the actual calculated value, 188
rather than just "0.0"? There's a few bits of code that you'll need to change, but the end result 189
if you're successful is you should be able to change the height and weight toggles, click on calculate, 190
and you should end up with the actual BMI result being passed over and displayed in this BMI label. Pause 191
the video and see if you can complete this challenge. 192
All right. 193
So at the moment, we know that we've got our BMI being calculated in the calculatePressed IBAction, 194
but we actually need that value in our "prepare for segue" method. 195
So how do we move it over? 196
Well, one of the easiest ways is to simply create a variable in our CalculateViewController, so we can 197
call it bmiValue and we can set it to start off being equal to "0.0." And then when we calculate 198
our BMI, we can format it so that we only get it to one decimal place. So we could say bmiValue is equal 199
to a String that is formatted with this particular format. 200
So it's gonna be 201
"%.1f" and then the value that we want to format is of course that bmi floating point number 202
that we got here. 203
And so now we've got our bmiValue stored at the top level and we can place it in here as the value 204
that we'll pass over. 205
So now when our segue gets triggered, we'll pass over the bmiValue to our destinationVC which is 206
our ResultViewController and it should land in here. 207
Now, when this view loads up, we can set the bmiLabel's text property to equal the bmiValue. 208
Now, if we hit run, 209
you'll see that our calculated 210
BMI gets passed over and displayed in our result view controller. So now that you've seen how we can 211
use these segues to move forwards going from one screen to the next, 212
how do we go backwards? 213
How can we dismiss this screen and show the one previous? 214
So I'm going to delete this view controller because we're only working with these two. And you might 215
think that the solution is to create a segue wheel this way back, but actually there's a much easier 216
way. 217
If you go into your ResultViewController, the time point where we actually want to remove this 218
ResultViewController from the app, and instead show the previous screen which is our CalculateViewController 219
is when the user presses the recalculate button. 220
So inside this IBAction, we can simply tap into a method that, again, comes from our UIViewController 221
and we can call it by simply writing self.dismiss. 222
And this will dismiss the current view controller which is, of course, our result view controller. 223
So if we go ahead and insert that code, we can set the animated property to true, so that the transition 224
happens in an animated way, and we can set the completion to nil, so that nothing happens after we dismiss 225
it, we just go back to the previous screen. And if we now run our app, 226
you can see that we're able to go forwards by clicking on the calculate button which triggers the segue 227
and go backwards by clicking on the recalculate button which dismisses that second screen, 228
the ResultViewController. 229
Now, here's something I want to show you. when we create apps that have multiple screens, what essentially 230
happens is that the newer screens gets laid on top of the previous screens, like a stack of pancakes. 231
And if you want to see them in 3D, then you can simply make sure that your app is running, and then click 232
on this particular icon, 233
and it will show you what's currently displayed in the app. 234
But if you click and move, you can see it in 3D. 235
You can see that our ResultViewController is simply overlaid on top of the CalculateViewController. 236
So once we dismiss this view controller, then we go back to our previous screen. 237
You can also see where our labels lie on top. 238
And relative to other User Interface Elements, there's a really handy way of debugging what's going on 239
with our UI. So that's just a neat trick. Now that we've figured out how to go forwards by creating a 240
segue in the storyboard, and then triggering it by calling performSegue which comes from our SuperClass 241
that we're inheriting the UIViewController, and then we're able to prepare for that segue by setting 242
some properties in the destination of the segue. 243
And finally, were able to dismiss the presented view controller by calling the dismiss method which, again, 244
we have access to because it comes from the UIViewController which we're inheriting from. 245
Now, of course, Swift is smart enough to know that we're using the dismiss method from this current class. 246
So in fact, the "self" keyword is not necessary. In some cases, especially when we're creating initializers, 247
it's quite helpful to use the "self" keyword to make it easier to understand what's going on. 248
So it's up to you whether if you want to keep it or if you want to simply delete it and keep your code 249
clean. But that's how we can create segues and how we can go backwards and forwards and navigate 250
through our multiscreen app. 251
Now, in the next lesson, we're going to continue building out our app, and we're going to modify more of 252
our ResultViewController by changing the background color depending on the BMI, changing the advice, 253
and making our app conform to the MVC design pattern. 254
So for all of that and more, I'll see you on the next lesson.