Categories
Swift Kick

How to build an iOS weather app in Swift, part 2: A little more explanation, and turning OpenWeatherMap’s JSON into a Swift dictionary

smartphone weather

Welcome to part two in a series of articles on building your own iOS weather app in Swift!

The previous article in this series showed you how to:

  • Get a key for OpenWeatherMap’s API for current weather data
  • Make a manual API call using your browser
  • Create a basic app to get the data from OpenWeatherMap
  • Tweak the app

In this article, we’ll explain the code that was presented in the previous article and make the next major step in writing our weather app: extracting the data from the JSON returned by OpenWeatherMap.

A deeper look at the code that gets the weather data

In the last installment, I gave you enough code to connect to OpenWeatherMap and retrieve the weather data, but never explained how it works. Let’s fix that oversight.

Our simple weather app makes use of the NSURLSession class along with some classes that go along with it. Together, classes in the NSURLSession family of classes give us an API for downloading and uploading data from the internet, in a number of different ways.

The diagram below shows the classes that our weather app works with, either directly or indirectly:

nsurlsession diagram

Here’s a slightly more in-depth explanation of the classes in the diagram above:

NSURLSession class Instances of NSURLSession are containers — collections of other objects — that provide an API for sending data to and receiving data from a given URL. You use the NSURLSession API to create one or more sessions, which are objects that coordinate one or more tasks, which are objects that actually transfer the data.
Shared session object The shared session object is a pre-made singleton instance of NSURLSession. It’s not as configurable as other NSURLSession instances that you can instantiate yourself, but for simple requests, such as the kind we’re making in our bare-bones weather app, it’s good enough. You access the shared session with NSURLSession‘s sharedSession class method.
NSURLSessionTask class As mentioned in the explanation of NSURLSession above, tasks are the objects within an NSURLSession instance that actually transfer the data. NSURLSessionTask is the base class for tasks.
NSURLSessionDataTask class

You typically don’t instantiate an NSURLSessionTask object, but one of its subclasses, and you do so by calling one of NSURLSession‘s task creation methods. NSURLSessionDataTask is the class for data tasks, which are used to download data from a given server into memory. We’re using an NSURLSessionDataTask instance to request and collect data from OpenWeatherMap.

In addition to data tasks, there are also upload tasks, which are used to upload data to a server, and download tasks, which are used for downloading data from a server into a file (as opposed to memory, which is where data tasks download their data to).

That’s the “big picture” view. Keep it in mind as we look at the code that does the downloading, contained in the getWeatherInfo function in the WeatherGetter class:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "06db44f389d2172e9b1096cdce7b051c"
  
  func getWeatherInfo(city: String) {
    
    // *** 1 ***
    let session = NSURLSession.sharedSession()

    // *** 2 ***
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // *** 3 ***
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      // *** 4 ***
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      // *** 5 ***
      else {
        // Case 2: Success
        // We got a response from the server!
        print("Raw data:\n\(data!)\n")
        let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("Human-readable data:\n\(dataString!)")
      }
    }

    // *** 6 ***
    dataTask.resume()
  }
  
}

I’ve added number comments to the code, which correspond to the explanations below:

  1. We use NSURLSession‘s class method sharedSession to get a reference to the shared session object, and assign that reference to the local variable session.
  2. We construct an URL using the URL for OpenWeatherMap’s current weather API, filling in the blanks with the name of the city that we want the weather for, and our OpenWeatherMap API key.
  3. We want to use a data task to request and retrieve the data from OpenWeatherMap. As mentioned earlier, tasks are created by using one of NSURLSession‘s task-creation methods. In this case, we’re using dataTaskWithURL, which we provide with 2 arguments:
    • The URL created in step 2, and
    • A completion handler that executes once the task is done requesting and retrieving the data. The completion handler will receive three arguments, which we can work with:
      1. data: If the request was successfully made and the data was successfully received, this will contain that received data.
      2. response: The response from the server.
      3. error: If the request was not made successfully or the data was not received successfully, this will contain information about the error.
  4. If the error argument isn’t nil, we assign its value to a local variable also named error, and then display its contents.
  5. If the error argument is nil, it means that no error occurred, and we display both the server response and the data received, in both raw and human-readable formats.
  6. Up until this point, we’ve only defined the data task. This statement activates it.

Turning the weather data into a Swift dictionary

Right now, if the weather request was successfully made and the data was successfully received, we take that data, convert it into a human-readable string, and output it to the debug console…

// Case 2: Success
// We got a response from the server!
print("Raw data:\n\(data!)\n")
let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
print("Human-readable data:\n\(dataString!)")

…and that human-readable string looks something like this:

{"coord":{"lon":-82.46,"lat":27.95},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"cmc stations","main":{"temp":295.49,"pressure":1015,"humidity":64,"temp_min":294.15,"temp_max":296.15},"wind":{"speed":2.1,"deg":280},"clouds":{"all":1},"dt":1460163717,"sys":{"type":1,"id":728,"message":0.004,"country":"US","sunrise":1460200241,"sunset":1460245929},"id":4174757,"name":"Tampa","cod":200}

We could simply perform all sorts of string operations to extract the data we need, but why should we? The data is already in JSON format, which maps nicely to data structures in most programming languages, including Swift. There should be a simple way to take that incoming data and turn it into a nice Swift dictionary.

Enter the NSJSONSerialization class, which can convert JSON into Foundation objects (such as arrays and dictionaries), and vice versa. It has a method called JSONObjectWithData that takes two arguments:

  • data: An NSData object containing JSON data, which we happen to have as one of the parameters of our completion handler, and
  • options: Options that specify how the JSON data should be read and how the corresponding Foundation objects should be created.

Let’s change the code so that we no longer turn the data into a string, but instead using NSJSONSerialization‘s JSONObjectWithData method to turn it into a dictionary:

// Case 2: Success
// We got a response from the server!
do {
  // Try to convert that data into a Swift dictionary
  let weather = try NSJSONSerialization.JSONObjectWithData(
    data!,
    options: .MutableContainers) as! [String: AnyObject]

  // If we made it to this point, we've successfully converted the
  // JSON-formatted weather data into a Swift dictionary.
  // Let's print its contents to the debug console.
  print("Date and time: \(weather["dt"]!)")
  print("City: \(weather["name"]!)")
  
  print("Longitude: \(weather["coord"]!["lon"]!!)")
  print("Latitude: \(weather["coord"]!["lat"]!!)")

  print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
  print("Weather main: \(weather["weather"]![0]!["main"]!!)")
  print("Weather description: \(weather["weather"]![0]!["description"]!!)")
  print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

  print("Temperature: \(weather["main"]!["temp"]!!)")
  print("Humidity: \(weather["main"]!["humidity"]!!)")
  print("Pressure: \(weather["main"]!["pressure"]!!)")

  print("Cloud cover: \(weather["clouds"]!["all"]!!)")

  print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
  print("Wind speed: \(weather["wind"]!["speed"]!!)")

  print("Country: \(weather["sys"]!["country"]!!)")
  print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
  print("Sunset: \(weather["sys"]!["sunset"]!!)")
}
catch let jsonError as NSError {
  // An error occurred while trying to convert the data into a Swift dictionary.
  print("JSON error description: \(jsonError.description)")
}

If you run the app now, you’ll see that when put into dictionary form, it’s easy to extract weather data:

Date and time: 1462277829
City: Tampa
Longitude: -82.45999999999999
Latitude: 27.95
Weather ID: 800
Weather main: Clear
Weather description: clear sky
Weather icon ID: 02d
Temperature: 294.659
Humidity: 95
Pressure: 1025.47
Cloud cover: 8
Wind direction: 168.003 degrees
Wind speed: 2.11
Country: US
Sunrise: 1462272456
Sunset: 1462320359

Here’s what the complete WeatherGetter.swift should look like now:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API KEY HERE"
  
  func getWeather(city: String) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      else {
        // Case 2: Success
        // We got a response from the server!
        do {
          // Try to convert that data into a Swift dictionary
          let weather = try NSJSONSerialization.JSONObjectWithData(
            data!,
            options: .MutableContainers) as! [String: AnyObject]

          // If we made it to this point, we've successfully converted the
          // JSON-formatted weather data into a Swift dictionary.
          // Let's print its contents to the debug console.
          print("Date and time: \(weather["dt"]!)")
          print("City: \(weather["name"]!)")
          
          print("Longitude: \(weather["coord"]!["lon"]!!)")
          print("Latitude: \(weather["coord"]!["lat"]!!)")

          print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
          print("Weather main: \(weather["weather"]![0]!["main"]!!)")
          print("Weather description: \(weather["weather"]![0]!["description"]!!)")
          print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

          print("Temperature: \(weather["main"]!["temp"]!!)")
          print("Humidity: \(weather["main"]!["humidity"]!!)")
          print("Pressure: \(weather["main"]!["pressure"]!!)")

          print("Cloud cover: \(weather["clouds"]!["all"]!!)")

          print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
          print("Wind speed: \(weather["wind"]!["speed"]!!)")

          print("Country: \(weather["sys"]!["country"]!!)")
          print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
          print("Sunset: \(weather["sys"]!["sunset"]!!)")
        }
        catch let jsonError as NSError {
          // An error occurred while trying to convert the data into a Swift dictionary.
          print("JSON error description: \(jsonError.description)")
        }
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

At this point, the WeatherGetter class doesn’t just get the weather data from OpenWeatherMap; it also puts that data into a form that we can process: a Dictionary. We’re still displaying the information in the debug console — we’re still not showing it to anyone who’s not running the app with Xcode.

In the next installment in this series, we’ll take the weather data, now in dictionary form, and make it visible to the user. We’ll also make it possible for the user to enter a city to get the weather for, rather than hard-wire it into the app.

xcode download

You can download the project files for this aricle (41KB zipped) here.

12 replies on “How to build an iOS weather app in Swift, part 2: A little more explanation, and turning OpenWeatherMap’s JSON into a Swift dictionary”

ON this line of code I’m getting this error.

`print(“Weather ID: \(weather[“weather”]![0]![“id”]!!)”)`

Type ‘Any’ has no subscript members.

Hi Joey,

I am following your tutorial but it’s in Swift 3. I am using a new http request as it on Stackoverflow http://stackoverflow.com/questions/38292793/http-requests-in-swift-3. I managed to retrieved data back but it doesn’t display in UI. I tried build all views programmically but it still not see anything. I did use my API key with a sample project and it isn’t working either. Can you please help me? I can send my project to you.

Best,

Thanks for the nice tutorial! Struggled with this, but here is working code for Swift 3:

//
// WeatherGetter.swift
// WeatherApp
//
// Created by Willy Bergsnov on 15.02.2017.
// Copyright © 2017 Mother Ocean. All rights reserved.
//

import Foundation

class WeatherGetter {

private let openWeatherMapBaseURL = “http://api.openweathermap.org/data/2.5/weather”
private let openWeatherMapAPIKey = “fce9415e81588e8c8498361ef487dbb8”

func getWeather(city: String) {

// This is a pretty simple networking task, so the shared session will do.
let session = URLSession.shared

let weatherRequestURL = URL(string: “\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)”)!

// The data task retrieves the data.

let dataTask = session.dataTask(with: weatherRequestURL, completionHandler: {
(data, response, error) in
if let error = error {
// Case 1: Error
// We got some kind of error while trying to get data from the server.
print(“Error:\n\(error)”)
}
else {
// Case 2: Success
// We got a response from the server!
do {
// Try to convert that data into a Swift dictionary
let weather = try JSONSerialization.jsonObject(
with: data!,
options: .mutableContainers) as! Dictionary

// If we made it to this point, we’ve successfully converted the
// JSON-formatted weather data into a Swift dictionary.
// Let’s print its contents to the debug console.
print(“Date and time: \(weather[“dt”]!)”)
print(“City: \(weather[“name”]!)”)

print(“Longitude: \(weather[“coord”]![“lon”]!!)”)
print(“Latitude: \(weather[“coord”]![“lat”]!!)”)

print(“Weather ID: \((weather[“weather”]![0]! as! [String:AnyObject])[“id”]!)”)
print(“Weather main: \((weather[“weather”]![0]! as! [String:AnyObject])[“main”]!)”)
print(“Weather description: \((weather[“weather”]![0]! as! [String:AnyObject])[“description”]!)”)
print(“Weather icon ID: \((weather[“weather”]![0]! as! [String:AnyObject])[“icon”]!)”)

print(“Temperature: \(weather[“main”]![“temp”]!!)”)
print(“Humidity: \(weather[“main”]![“humidity”]!!)”)
print(“Pressure: \(weather[“main”]![“pressure”]!!)”)

print(“Cloud cover: \(weather[“clouds”]![“all”]!!)”)

print(“Wind direction: \(weather[“wind”]![“deg”]!!) degrees”)
print(“Wind speed: \(weather[“wind”]![“speed”]!!)”)

print(“Country: \(weather[“sys”]![“country”]!!)”)
print(“Sunrise: \(weather[“sys”]![“sunrise”]!!)”)
print(“Sunset: \(weather[“sys”]![“sunset”]!!)”)
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print(“JSON error description: \(jsonError.description)”)
}
}
})
// The data task is set up…launch it!
dataTask.resume()
}

}

Thank you Joey, these posts have been very educational!

Thank you Wily, your Swift version 3 code was very helpful in figuring out how to access the “weather” items.

Hey Joey! I can’t thank you enough. Your tutorial saved me hours of figuring out. The article is neat and well written.

Wish you all the very best for your future endeavors :)

Even the revised code is throwing an error now:
Generic parameter ‘Key’ could not be inferred in cast to ‘Dictionary’

:)

Comments are closed.