All right. 1
So now that our app is actually pretty much fully functional, other than the fact that we still don't 2
have the location data and we can't get the weather for the current location. 3
So that's what we're going to tackle in this lesson. 4
Now, the first thing we need to do to be able to get hold of the location data is to import a package 5
that Apple has created called CoreLocation. 6
And this comes with something called a location manager. 7
So we can create a locationManager here and it's going to be created from 8
the CLLocationManager class. 9
And this object that we've created, our locationManager is going to be responsible for getting hold 10
of the current GPS location of the phone. 11
But before we can use it though, the first thing we have to do is we have to trigger a permission request. 12
So we're going to say requestWhenInUseAuthorization. Because the user's location is a really private 13
piece of data, 14
you don't want apps to just know exactly where you are without having to ask for explicit permission. 15
So when we call this locationManager.requestWhenInuseAuthorization method, 16
wow, what a mouthful, 17
then it will show a pop up on screen that asks the user for permission. 18
But in addition to calling this method, we also have to go into the info.plist, which is the property 19
list where we've got a bunch of key-value pairs that determine some of the basic settings of our app. 20
And here, we're going to add a new property. 21
And if you scroll down in this list, you should be able to find one that says, "Privacy - Location When in 22
Use Usage Description. 23
And for this key, we're going to give a value that's going to be the message that we're going to tell 24
the user why we need their location. 25
So, we'll say, "We need your location to get the current weather for where you are." 26
So, now that we've got a Privacy - Location When in Use Usage Description, we can now head back and run our 27
app. 28
And as soon as the WeatherViewController loads up, you should see a pop up which is in black 29
if you're in dark mode, like I am, or in white if you are not in dark mode. And it'll ask you, "Do you want 30
to allow 'Clima' to access your location?" 31
And then here is that little string that we added earlier on in our plist, 32
"We need your location to get the current weather for where you are." 33
So make sure that while you're testing, you always click on Allow because, otherwise, you're going to have 34
to dig into the settings, the privacy and location settings, to allow the Clima app to access the location. 35
So now that we've gotten permission from the user to get their location, well, then the next step is to 36
use the locationManager to request location, and this method "Request for a one-time delivery of the 37
user's current location." 38
And because we're building a weather app here where we don't need to track where the user is or where 39
their car is, 40
so if you were building a navigation app or a fitness app where you needed the turn by turn location 41
data the entire time that they're using the app, then you might want to use something called 42
startUpdatingLocation instead 43
and this basically monitors their location and constantly reports back when it updates. But in our case, 44
the request location is sufficient. 45
But how do we actually get hold of that location once the location manager has found it? 46
Well, notice how requests location doesn't return anything. 47
In fact, the only way to get a hold of it is through the locationManager(_:didUpdateLocations:) method. 48
And this is what the locationManager is going to call once it finds the location. So given now that 49
you know what the delegate method is that you need to implement and you know all about protocols and 50
delegates, here's a challenge for you. 51
See if you can implement the required protocols and simply be able to print got location when the 52
locationManager triggers the didUpdateLocationsDelegate 53
method. Also, try to use a Swift extension to split out the didUpdateLocation method from 54
the WeatherViewController. 55
Now, printing out got location can actually be a little bit harder than it sounds. 56
Make sure you have a read of the documentation to figure out how to do this successfully. 57
So pause the video and see if you can complete this challenge. 58
All right. 59
So firstly, we know that this method, once it gets hold of the location, is going to trigger this delegate 60
method. 61
And this delegate method if we click on it, shows us that it comes from the protocol 62
CLLocationManagerDelegate. 63
So we have to adopt this delegate protocol in our WeatherViewController if we want to be able to assign 64
ourselves as the delegate for the locationManager to be notified of that location update. 65
So we can either add our CLLocationManagerDelegate here, alternatively, as we saw in the last lesson. 66
We could simply create a brand-new extension down here which extends our WeatherViewController. 67
And here, we can add our CLLocationManagerDelegate, so that we split out this part of the functionality 68
of our WeatherViewController and we can mark it out with MARK comment, and we can call this 69
the 70
CLLocationManagerDelegate. 71
So you'll often see that people will split out their view controllers with sections that are named based 72
on the delegate protocols that they are adopting. 73
So this action is obviously related to the location manager, and the delegate method was called 74
didUpdateLocations, location manager did update locations. 75
So if we hit enter, that should enter that method for us. 76
Now, you have to remember that in order to actually get the notification for this, we have to set our 77
WeatherViewController, 78
so this current class, as the delegate for the locationManager. Without this property set, nothing is 79
going to happen even if you adopt the protocols and have the delegate methods. 80
So, now that everything is all set, we can simply print "Got location data" and then go ahead and hit run. 81
And at this point, your app will crash. 82
And if you scroll right to the top, you'll see that you have an error saying, "Terminating apps," so your app 83
crashed, "because of a reason the delegate must respond to locationManager:didUpdateLocations." 84
And you're wondering, "Hey, wait a minute. I did actually respond to locationManager:didUpdateLocation. 85
It's right here. 86
What's going on? 87
Well, the reason is because we have to set the current class as the delegate to be able to register with 88
the locationManager and to be able to respond when requestLocation actually triggers this method. 89
So what you'll find is that if you have the locationManager.delegate = self set after you 90
trigger this method, you're going to end up crashing your app. 91
Now, in addition, if you take a look at the quick docs for request location, it says right at the bottom: 92
"When using this method, the associated delegate must implement the didUpdateLocation and 93
didFailWithError methods. 94
Failure to do so is a programmer error." 95
So in addition to having this locationManager, didUpdateLocationsDelegate method implemented, 96
we also must make sure that we have our locationManager didFailWithErrorMethod implemented. 97
And in this method, all I'm gonna do is just print the error 98
if there were any. 99
And that's the solution to the challenge. 100
The trick of this challenge was to make sure that you set the WeatherViewController as the delegate 101
before requesting the location and also to make sure you've implemented the required didFailWithErrorDelegate 102
method. 103
So, now if we run our app, 104
then you should see "Got location" printed in the console because the locationManager has found the location 105
and they are triggering this method. 106
But where's the permission request? 107
Where's the pop up? 108
Because we've already agreed to share our location to this app, 109
we don't get that pop up ever again. 110
We only see it once. And once you've agreed to it, then you don't need to touch it anymore. 111
If you ever need to change it, then you can go into the settings of the simulator or your phone privacy 112
location services. And here under your app Clima, you can see "Never," "Ask Next Time," or what we've agreed 113
to at the moment "While using the app." So what if instead of printing "Got location data," we can actually 114
print out the actual location of where our simulator is located, 115
terms of the longitude and latitude. 116
How would we do that? 117
Well, notice how when the locationManager calls this method, it also passes in this "locations" input. 118
And this is an array of CLLocations. We can get hold of the last location that they added in there. 119
So we'll just call it location and we'll set it to equal locations.last. 120
So that's the last item in the array. 121
And generally, the locationManager will try to get the current GPS location and hone in on a relatively 122
accurate version of it, 123
so you might end up with several locations. By fetching the last location, 124
we should be able to get the most accurate one. But because this locations.last is an optional, 125
in order to use it, we should first optionally bind it to a new constant called location, so that we unwrap 126
it. And then within this block, we can create a latitude which will set to equal to location.coordinate.latitude, 127
and then we can create a longitude which is going to be the location.coordinates.longitude. 128
- 129
And now let's try and print out those values: the lat and lon. 130
So let's hit run and see what happens. 131
And there it is. 132
I can see my current location which is London and I've got a latitude of 51.5, 133
a longitude of -0.1. 134
Now, you might be wondering, how are you getting a location data for the simulator? 135
Well, if you're going to debug, there's a section called Location. And here you can set a location for the simulator. 136
I've set mine to Custom Location and added the coordinates for London. 137
You can also change the simulators location, say, to Apple's headquarters. 138
However, the simulator doesn't always pick up the location change right away. 139
And in order for that to go through, you also have to head over to Home. and go ahead and delete that 140
Clima app. In order to run it again, and it will now report the updated latitude and longitude. 141
And if you want to test this, you can also set a custom location with a custom latitude and longitude 142
as well. 143
But again, you'll have to go through the process of deleting the app and rerunning it again. 144
So, now that we've got a latitude and longitude, if we head over to the OpenWeather Map API Docs, 145
you can see that in order to get the current weather data, we can either go by city name or we can also go 146
by geographic coordinates which is what we've got at the moment. 147
And the way to make that API call is using the same URL as what we used previously. 148
But then at the end, we tag on two parameters: a lot and a lon. 149
And notice how the lon is spelled a little bit 150
weirdly. it's L-O-N, rather than L-O-N-G. 151
So in order for our API call to work, we have to be able to match their parameter names. 152
Let's first test it out in the browser. 153
Let's go ahead and go into our WeatherManager and get hold of our weatherURL with our app ID, 154
and our units already set, and then I'm going to paste it into the browser bar, and then I'm going 155
to add another parameter by adding the ampersand symbol, and the first one was called lats will set lat 156
equal to, let's say, 51, which is roughly around where I am. 157
And then let's add another one with the ampersand and this is going to be lon with L-O-N, 158
and this is gonna be equal to -0.1 which is going to be roughly around London. 159
So now if we hit Enter, you can see that we're getting our data back. 160
It's clear skies with a temperature of 19.4. 161
Perfect. 162
Now that it's working, let's try and figure out how we can form this URL string in our code. 163
So down here, we've already got a fetchWeather method that takes a cityName and creates a 164
urlString from it. And then performs the request with that urlString which goes into this method 165
and does the networking using URLSession to fetch some data back from OpenWeather Map. And then 166
we parse the data, and then we create a weather object to pass back to our delegate. 167
So this part is actually completely reusable. 168
It's exactly the same process. 169
So all that we need is another fetchWeather method. 170
So if we go into our WeatherViewController, and right where we get back our latitude and longitude, 171
so I'm going to delete those two print statements, 172
instead, I'm gonna call my weatherManager and I'm going to get it to fetchWeather, but instead of using 173
a cityName parameter, I want to be able to use the latitude parameter and put in the lat constants that I've 174
just got, 175
and then the longitude, which is going to have the long property. And I want to be able to call this method 176
in order to get some weather data. And when this goes through successfully and I actually manage to get 177
the data, then the data is going to come back inside my didUpdateWeatherDelegate method. And it's 178
here where it's going to update our temperatureLabel and conditionImageView without any extra code 179
on our part. 180
So this is a time for your challenge. 181
The challenge is to make this method work and get rid of all of the errors. Write the method 182
fetchWeather with latitude and longitude in the WeatherManager, so that when you run the app, it actually gets the 183
weather for your current location. 184
So pause the video and try that now. 185
All right. 186
So let's go ahead and copy this method and we're going to implement it in our WeatherManager. 187
So down here, I'm going to create another function called fetchWeather. And Swift is totally cool with 188
methods which have the same name as long as they have different parameter names. 189
So this fetchWeather method is going to have the latitude and longitude parameter names and it's going 190
to take two pieces of data. 191
Now, how can we figure out what data type we are going to send over? 192
Well, if we head back to our WeatherViewController and we hold down option and click on latitude or 193
longitude, then you can see the data type of the latitude and longitude. 194
It's something called Core Location LocationDegrees. 195
So CLLocationDegrees. 196
So let's go back to our WeatherManager and assign that as the data type that we're expecting, 197
CLLocationDegrees for the latitude and CLLlocationDegrees for the longitude. 198
And once we get hold of the latitude and longitude, what we're going to do is we're going to create a 199
URLString, and it's going to be equal to an interpolated string that's created the first part being 200
the weatherURL, and then at the end, we're going to tag on the property lat which is going to 201
equal latitude that we got passed in. 202
And then the second property that we need to add is the lon, and remember to check that you've spelled 203
it exactly the same as what I've got here, 204
L-O-N and L-A-T. 205
This is a really common place where students fall down. And then we're going to set it equal to, making 206
sure that there's no spaces anywhere the longitude that got passed over from our location manager. 207
Now that we're done with our urlString, all we have to do is trigger performRequest with 208
our urlString. And it will go through the same process as it did before with the cityName to network 209
and pass the data, and then trigger our delegate method didUpdateWeather which will update our temperatureLabel 210
and our conditionImageView. 211
So let's hit run and see this in action 212
So you saw it immediately update with the location-based temperature and condition. 213
And now if I go ahead and type a city, say, Paris, you can see that in Paris, it's thunderstorms. That also 214
works. But how do we get back to our location-based weather? And how can we update our cityLabel to be 215
able to tell us which city we're currently in when we're getting our location data? And I want to pose 216
this as a challenge to you. 217
Can you write the last part of the app on your own? Link of the button in the top left, so that when it 218
gets pressed, we get the weather for the current location displayed. Also, the label should update to show 219
the name of the current location. On the other hand, when we type in a cityName, we get the weather for 220
that location and the label will display the name we get back from OpenWeather Map. Linking up the button 221
will be the tricky part. So check the documentation in Stack Overflow 222
to figure this out. I'll give you a few seconds to pause video before I show you the solution. 223
All right. 224
Here's the solution. 225
Let's tackle updating the cityLabels first. 226
Now, if we take a look at the data that we get back from OpenWeather Map, you can see that right at the 227
bottom, we get the name of the location based on the latitude and longitude. 228
So if I go ahead and update my latitude and longitude to my current location, then you can see that it's 229
telling me that I'm currently inside the city of London. 230
So we've already managed to get hold of that name property here. 231
All we need to do is inside our WeatherViewController where we're updating our labels, we should go 232
ahead and set the self.cityLabel.text to equal the weather.cityName property. So now 233
that should populate our citylabel. 234
Let's give it a run, see if that works. 235
And as you can see that the label is now updating using the location name from the JSON that we got 236
from OpenWeather Map. 237
At the moment, I still got my simulator set to Cupertino. 238
Let me change the simulator back to London using the custom coordinates. 239
And now if I simply just quit the simulator and then rerun the app, you can see that it's now updated 240
to use that current location. 241
So switching different location types in the simulator can be quite painful. But hopefully, if you're 242
running this on a physical device, you'll actually be getting your exact current location. So, now all 243
that's left is to be able to switch back to location mode when we have gone to the city mode. 244
So right now I've got my weather for Paris and I want to be able to click on this button to go back 245
to my current location's weather. 246
So in order to do that, the first thing we need to do is to create an IBAction. 247
Here let's create an IBAction that links this button down into our WeatherViewontroller 248
and I'm going to call this action locationPressed, and then change the data type into UIButton. 249
And now I've got my IBAction locationPressed where I can change my app to, again, 250
use the locationManager's location to get the weather. But how do I trigger this 251
locationManager, didUpdateLocations method again? 252
Well, one way of doing it would be to call this requestLocation method again. 253
But if we've already requested the location and we haven't changed our GPS location, then if there's 254
no change, then there's gonna be no update. 255
So this method is not going to get called. 256
And you can confirm this by simply adding a print statement inside the locationManager. 257
So the way to solve this is we want to be able to call locationManager.requestLocation and trigger 258
a reupdate and call this method again so that we fetch the weather. 259
But in order for this to work, once we've gotten hold of the weather, either at the time when we've loaded 260
up our app or at the time where they press the location button, we want to be able to stop the 261
locationManager. 262
So we're gonna say locationManager.stopUpdatingLocation as soon as it's gotten hold of a location. 263
So now the sequence of events is, first, we set our locationManagersDelegate as ourself, then we call 264
requestLocation, locationManager is gonna be hard at work fetching the current location. 265
And once it does, it calls this method passing in the locations that it's found, and we look inside that 266
array to find a location. 267
And as soon as we found one, then we tell the locationManager to stop and we use the location to get 268
the current weather. 269
So at a later point, if we press on the locationPressed button, then it calls this method again. 270
And now our locationManager has been activated once more, so we can call this delegate method again. 271
Let's give that a go and see if it works. 272
The first thing that we see is the current location's weather. And now I'm going to search for another 273
location's weather, see what it's like in Beijing. And now if I want to switch back to location mode, 274
I press on this button and I trigger the request location once more where the locationManager calls 275
didUpdateLocation and I fetch the weather again. 276
So that's it. 277
We've managed to create our weather app that uses the current location to get weather, as well as using 278
the city name that the user types in through the text field. And in the process, we've learned about text 279
field, protocols and delegates, extensions, networking with URL session, as well as parsing JSON data. 280
So it's been a pretty jam-packed module if you ask me. And it might be that you might have to review 281
certain parts of this module over the coming days to be able to really digest it and make it your own. 282
But once you've done that, head over to the next lesson and there'll be a challenge, as usual, for you. 283
And I'll see you on the next module.