We have our forecast JSON from Dark Sky, but we want to parse that into a natural Swift object. We’ll create a new file called WeatherData.swift and start it off with a simple struct:

import SwiftyJSON

struct WeatherData {
    
    var temperature: String
    var description: String
    var icon: String
    
}

In Swift, a struct is a special type of object. They’re passed around by value instead of by reference and they have some performance benefits. They make a lot of sense for small objects like this WeatherData.

We want this object to be able to initialize itself from the JSON object returned by Alamofire. We can create a new initializer that starts to access the data:

init(data: Any) {
    let json = JSON(data)
}

The JSON object that we saw in the Alamofire response looked like this:

{
    currently = {
        apparentTemperature = "43.67";
        cloudCover = 0;
        dewPoint = "40.32";
        humidity = "0.8";
        icon = "clear-day";
        ozone = "289.27";
        precipIntensity = 0;
        precipProbability = 0;
        pressure = "1027.76";
        summary = Clear;
        temperature = "46.23";
        time = 1480048337;
        windBearing = 242;
        windSpeed = "5.18";
    };
    ...
}

Using SwiftyJSON, we can access individual properies by doing stuff like json["currently"]["temperature"].float or json["currently"]["icon"].string.

In this initializer, we need to set a value for each of the WeatherData’s properties. The SwiftJSON calls will return an optional value, so we’ll need to provide default choices in case the value doesn’t actually exist:

let currentWeather = json["currently"]

if let temperature = currentWeather["temperature"].float {
    self.temperature = String(format: "%.0f", temperature) + " ºF"
} else {
    self.temperature = "--"
}

self.description = currentWeather["summary"].string ?? "--"
self.icon = currentWeather["icon"].string ?? "--"

For the temperature, we access the numerical value and then convert it to a String. If the temperature doesn’t exist, we give it a placeholder.

For the description and icon, we can use the shorthand nil-coalescing operator ??. It has a mouthful of a name, but is very convenient. If the value on the left is nil / doesn’t exist, it’ll use the default value on the right.

Your final WeatherData struct should look like this:

struct WeatherData {
    
    var temperature: String
    var description: String
    var icon: String
    
    init(data: Any) {
        let json = JSON(data)
        let currentWeather = json["currently"]

        if let temperature = currentWeather["temperature"].float {
            self.temperature = String(format: "%.0f", temperature) + " ºF"
        } else {
            self.temperature = "--"
        }

        self.description = currentWeather["summary"].string ?? "--"
        self.icon = currentWeather["icon"].string ?? "--"
    }
}

Updating the network call

Now that we actually have a WeatherData object, we can update our weatherForCoordinates method. Open up the DarkSkyService.swift file again.

You want to change the completion handler to (WeatherData?, Error?) -> () and the success case to completion(WeatherData(data: result), nil). Now the function should look like this:

static func weatherForCoordinates(latitude: String, longitude: String, completion: @escaping (WeatherData?, Error?) -> ()) {
    
    let url = baseURL + apiKey + "/\(latitude),\(longitude)"
    
    Alamofire.request(url).responseJSON { response in
        switch response.result {
        case .success(let result):
            completion(WeatherData(data: result), nil)
        case .failure(let error):
            completion(nil, error)
        }
    }
    
}

If you run the app again, you should see it print out a WeatherData object:

Optional(Weather_App.WeatherData(temperature: "45 ºF", description: "Clear", icon: "clear-day"))

Next Time

Now that we can request a forecast for a location, we’ll use CoreLocation to get the user’s longitude and latitude.