When building mobile applications, developers often get the opportunity to take advantage of third-party libraries, tools, and components to expedite the development process. These tools serve as invaluable assets when a budget doesn't allow for the time to write robust components from scratch.

With the rise in popularity of smart phones and apps over the past five years, more and more developers encounter the same problems and features. As a result, the solutions to these common problems, like retrieving a weather forecast, get molded into open source libraries and APIs for developers to take advantage of.

Throughout the course of this article, you'll develop an iOS class to get current and forecasted weather information using the OpenWeatherMap API and a popular open source client-side iOS library called AFNetworking.

Open Weather Map

OpenWeatherMap (openweathermap.org) is a public-access weather API that serves over a billion forecast requests per day. Powered by the OWM platform, OpenWeatherMap provides a variety of weather details for over 200,000 cities worldwide using data from over 40,000 weather stations. Using this API, developers can easily access current weather information, hour-by-hour forecasts, or even daily forecasts up to 16 days in advance. The service offers flexibility as well, allowing responses to be retrieved in XML, JSON, or HTML.

For the purposes of this demonstration, you'll work with the current weather and weekly seven-day forecast using JSON as the response type. You can access the documentation for the various API endpoints offered, including ones not covered in this article, at http://openweathermap.org/api.

OpenWeatherMap provides a variety of weather details for over 200,000 cities worldwide using data from over 40,000 weather stations

API Usage

The OpenWeatherMap API is free to use for smaller-scale applications. Large applications with bigger user bases may need to opt into one of the paid plans in order to satisfy the higher call-per-minute needs. The free tier comes with the following restrictions:

  • 1,200 API calls per minute
  • Weather history no greater than one day old
  • 95% up time
  • Weather data updates every 2 hours
  • No SSL

If you need more calls per minute, increased up time, more frequent weather updates, or secured access, you'll have to jump up to the first paid tier, which costs $180/month. Regardless of which tier you decide to use, you need to register an API key for your application. To do this, register your application at http://openweathermap.org/appid by signing up for an account. Once logged in, you can find the key under the setup tab. Since the API is public access, the key needs to be added as a parameter to whatever API endpoint you send a request to (demonstrated in the next section).

The Endpoints

As mentioned earlier, you'll work with two of the endpoint offerings from OpenWeatherMap: one that gets current weather and the other than gets a seven-day forecast. When dealing with either of these endpoints, pass the location you wish to retrieve weather for. You can pass that location data in a variety of ways:

  • Pass the city ID for the area (a list of city IDs can be downloaded at http://bulk.openweathermap.org/sample/)
  • Send geographical coordinates for the location (latitude and longitude)
  • Use a zip code and country code (use ISO 3166 country codes)

The base API request URL to get current weather in a given location looks like this:

http://api.openweathermap.org/data/2.5/weather

The base URL to get the future weather forecast for a set amount of days, and a set location, looks like this:

http://api.openweathermap.org/data/2.5/forecast/daily

In mobile applications that track a user's location, the most readily available location metric is usually latitude and longitude. Therefore, for the purpose of this demonstration, you'll send the location data to the API as geographical coordinates. To query either of the above endpoints using latitude and longitude, add “lat” and “long” variables to the request by appending the following onto one of the above URLs:

?lat={latitude val}&lon={longitude value}

Furthermore, when calling “forecast/daily,” you can request a specific number of days to get a forecast for. In this demo, you'll ask for seven days' worth of data by appending cnt=7 to the URL. Don't forget that you need to include your API key. All together, the URL request to get a seven-day forecast in a specific location with a specific lat/long looks like this:

http://api.openweathermap.org/data/2.5/
forecast/daily?lat=-16.92&lon=145.77&cnt=7
&APPID={APIKEY}

The Data

Once one of the forecast requests is sent, the service responds with the corresponding information. The default response type is JSON, but you can opt to receive information in XML or HTML, depending on your needs. Let's take a look at the response format for the two calls previously explained. Listing 1 shows a sample JSON response object from the call to get current weather for Cairns, AU, and Listing 2 shows the response format of a daily forecast call for Shuzenji, JP.

Listing 1: JSON response for current weather info from OpenWeatherMap

{
    "coord":{ "lon":145.77, "lat":-16.92},
    "weather":[{
        "id":803,
        "main":"Clouds",
        "description":"broken clouds",
        "icon":"04n"
    }],
    "base":"cmc stations"
    "main": {
        "temp":293.25,
        "pressure":1019,
        "humidity":83,
        "temp_min":289.82,
        "temp_max":295.37
    },
    "wind:{
        "speed":5.1,
            "deg":150
    },
    "clouds":{"all":75},
    "rain":{"3h":3},
    "dt":1435658272,
    "sys":{
        "type":1,
        "id":8166,
        "message":0.0166,
        "country":"AU",
        "sunrise":1435610796,
        "sunset":1435650870},
    "id":2172797,
    "name":"Cairns",
    "code":200
}

Listing 2: JSON forecasted weather response info from OpenWeatherMap

{
    "code":"200",
    "message":0.0032,
    "city":{
        "id":1851632,
        "name": "Shuzenji",
            "coord":{
                "lon":138.933334,
                "lat":34.966671
            },
        "country":"JP"
    },
    "cnt":10,
    "list":[{
        "dt":1406080800,
        "temp":{
            "day":297.77,
            "min":293.52,
            "max":297.77,
            "night":293.52,
            "eve":297.77,
            "morn":297.77
        },
        "pressure":925.04,
        "humidity":76,
        "weather":[{
            "id":803,
            "main":"Clouds",
            "description":"broken clouds",
            "icon":"04d"
        }],
    }]
}

The default metric for numerical values from the API, and also the metric used in the sample responses shown in Listing 1 and 2, is Kelvin. You can format the call to the API to return data in Imperial (Fahrenheit) or Metric (Celsius) as well. Date and time stamps use the UNIX epoch format (the number of seconds elapsed since January 1, 1970). In this example, you'll convert those times to the device's local time zone and store them as NSDate objects. Notice that the weather component of each response has an id and an icon field. The ID is unique to each specific weather condition in the system and the icon coordinates to a graphical representation of the corresponding condition. In Listing 1, for example, the icon field maps to a value of 04n. Using the URL format provided in the documentation, you can access an image icon representing the current weather condition at http://openweathermap.org/img/w/04n.png. Further documentation on all weather conditions and icon mappings can be found at http://openweathermap.org/weather-conditions.

The list array in Listing 2 is the most noticeable difference between the two response formats. That list contains an entry for each day of the forecast. In this example, you'll ask for a seven-day forecast, so that list array has seven entries, whereas Listing 1 only shows detailed information for the current date and time.

AFNetworking

AFNetworking is an open-source networking library for iOS and Mac OSX. It's built atop the URL Loading System found in the Foundation framework and offers a feature-rich API that includes many useful features such as serialization, support for reachability, and various UIKit integrations. It's currently one of the most widely used open-source iOS projects with nearly 20k stars, 6k forks, and 240+ contributors on Github (https://github.com/AFNetworking/AFNetworking).

AFNetworking is currently one of the most widely used open-source iOS projects with nearly 20k stars, 6k forks, and 240+ contributors on Github.

The latest version of AFNetworking (2.0) requires iOS 7 or greater, relying heavily on the use of NSURLSession, which is now preferred over the older NSURLConnection. In this example, you'll use AFHTTPRequestOperationManager, which encompasses all the common use cases for communicating with a Web application over HTTP, including built-in calls for your standard GET, POST, PUT, and DELETE requests. You'll get further into the syntax and how to implement the library during the coding exercise in the next section.

Let's Code It

In this code exercise, you'll write a WeatherRadar class that can fetch either current weather conditions or an extended weekly weather forecast. The WeatherRadar class uses callbacks to return either a weather object or an NSArray of weather objects, depending on what API call is made. This portable library can get dropped into any project that needs to gather weather information.

Getting Started

The first thing you'll want to do is create a new Xcode project for this exercise. To do this, launch Xcode and click Create a new Xcode project. Select Single View Application and press Next. Finally, fill in a project name and match the other settings to the screenshot in Figure 1.

Figure 1: Initial project settings when creating the sample project in Xcode
Figure 1: Initial project settings when creating the sample project in Xcode

Once your settings match Figure 1, click Next. You should now have a blank Xcode project. The only other thing you need to set up before you get coding is AFNetworking. Add AFNetworking to the project using CocoaPods, one of the simplest methods to add third-party content to any iOS project. For the sake of this walkthrough, I'll assume that you have a working understanding of how to install a library using CocoaPods (If not, please refer to the sidebar for additional information on how to use CocoaPods or where you can locate additional installation instructions). Once you've created a Podfile, add the following line to the file and save it:

pod "AFNetworking", "~> 2.0"

Once saved, launch terminal and navigate to the root folder for your project. Make sure that the project is closed in Xcode and run the following command in terminal:

pod install

This automatically downloads the AFNetworking library and creates an .xcworkspace file. Re-open your project by double clicking that new project file.

Weather Model

The next thing you want to add to the project is a weather object model that holds the information you receive from the OpenWeatherMap API. Click File > New > File and then choose Cocoa Touch Class. Next, choose NSObject and name the object “Weather,” as shown in Figure 2.

Figure 2: Settings when creating the weather object model to store weather data
Figure 2: Settings when creating the weather object model to store weather data

Once that has been created, you'll need to add various properties to correspond to the weather data you wish to collect. For the purpose of this example, only the basic weather attributes will get captured in order to demonstrate how to interface with the API. You can follow the same structure to store any of the remaining data points that aren't used in this sample.

Open the Weather.h file you just generated. Add the properties for the data you'll track, as shown in Listing 3. These properties map to the various data points that get returned by the API; the comments in Listing 3 offer more detail on what each property represents.

Listing 3: Weather.m - variable parameters

@interface Weather : NSObject

/// the date this weather is relevant to
@property (nonatomic, strong) NSDate *dateOfForecast;

/// the general weather status:
/// clouds, rain, thunderstorm, snow, etc...
@property (nonatomic, strong) NSString* status;

/// the ID corresponding to general weather status
@property (nonatomic) int statusID;

/// a more descriptive weather condition:
/// light rain, heavy snow, etc...
@property (nonatomic, strong) NSString* condition;

/// min/max temp in farenheit
@property (nonatomic) int temperatureMin;
@property (nonatomic) int temperatureMax;

/// current humidity level (perecent)
@property (nonatomic) int humidity;

/// current wind speed in mph
@property (nonatomic) float windSpeed;

@end

The next step involves creating the radar class that gathers the information needed to populate this weather object. That radar class retrieves the weather data as a JSON object. Once you code that (in the next section), you'll loop back and create an initializer method on this weather model to take the JSON data and parse it into the appropriate properties that you just created.

Weather Radar

Just like you previously did with the weather model, go to File > New > File and select NSObject to create a new class. Name it WeatherRadar and click Next and then Save. This WeatherRadar class directly integrates with OpenWeatherMap to get the weather information. As mentioned previously, AFNetworking handles the call for data, so you need to write a function using that library to make a GET request to the specified API URL. You use an AFHTTPRequestOperationManager, a component of AFNetworking, to make that request. Listing 4 demonstrates the function implementation in WeatherRadar.m and how to use the operation manager to formulate the query.

Listing 4: WeatherRadar.m - send GET request to specified URL

- (void)fetchWeatherFromProvider:(NSString*)URL completionBlock:
  (void (^)(NSDictionary *))completionBlock {

    AFHTTPRequestOperationManager *manager =
    [AFHTTPRequestOperationManager manager];

    [manager GET:URL parameters:nil success:
    ^(AFHTTPRequestOperation* operation, id responseObject) {
        if (responseObject) {
            completionBlock(responseObject);
        } else {
            // handle no results
        }
    } failure:^(AFHTTPRequestOperation*
    operation, NSError *error) {
        // handle error
    }
    ];
}

Notice that the function definition takes a URL parameter. This URL either points to the API endpoint for today's real-time weather or to the endpoint for the weekly forecast, depending on which information you request. By making the URL a parameter rather than part of the function, you're able to reuse this function to make both API calls, rather than needing to duplicate code. Also notice that there's a callback block that passes an NSDictionary to the calling function. That dictionary contains the JSON response from the API.

You've now primed the radar class to communicate with the API. Next, you need methods that formulate the appropriate URLs to feed to the fetchWeatherFromProvider call defined in Listing 4. To do this, you write one method for each API call that you want to format, which is two in this case. Before doing that, switch over to WeatherRadar.h and add the import in the next snippet to the top of the file. You need this import because the functions you write return instances of the Weather model that you previously created.

#import "Weather.h"

The main functions of the radar class need to be publicly accessible so you can call them from anywhere in the application that you need weather data. Therefore, define the functions in WeatherRadar.h as shown in Listing 5 so other classes can access them. Listing 5 defines the function headers but you still need to write the implementation for each of the corresponding functions. You do this in WeatherRadar.m and it should resemble the code from Listing 6 when completed.

Listing 5: WeatherRadar.h - declare weather radar functions headers

/**
*  Returns weekly forecasted weather conditions
*  for the specified lat/long
*
*  @param latitude           Location latitude
*  @param longitude          Location longitude
*  @param completionBlock    Array of weather results
*/
- (void)getWeeklyWeather:(float)latitude longitude:(float)longitude
   completionBlock:(void (^)(NSArray *))completionBlock;

/**
*  Returns realtime weather conditions
*  for the specified lat/long
*
*  @param latitude        Location latitude
*  @param longitude       Location longitude
*  @param completionBlock Weather object
*/
- (void)getCurrentWeather:(float)latitude longitude:(float)longitude
   completionBlock:(void (^)(Weather *))completionBlock;

Listing 6: WeatherRadar.m - weather radar function implementations

- (void)getWeeklyWeather:(float)latitude longitude:(float)longitude
completionBlock:(void (^)(NSArray *))completionBlock {

    // formulate the url to query the api to get the 7 day
    // forecast. cnt=7 asks the api for 7 days. units = imperial
    // will return temperatures in Farenheit
    NSString* url = [NSString stringWithFormat:
      @"http://api.openweathermap.org/data/2.5/forecast/daily?units=imperial
      &cnt=7&lat=%f&lon=%f", latitude, longitude];

    // escape the url to avoid any potential errors
    url = [url stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];

    // call the fetch function from Listing 4
    [self fetchWeatherFromProvider:url completionBlock:
      ^(NSDictionary * weatherData) {
        // create an array of weather objects (one for each day)
        // initialize them using the function from listing 7
        // and return the results to the calling controller
        NSMutableArray *weeklyWeather = [[NSMutableArray alloc] init];

        for(NSDictionary* weather in weatherData[@"list"]) {
        // pass false since the weather is a future forecast
        // this lets the init function know which format of
        // data to parse
        Weather* day = [[Weather alloc]
        initWithDictionary:weather isCurrentWeather:FALSE];
        [weeklyWeather addObject:day];
        }

        completionBlock(weeklyWeather);
        }];
    }

- (void)getCurrentWeather:(float)latitude longitude:(float)longitude
   completionBlock:(void (^)Weather *))completionBlock {
        // formulate the url to query the api to get current weather
        NSString* url = [NSString stringWithFormat:
        @"http://api.openweathermap.org/data/2.5/weather?units=imperial
        &cnt=7&lat=%f&lon=%f", latitude, longitude];

        // escape the url to avoid any potential errors
        url = [url stringByAddingPercentEscapesUsingEncoding:
        NSUTF8StringEncoding];

        // call the fetch function from Listing 4
        [self fetchWeatherFromProvider:url completionBlock:
          ^(NSDictionary * weatherData) {
            // create an weather object by initializing it with
            // data from the API using the init func from Listing 7
            completionBlock([[Weather alloc]
            initWithDictionary:weatherData isCurrentWeather:TRUE]);
        }];
}

Notice that both of the functions from Listing 6 receive data from a call to fetchWeatherFromProvider and return it using a completion block callback. The callbacks use corresponding functions located in the weather model that transpose the JSON data from the API into weather objects that the application can better work with. The next section covers the implementation of that functionality.

Back to the Weather Model

You've almost completed the circle. You started by creating a weather model object, wrote a radar class to get the data to populate that object, and now you need the final logic that maps that data from the API results into the model. This logic needs to account for the variable formatting between the results of the two API calls you implemented (the variation seen by comparing the results from Listing 1 and Listing 2). Move back to your Weather.h file and declare the following initialization function header:

/**
*  initialize this object using the JSON data
*  passed in the dictionary paramter
*
*  @param dictionary       JSON data from API
*  @param isCurrentWeather BOOL (FALSE=Weekly)
*
*  @return instance of self
*/
- (instancetype)initWithDictionary:
(NSDictionary *) dictionary
isCurrentWeather:(BOOL)isCurrentWeather;

You probably noticed from the code you wrote in Listing 6 that both getCurrentWeather and getWeeklyWeather use the same initialization function (the one you just declared in the previous snippet). However, as just mentioned, the data gathered by those calls has different formats. To compensate, the isCurrentWeather Boolean is added as a function parameter. Pass TRUE and the initialization function parses the JSON dictionary using the current weather format (Listing 1). Pass FALSE and it adheres to the weekly weather data format (Listing 2). This initialization breakdown and full function implementation is demonstrated in Listing 7.

Listing 7: Weather.m - weather object initialization function

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
  isCurrentWeather:(BOOL)isCurrentWeather{
      self = [super init];

      if (self) {
          /*
          * Parse weather data from the API into this weather
          * object. Error check each field as there is no guarantee
          * that the same data will be available for every location
          */

          _dateOfForecast = [self utcToLocalTime:[NSDate
          dateWithTimeIntervalSince1970: 
          [dictionary[@"dt"] doubleValue]]];

          // use the bool to determine which data format to parse
          if (isCurrentWeather) {
              int temperatureMin =
              [dictionary[@"main"][@"temp_min"] intValue];

  
              if(temperatureMin) {
                  _temperatureMin = temperatureMin;
              }

              int temperatureMax =
                  [dictionary[@"main"][@"temp_max"] intValue];
              if (temperatureMax) {
                  _temperatureMax = temperatureMax;
              }

              int humidity = 
                [dictionary[@"main"][@"humidity"] intValue];
              if (humidity) {
                    _humidity = humidity;
              }

              float windSpeed =
                [dictionary[@"wind"][@"speed"] floatValue];
              if (windSpeed) {
                  _windSpeed = windSpeed;
              }
          }
          else {
              int temperatureMin =
                [dictionary[@"temp"][@"min"] intValue];
              if (temperatureMin) {
                    _temperatureMin = temperatureMin;
              }

              int temperatureMax =
                [dictionary[@"temp"][@"max"] intValue];
              if (temperatureMax) {
                _temperatureMax = temperatureMax;
              }

              int humidity = [dictionary[@"humidity"] intValue];
              if (humidity) {
                  _humidity = humidity;
              }

              float windSpeed = [dictionary[@"speed"] floatValue];
              if (windSpeed) {
                  _windSpeed = windSpeed;
              }
          }

          /*
          * weather section of the response is an array of
          * dictionary objects. The first object in the array
          * contains the desired weather information.
          * this JSON is formatted the same for both requests
          */
          NSArray* weather = dictionary[@"weather"];
          if ([weather count] > 0) {
              NSDictionary* weatherData = [weather objectAtIndex:0];
              if (weatherData) {
                  NSString *status = weatherData[@"main"];
                  if (status) {
                      _status = status;
                  }

                  int statusID = [weatherData[@"id"] intValue];
                  if (statusID) {
                      _statusID = statusID;
                  }

                  NSString *condition = weatherData[@"description"];
                  if (condition) {
                      _condition = condition;
                  }
              }
          }

      }

      return self;
}

In order for the code in Listing 7 to function properly, you also need to add the following helper function to convert the data to local time.

/**
*  Takes a unic UTC timestamp and converts it
*  to an NSDate formatted in the device's local
*  timezone
*
*  @param date  Date to be converted
*
*  @return      Converted date
*/
-(NSDate *)utcToLocalTime:(NSDate*)date {
    NSTimeZone *currentTimeZone =
      [NSTimeZone defaultTimeZone];
    NSInteger secondsOffset =
      [currentTimeZone secondsFromGMTForDate:date];
return [NSDate dateWithTimeInterval:
secondsOffset sinceDate:date];
}

You've now completed both the WeatherRadar and Weather classes. Simply initialize a WeatherRadar object from anywhere in your application and call either getCurrentWeather or getWeeklyWeather to retrieve the desired weather information.

Practical Application

The code you just wrote is a portable library that can be dropped into any application; just add the WeatherRadar and Weather classes to the project and import the AFNetworking library. To demonstrate the practicality of the library, I've used it to create a demo application that visually displays the weekly forecast. Figure 3 depicts the application UI. The center of the screen highlights today's forecast and the bottom of the screen uses a UIScrollView to show the remainder of the week. Users can slide back and forth on the bottom portion of the screen to scan the days. It has a visual icon that correlates to the weather condition and is accompanied by the high and low forecasted temperatures for the corresponding day. The code included with this article not only encompasses the weather library covered previously, but it also includes all code for this sample application. This should help you understand exactly how to call the library and how to apply the results.

Figure 3: The sample application using the weather library from this article
Figure 3: The sample application using the weather library from this article

Wrapping Up

I hope that over the course of this article, you gained a basic understanding of the services offered by OpenWeatherMap's API and how to use those to add weather forecasting to any iOS application. If you need a more complex weather breakdown, be sure to visit OpenWeatherMap.com for full API documentation. The sidebars for this article have links to additional resources that may be of use as well.