Chapter 36: SimpleWeather

Learn all about communicating with APIs and using CoreLocation in this app that will provide current weather details in a beautiful UI.


What you will learn

  • Communicate with an API
  • Use URLSession to make web requests
  • How to use closures
  • How to work with JSON
  • How to use LocationServices

Key Terms

  • Closure
  • API
  • JSON
  • URLSession

Resources

Download here: https://github.com/devslopes/book-assets/wiki

In this chapter, we are going to build a simple weather app called SimpleWeather. It will display the weather at your current location with a beautiful UI that communicates with the open weather API system.

Here is a sneak peak at the finished product!

Figure 5.3.1

1.png

Creating an Xcode Project

Open Xcode and, from the File menu, select New and then New Project.... A new workspace window will appear, and a sheet will slide from its toolbar with several application templates to choose from. On the top, make sure to select iOS, and then look in the ‘Application’ section. From the choices that appear, select Single View Application and press the Next button (Figure 5.5.2). (Note that Apple changes these templates and their names often. But it should be very similar to what is shown here)

Figure 5.3.2

2.png

Then fill out the product name. I am calling mine ‘SimpleWeather’. You can select a Team and Organization Name. The organization identifier is usually in reverse DNS format. Make sure the language is Swift and select which devices you want your app to run on. We’ll keep it simple for this app and just do iPhone. We do not need Core Data, Unit or UI Tests for this chapter, so you can leave those unchecked (Figure 5.5.3).

Figure 5.3.3

3.png

Finally choose a location for your app to be saved to, and I recommend checking the ‘Create Git repository’ so that you can push your project to your Github (or Bitbucket) account and show off your awesome app to friends and potential employees!

Once the project loads, you will be looking at a brand new project that is just waiting to be turned into something great!

Setting up the Interface

With this app we are actually going to start out building the interface first since it is pretty simple. So go to your Main.storyboard file and first thing we are going to do is give the background a nice blue color. Select the ViewController in the interface builder, and change the background color to #51A4FF as seen below:

Figure 5.3.4

4.png

Next, referring back to figure 5.5.4 it looks like we have several labels and one image view. They are all lined up vertically, so I think this would be a good time to use our old friend stack views. So lets add the three labels on top of each other, then an image view, then one more label. They don't have to be placed exactly or anything, since we’ll be dropping them in a stack view.

Figure 5.3.5

5.png

Next, we’ll style the labels a little. Select all 4 labels and change the color to white, the font to Avenir Next, and the style to medium.

Figure 5.3.6

6.png

Next add titles to the labels as follows in Figure 5.5.7. This is just placeholder text to let us know how to space things out. Change the size of the second from top label to 64 and make the style bold. Change the size of the image view to 100 x 100.

Figure 5.3.7

7.png

Next select all elements, and add them to a stack view by clicking on the button indicated in Figure 5.3.8.

Figure 5.3.8

8.png

Next, with the stack view selected, make sure the Axis is Vertical, Distribution is Fill Proportionally, and Spacing is 10. Then give it alignment constraints of Horizontally and Vertically in container to center it.

Figure 5.3.9

9.png

And that’s our whole UI!

Now lets jump over to ViewController.swift file and make some IBOutlets and hook up those elements to our code. Just above the viewDidLoad function add the following:

@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var currentTempLabel: UILabel!
@IBOutlet weak var locationLabel: UILabel!
@IBOutlet weak var currentWeatherImage: UIImageView!
@IBOutlet weak var currentWeatherTypeLabel: UILabel!

Then as we have done so many times, go back into storyboard, and right-click on the ViewController in the project navigator and drag from the IBOutlets to the corresponding UI elements.

Figure 5.3.10

10.png

Now all our labels and elements are hooked up and we are ready to start talking about APIs!

OpenWeather API

What is an API? API stands for “Application Programming Interface” and provides a way for programmers to communicate with a certain application. There are many many useful APIs available. Some are from well known companies like Facebook, Google, Twitter and YouTube.

There are ones for getting information about StarWars, Pokemon, and the weather. What’s important to know is that, whatever API you use, you have to follow they rules. An API is able to provide only the specific information that it was built to provide, and the programmer must communicate with the API in a very specific way to get the results they want.

In this app we are going to be working with an API that can be found at ‘OpenWeatherMap.org’ Go ahead and check out the website, as we will be creating an account and using it extensively. On the website homepage you will see the Current weather data service description which includes:

•   Access current weather data for any location including over 200,000 cities
•   Current weather is frequently updated based on global models and data from more than 40,000 weather stations
•   Data is available in JSON, XML, or HTML format
•   Available for Free and all other paid accounts

That is the one we want. But first we need to sign up, so click the sign up button and follow the instructions:

Figure 5.3.11 11.png

Figure 5.3.12

12.png

Figure 5.3.13

13.png

You will land on a dashboard, then select the API keys menu option. Your API key is very important, without it, you would not be able to use the service. This is a common practice with APIs and is used to track usage for APIs that charge. Don’t worry though, unless you switch to a paid plan you will not be charged for your account. Now copy your API key and navigate back to the API Page:

Figure 5.3.14 14.png

Find the ‘Current Weather data’ section and click on the ‘API doc’ button.

Figure 5.3.15

15.png

Then scroll down to the header ‘By city name’ and click on the first example link.

Figure 5.3.16 16.png

You will get an error saying “{"cod":401, "message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info.”}” This means that the API key is not valid. Thats to be expected, because that is just the default API key open weather uses. Go ahead and click on the URL in your browser and at the end of the url you will see a long string of numbers. This is the bad API key.

Figure 5.3.17 17.png

Go ahead and replace that API key (everything after ‘&appid=‘) with your API key we copied earlier, and reload it. You should now see a bunch of information that may seem difficult to read. If you look in the URL you will ‘london,uk’. If you want, you can change the city and get different results.

Figure 5.3.18 18.png

So what you are looking at here, is what is called JSON, which stands for JavaScript Object Notation. It is defined as a “lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate.”

Now it says its easy for humans to read, but let’s make it a little easier shall we. Copy everything, and google ‘JSON formatter’ and I like this first one at https://jsonformatter.curiousconcept.com

Figure 5.3.19 19.png

Paste the raw JSON data into the JSON Formatter window and press Process:

Figure 5.3.20 20.png

This will take you down to the formatted JSON view as seen below:

Figure 5.3.21 21.png

Now let’s take a closer look here. JSON is arranged with keys and values. The keys are strings, and the values could be objects like strings, ints, or bools.

So taking a look at Figure 21 you see that it is arranged in a kind of nested format. There is an opening parentheses that contains the entirety of the data. Then within that there are other groups of data. For instance, there is a key of “weather” that has a value of type array. That array has within it keys with values. Among which is the key “main” and the value of “clouds”. A little further down is the key “main” with a dictionary of keys and values. One of which is “temp”. BINGO! We have located our temperature within this jumble of JSON.

You can kind of think of JSON like a Russian nesting doll, only instead of one doll in each, there may be many. The outermost doll is the entire data set. Then you open it up and you might find the doll called “main” and you find a bunch of other dolls inside it, then you find the doll named “temp” open it and find the value of 283.157. And thats what you do for every value you are looking for.

Scroll through the formatted JSON and familiarize yourself with it. Most APIs these days will return JSON data. And the way to request that data is through a web request. Just like you entered the url with your key into the web browser to get this data, we are going to make a web request from your app to retrieve this JSON data, extract the necessary information, then display the data in our UI.

Remember in the URL you included the city information? You can imagine how you could have a variable for a city, then include that in the API request to retrieve the information for a specific city. We are going to actually use your current location to do that, but we’ll get into that later.

So let’s jump back into Xcode and get working on it! Create a new Swift file and name it CurrentWeather. This is going to be our custom class that contains all the class properties and API call. So what are we going to need? We will need private variables and getters for the city name, the date, the weather type, and the current temperature. Add this to the CurrentWeather file:

class CurrentWeather {
    fileprivate var _cityName: String!
    fileprivate var _date: String!
    fileprivate var _weatherType: String!
    fileprivate var _currentTemp: Double!

    var cityName: String {
        if _cityName == nil {
            _cityName = ""
        }
        return _cityName
    }

    var date: String {
        if _date == nil {
            _date = ""
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .long
        dateFormatter.timeStyle = .none
        let currentDate = dateFormatter.string(from: Date())
        self._date = "Today, \(currentDate)"
        return _date
    }

    var weatherType: String {
        if _weatherType == nil {
            _weatherType = ""
        }
        return _weatherType
    }

    var currentTemp: Double {
        if _currentTemp == nil {
            _currentTemp = 0.0
        }
        return _currentTemp
    }
}

The city name, weather type, and temperature are straight forward. We declare the properties, and then create the getters and do a check to see if the value is nil, and if so, we set it equal than empty string so we don’t get any crashes.

The date is a little more involved so lets break that down. First we create a DateFormatter(). This is a class that helps create string representations of NSDate objects. It has a number of functions as seen in the next two lines. First we set the .dateStyle = .long which specifies a long style, typically with full text, such as “October 23, 2016”. Then since we are not really interested in the time we set .timeStyle = .none. Then we create the date by using dateFormatter.string(from: Date()) where we take the current date as created by Date() and turn it into a string based on the dateFormatter parameters we set.

Setting up the API Call

Now that we have our current weather properties we need some way to get the data and assign it to our class properties.

But first we need to think about how this is going to work. We could put the API call directly in the ViewController file, assign the properties, then update the UI. But it is not considered best practice to have the controller also be retrieving and managing data. So we want the API call to be in the CurrentWeather custom class.

But that introduces a new challenge. Web requests are asynchronous. For example, suppose I have three lines of code A, B, and C. And B is a web request that retrieves information, and C is the function that takes that information and updates the UI. But because B is asynchronous, we don’t know when that information will be available. It could be milliseconds, it could be minutes, depending on connection speed and the service or site being contacted. So then, B fires off the web request, and C immediately tries to use information that may or may not be there, potentially causing crashes.

So what we need is a way to know when a web request has completed, and we do that by way of closures. A closure is basically a block of code, that is self contained and can be passes around in your code, and that we can call at a pre-determined time. And we are going to create a custom closure that we can use to determine when the download is complete.

So create a new Swift file, and call it Constants. And add the following:

typealias DownloadComplete = () -> ()

The syntax to create a closure is var closureName: (ParamterTypes) -> (ReturnType)

Our custom closure, does not have any parameter types, nor are we returning anything hence the empty parentheses. And typealias is just a way of renaming an existing type.

Now we are ready to create our function that we will eventually call in the ViewController file that will update the UI. So go back to CurrentWeather.swift and add the following below your getters:

func downloadWeatherDetails(completed: @escaping DownloadComplete) {

}

We name our function, then we name our closure completed and then we include our custom closure DownloadComplete. But first we have the @escaping. An escaping closure is often used in asynchronous operations like the web request we are going to be dealing with.

So lets build that web request!

First we need to have a URL to point to. At this point we are just going to call it CURRENT_WEATHER_URL. This will go inside the downloadWeatherDetails function we just created. Later on we will add the URL to the Constants file when we are ready.

let url = URL(string: CURRENT_WEATHER_URL)!

Then add let session = URLSession.shared . The URLSession class is the Apple class that provides the API for downloading content. It provides all the necessary delegate methods we will need later on such as serializing JSON data and capturing the data and error responses.

Then we need to call a dataTask which retrieves the contents of our URL, and supplies the data, response, and error in a completion handler. When you auto complete session.dataTask there will be code completion prompts, press return, then rename the data, response, and error parameters. The .resume() is necessary to kick off the desk of making the request.

So our downloadWeatherDetails function should now look like this:

func downloadWeatherDetails(completed: @escaping DownloadComplete) {

    let session = URLSession.shared
    let url = URL(string: CURRENT_WEATHER_URL)!

    session.dataTask(with: url) { (data, response, error) in


    } .resume()
}

Next we need to take the response from the web request, and turn it into useable JSON data. So we are going to use the JSONSerialization.jsonObject method as follows.

session.dataTask(with: url) { (data, response, error) in
    if let responseData = data {
        do {
            let json = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments)
            print(json)
        } catch {
            print("Could not serialize”)
        }
    }
} .resume()

The JSONSerialization.jsonObject can fail, so it needs a do-catch block. If it fails, we’ll simple print out that it could not serialize. Now we have an object JSON that contains all the information from the point of API call and can start drilling down into the JSON to acquire the information we need.

But first we need an actual url. If you recall CURRENT_WEATHER_URL was just a placeholder. So lets go back to our API website http://openweathermap.org/api and return to the ‘Current weather data’ API docs.

Figure 5.3.22 22.png

Then scroll down to the ‘By geographic coordinates’ section. If you recall, we want our app to give us the weather based on our current location. This section provides us with the information we need to make that type of call. It provides the base API call:

api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}

Tells us what parameters we need, lat and lon (latitude and longitude).

And gives an example of an API call:

api.openweathermap.org/data/2.5/weather?lat=35&lon=139

Go ahead and plug that into your browser, and remember to replace the invalid API key at the end with your own.

This will provide the following JSON data:

{
    "coord": {
        "lon": 138.93,
        "lat": 34.97
    },
    "weather": [{
        "id": 803,
        "main": "Clouds",
        "description": "broken clouds",
        "icon": "04n"
    }],
    "base": "stations",
    "main": {
        "temp": 295.139,
        "pressure": 1021.33,
        "humidity": 100,
        "temp_min": 295.139,
        "temp_max": 295.139,
        "sea_level": 1030.63,
        "grnd_level": 1021.33
    },
    "wind": {
        "speed": 2.11,
        "deg": 68.0001
    },
    "clouds": {
        "all": 68
    },
    "dt": 1477077361,
    "sys": {
        "message": 0.1611,
        "country": "JP",
        "sunrise": 1476996953,
        "sunset": 1477036867
    },
    "id": 1851632,
    "name": "Shuzenji",
    "cod": 200
}

So let’s copy the base API call and paste it into our Constants file as follows, including the required http:// portion of a URL.

let CURRENT_WEATHER_URL = “http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}”

Then from playing with the URLs in the browser, we know we need to end with &appid= then your API key.

So now your URL should look something like:

let CURRENT_WEATHER_URL = “http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid=42a1771a0b787bf12e734ada0cfc80cb”

We’re getting closer now. But you see that we now need to know the longitude and latitude parameters to pass into the URL. So lets work on that next.

Getting Current location

Our location anywhere on the planet can be determined by latitude and longitude. Your phone has GPS (global positioning system) and our app can take advantage of the phone GPS by using the Apple library CoreLocation.

Let’s create another custom class that will hold our current location. Create a new Swift file and name it Location and add the following:

import CoreLocation

class Location {
    static var sharedInstance = Location()
    private init() {}

    var latitude: Double!
    var longitude: Double!
}

First we import the CoreLocation library that is necessary to provide the required methods and delegates we will need.

The we create a static sharedInstance of the Location class and set up an initializer. And the only two properties we need in this class are latitude and longitude. So we are good here!

Next we need to go to our ViewController.swift file and get ready to calculate our current location when the app opens.

First up, import the CoreLocation library and add the ‘CLLocationMnagerDelegate’.

import CoreLocation

class WeatherVC: UIViewController, CLLocationManagerDelegate {

Then we need to instantiate and declare a locationManager and a currentLocation. Add these right below the IBOutlets and above the viewDidLoad function.

let locationManager = CLLocationManager()
var currentLocation: CLLocation!

So, what is a locationManager? Apple defines it as “the central point for configuring the delivery of location- and heading-related events to your app. You use an instance of this class to establish the parameters that determine when location and heading events should be delivered and to start and stop the actual delivery of those events. You can also use a location manager object to retrieve the most recent location and heading data.”

So basically it is in charge of all the location acquisition, parameters like accuracy, requesting permission to use GPS, and more.

currentLocation is a variable of class CLLocation which represents the location data generated by the locationManager

Next we need to add some settings for our locationManager so in viewDidLoad add the following:

locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startMonitoringSignificantLocationChanges()

First we are setting the delegate like we are used to doing, then we are choosing the desired accuracy which we are selecting the best available. The next line locationManager.requestWhenInUseAuthorization() generates the pop up that asks the user if they will allow our app to access their location. Then we have locationManager.startMonitoringSignificantLocationChanges() which monitors location changes and will update the locationManager delegates as needed.

Next we need to add two more functions. The first one is a method of locationManager and tells the delegate whether or not the app has permission to use the location services.

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    locationAuthStatus()
}

We also want to call this function whenever the view appears in viewDidAppear(_ animated:), so that the temperature will be updated:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    locationAuthStatus()
}

locationAuthStatus() is a function we create that checks whether or not the app has permission to use location services. If it does, then we will set the current location, extract the latitude and longitude and set those equal to the custom class Location properties for latitude and longitude. If we do NOT have access, then we will request access.

func locationAuthStatus() {
    if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
        currentLocation = locationManager.location
        Location.sharedInstance.latitude = currentLocation.coordinate.latitude
        Location.sharedInstance.longitude = currentLocation.coordinate.longitude

    } else {

        locationManager.requestWhenInUseAuthorization()

    }
}

We also need to add permissions to our info.plist so head over there, hover over the last entry until a ‘+’ sign pops up. Click on it to create a new entry and start typing ‘Privacy-Location’ and select the one that says ‘Privacy - Location When In Use Usage Description’ and then to the right, under ‘Value’ add a message that will be displayed to the user when it opens, something like “We need your location to give you relevant, up-to-date weather information.”

While we are in the info.plist we also need to add permissions for web requests to non-https websites, as OpenWeather is. So again, hover over the last entry in the plist, and press the ‘+’ button. Select “App Transport Security Settings” then click the small arrow on the left that is pointing to the right, so that it points down, and click on the ‘+’ button again, which will create a sub entry. Here select ‘Allow Arbitrary Loads’ and set the value to YES. Now our app is able to communicate with any website.

So, remember where we were when we started down this rabbit hole? Let’s recap. We created a custom class for CurrentWeather, we started implementing the API call using URLSession, and we got to the point we are ready to make the call, so then we went to our API documentation and saw we need to know a longitude and latitude, so we created a Location custom class, then implemented the code to request access to location services, and if granted, set the current location latitude and longitude to our instance of custom class Location.

Whew! So now, we can circle back to our Constants file and insert the longitude and latitude parameters from our Location instance, and it should now look something like this:

let CURRENT_WEATHER_URL = “http://api.openweathermap.org/data/2.5/weather?lat=\(Location.sharedInstance.latitude!)&lon=\(Location.sharedInstance.longitude!)&appid=42a1771a0b787bf12e734ada0cfc80cb"

except with your API key at the end.

Lets head back to our CurrentWeather file. We have our URL and the next thing to do is extract the desired values from it. So lets take a look at the example result we had earlier, only I'm going to throw it into the JSON formatter we talked about earlier:

{
   "coord":{
      "lon":138.93,
      "lat":34.97
   },
   "weather":[
      {
         "id":803,
         "main":"Clouds",
         "description":"broken clouds",
         "icon":"04n"
      }
   ],
   "base":"stations",
   "main":{
      "temp":295.139,
      "pressure":1021.33,
      "humidity":100,
      "temp_min":295.139,
      "temp_max":295.139,
      "sea_level":1030.63,
      "grnd_level":1021.33
   },
   "wind":{
      "speed":2.11,
      "deg":68.0001
   },
   "clouds":{
      "all":68
   },
   "dt":1477077361,
   "sys":{
      "message":0.1611,
      "country":"JP",
      "sunrise":1476996953,
      "sunset":1477036867
   },
   "id":1851632,
   "name":"Shuzenji",
   "cod":200
}

We want the name of the city, the temperature, and the weather conditions. Remember our analogy of the Russian nesting dolls? We open the first doll and we see all this, inside it is a doll with the label “name” on it and inside it is the value of the city. In this case “Shuzenji”. For the weather type we have to go down two levels, first to the key “weather” then to the key “main” which has the value of ‘Clouds’.

So are you seeing how you drill down to the values you want?

Lastly we need the temperature which is in ‘Main’ then down one more level to ‘Temp’ with the value of 295.139.

So that is the path you take looking at it visually, but what does that look like in code. Inside our downloadWeatherDetails function we have created our session, data task, and serialized the response data into JSON. So the next step is to turn that JSON into a dictionary, then use implicit unwrapping to unwrap and cast to dictionaries one layer at a time until we get the values we need. So modify your downloadWeatherDetails as follows. These additions are inside the do block.

if let dict = json as? Dictionary<String, AnyObject> {

  if let name = dict["name"] as? String {
      self._cityName = name.capitalized
  }

  if let weather = dict["weather"] as? [Dictionary<String, AnyObject>] {

      if let main = weather[0]["main"] as? String {
          self._weatherType = main.capitalized
     }

  }

  if let main = dict["main"] as? Dictionary<String, AnyObject> {
      if let currentTemperature = main["temp"] as? Double {
          let kelvinToFarenheitPreDivision = (currentTemperature * (9/5) - 459.67)
          let kelvinToFarenheit = Double(round(10 * kelvinToFarenheitPreDivision/10))
          self._currentTemp = kelvinToFarenheit
     }
  }
}

We start out by converting the JSON into a dictionary of type <String, AnyObject>. Then just like we discussed above, we drill down to each level, implicitly unwrapping as we go, until we reach the desired value, at which point we set it equal to the CurrentWeather property.

The temperature is a little more involved as it requires some math to convert Kelvin to Fahrenheit. Next we need to add one last thing related to the closure.

Remember how we named the closure completed? This is what determines when the asynchronous call is done. When we call this function from the ViewController it is going to look for when it reaches completed() then execute the next block of code. So we need to place it inside the request and after all the JSON has been assigned to the class properties. One level inside the .resume() should be good. So your final downloadWeatherDetails function should look like this:

    func downloadWeatherDetails(completed: @escaping DownloadComplete) {

        let session = URLSession.shared
        let url = URL(string: CURRENT_WEATHER_URL)!

        session.dataTask(with: url) { (data, response, error) in

            if let responseData = data {

                do {
                    let json = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments)

                    print(json)
                    if let dict = json as? Dictionary<String, AnyObject> {

                        if let name = dict["name"] as? String {
                            self._cityName = name.capitalized
                        }

                        if let weather = dict["weather"] as? [Dictionary<String, AnyObject>] {

                            if let main = weather[0]["main"] as? String {
                                self._weatherType = main.capitalized
                            }

                        }

                        if let main = dict["main"] as? Dictionary<String, AnyObject> {
                            if let currentTemperature = main["temp"] as? Double {
                                let kelvinToFarenheitPreDivision = (currentTemperature * (9/5) - 459.67)
                                let kelvinToFarenheit = Double(round(10 * kelvinToFarenheitPreDivision/10))
                                self._currentTemp = kelvinToFarenheit
                            }
                        }
                    }

                    print(json)
                } catch {
                    print("Could not serialize")
                }
            }
            completed()
        }.resume()
    }

It’s a big one!

Now lets go back to our ViewController.swift file and add the final touches.

Above the viewDidLoad function declare a currentWeather variable:

var currentWeather: CurrentWeather!

Inside viewDidLoad instantiate it:

currentWeather = CurrentWeather()

And the inside the locationAuthStatus function modify as follows:

    func locationAuthStatus() {
        if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
            currentLocation = locationManager.location
            Location.sharedInstance.latitude = currentLocation.coordinate.latitude
            Location.sharedInstance.longitude = currentLocation.coordinate.longitude
            currentWeather.downloadWeatherDetails {

                DispatchQueue.main.async {
                    self.updateMainUI()

                }

            }
        } else {

            locationManager.requestWhenInUseAuthorization()

        }
    }

We are adding currentWeather.downloadWeatherDetails which will then call an async task that will then call a function updateMainUI which we will create next. And we don’t have to worry about crashes due to trying to assign variables that don't exist, all because of our use of closures!

In our updateMainUI function, all we are going to do is assign our UI elements IBOutlets to the properties of the custom class. The current weather image is assigned based on the image title, which we cleverly named the same as the weather type returned by the API call. So find those images in the resources, and drop them into your Assets folder.

Figure 5.3.23 23.png

    func updateMainUI() {
        dateLabel.text = currentWeather.date
        currentTempLabel.text = "\(currentWeather.currentTemp)"
        currentWeatherTypeLabel.text = currentWeather.weatherType
        locationLabel.text = currentWeather.cityName
        currentWeatherImage.image = UIImage(named: currentWeather.weatherType)
    }

And that seals the deal. Run it, and if you are on the simulator it will probably default to Cupertino. If you are not getting anything, check that there is a location set in the menu Debug > Location while in the simulator. If you are on device it should give your current location temperature and weather conditions.

Wrapping up

So let’s recap what we’ve learned in this chapter. We have learned a little about how APIs work, how to contact them using URLSession to make web requests, how to work with JSON and how to use CoreLocation. Quite a lot for such a simple app! Now I want to give you a challenge to think of some ways you could expand on this app and make it even more awesome! The OpenWeather API has more options for getting a ten day forecast, high and low temperatures, and much much more.

Exercise

Sometimes you don't want to know the weather conditions in your current location, but want to check the weather in a different city. You exercise is to add a button that takes you to a new view controller where you can enter in a city and return the weather details for that city. The weather API we have been dealing with allows you to do this quite easily. You will just need to modify the existing API call slightly.

Screen Shot 2016-12-01 at 9.50.39 AM.png

Happy coding!