iOS 7 Best Practices; A Weather App Case Study: Part 1/2

Learn various iOS 7 best practices in this 2-part tutorial series. You’ll master the theory and then practice by making a functional, beautiful weather app. By Ryan Nystrom.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Retrieving Weather Data

You’ll notice that the app says “Loading…“, but it’s not really doing anything. Time to get some real weather conditions.

You’re going to pull data from the OpenWeatherMap API. OpenWeatherMap is a really awesome service that aims to provide real-time, accurate, and free weather data to anyone. There are a lot of weather API’s out there, but most of them either use older data formats like XML, or they are paid services — and sometimes quite expensive.

You’ll follow the basic steps below to get weather data for your device’s location:

  1. Find the location of the device
  2. Download JSON data from an API endpoint
  3. Map the JSON to WXConditions and WXDailyForecasts
  4. Inform the UI that we have new data

Get started by creating your weather model and data management classes. Click File\New\File… and select Cocoa Touch\Objective-C class. Name your class WXClient and make it a subclass of NSObject.

Do this three more times to create the following classes:

  • WXManager as a subclass of NSObject
  • WXCondition as a subclass of MTLModel
  • WXDailyForecast as a subclass of WXCondition

All done? Now you can move onto the next section which deals with the mapping and transformation of your weather data.

Creating Your Weather Model

Your models will use Mantle which makes data mapping and value transformation a cinch.

Open up WXCondition.h and modify the interface so that it looks like the one below:

// 1
@interface WXCondition : MTLModel <MTLJSONSerializing>

// 2
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSNumber *humidity;
@property (nonatomic, strong) NSNumber *temperature;
@property (nonatomic, strong) NSNumber *tempHigh;
@property (nonatomic, strong) NSNumber *tempLow;
@property (nonatomic, strong) NSString *locationName;
@property (nonatomic, strong) NSDate *sunrise;
@property (nonatomic, strong) NSDate *sunset;
@property (nonatomic, strong) NSString *conditionDescription;
@property (nonatomic, strong) NSString *condition;
@property (nonatomic, strong) NSNumber *windBearing;
@property (nonatomic, strong) NSNumber *windSpeed;
@property (nonatomic, strong) NSString *icon;

// 3
- (NSString *)imageName;

@end

Again, this is a lot of setup code. Looking at the numbered comments, you’ll see the following:

  1. The MTLJSONSerializing protocol tells the Mantle serializer that this object has instructions on how to map JSON to Objective-C properties.
  2. These are all of your weather data properties. You’ll be using a couple of them, but its nice to have access to all of the data in the event that you want to extend your app down the road.
  3. This is simply a helper method to map weather conditions to image files.

Build and run your app and…it fails. Can you guess why? There’s a spoiler below if you need a little help.

[spoiler title=”Solution”]

You haven’t imported Mantle from your Cocoapods project! Replace the #import "MTLModel.h" at the top of WXCondition.h with the following:

#import <Mantle.h>

[/spoiler]

Build and run your app now; success. You’ll see a few new warnings, but you can ignore them for now.

First you’ll need to take care of the missing implementation of -imageName.

Open WXCondition.m and add the following methods:

+ (NSDictionary *)imageMap {
    // 1
    static NSDictionary *_imageMap = nil;
    if (! _imageMap) {
        // 2
        _imageMap = @{
                      @"01d" : @"weather-clear",
                      @"02d" : @"weather-few",
                      @"03d" : @"weather-few",
                      @"04d" : @"weather-broken",
                      @"09d" : @"weather-shower",
                      @"10d" : @"weather-rain",
                      @"11d" : @"weather-tstorm",
                      @"13d" : @"weather-snow",
                      @"50d" : @"weather-mist",
                      @"01n" : @"weather-moon",
                      @"02n" : @"weather-few-night",
                      @"03n" : @"weather-few-night",
                      @"04n" : @"weather-broken",
                      @"09n" : @"weather-shower",
                      @"10n" : @"weather-rain-night",
                      @"11n" : @"weather-tstorm",
                      @"13n" : @"weather-snow",
                      @"50n" : @"weather-mist",
                      };
    }
    return _imageMap;
}

// 3
- (NSString *)imageName {
    return [WXCondition imageMap][self.icon];
}

Here’s what the above code does:

  1. Create a static NSDictionary since every instance of WXCondition will use the same data mapper.
  2. Map the condition codes to an image file (e.g. “01d” to “weather-clear.png”).
  3. Declare the public message to get an image file name.
Note: You can find a list of all of the condition codes that OpenWeatherMap supports here.

Take a look at the following condensed sample JSON response from OpenWeatherMap:

{
    "dt": 1384279857,
    "id": 5391959,
    "main": {
        "humidity": 69,
        "pressure": 1025,
        "temp": 62.29,
        "temp_max": 69.01,
        "temp_min": 57.2
    },
    "name": "San Francisco",
    "weather": [
        {
            "description": "haze",
            "icon": "50d",
            "id": 721,
            "main": "Haze"
        }
    ]
}

You’ll need to map embedded JSON values to Objective-C properties. Embedded JSON values are elements such as temp, which is you can see above is a child of main.

To do this, you’ll take advantage of Objective-C’s Key-Value Coding and Mantle’s MTLJSONAdapter.

Still in WXCondition.m, setup your “JSON to model properties” mappings by adding the +JSONKeyPathsByPropertyKey method that the MTLJSONSerializing protocol requires.

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"date": @"dt",
             @"locationName": @"name",
             @"humidity": @"main.humidity",
             @"temperature": @"main.temp",
             @"tempHigh": @"main.temp_max",
             @"tempLow": @"main.temp_min",
             @"sunrise": @"sys.sunrise",
             @"sunset": @"sys.sunset",
             @"conditionDescription": @"weather.description",
             @"condition": @"weather.main",
             @"icon": @"weather.icon",
             @"windBearing": @"wind.deg",
             @"windSpeed": @"wind.speed"
             };
}

In this case, the dictionary key is WXCondition‘s property name, while the dictionary value is the keypath from the JSON.

You may have noticed that there’s a glaring issue with the way the JSON data is mapped to the Objective-C properties. The property date is of type NSDate, but the JSON has an NSInteger stored as Unix time. You’ll need to convert between the two somehow.

Mantle has just the feature to solve this problem for you: MTLValueTransformer. This class lets you declare a block detailing how to convert to and from a value.

The syntax for using Mantle’s transformers is a little strange. To create a transformer for a specific property, you add a class method that begins with the property name and ends with JSONTransformer.

It’s probably easier to see it in context than to try and explain it, so add the following transformers for NSDate properties to WXCondition.m.

+ (NSValueTransformer *)dateJSONTransformer {
    // 1
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [NSDate dateWithTimeIntervalSince1970:str.floatValue];
    } reverseBlock:^(NSDate *date) {
        return [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]];
    }];
}

// 2
+ (NSValueTransformer *)sunriseJSONTransformer {
    return [self dateJSONTransformer];
}

+ (NSValueTransformer *)sunsetJSONTransformer {
    return [self dateJSONTransformer];
}

Here’s how the above transformers work:

  1. You return a MTLValueTransformer using blocks to transform values to and from Objective-C properties.
  2. You only need to detail how to convert between Unix time and NSDate once, so just reuse -dateJSONTransformer for sunrise and sunset.

This next value transformation is a little annoying, but it’s simply a consequence of using OpenWeatherMap’s API and the way they format their JSON responses. The weather key is a JSON array, but you”re only concerned about a single weather condition.

Using the same structure as -dateJSONTransformer in WXCondition.m, can you create a transformer between an NSArray and NSString? The solution is provided below if you can’t quite get it on your own.

[spoiler title=”Solution”]

+ (NSValueTransformer *)conditionDescriptionJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSArray *values) {
        return [values firstObject];
    } reverseBlock:^(NSString *str) {
        return @[str];
    }];
}

+ (NSValueTransformer *)conditionJSONTransformer {
    return [self conditionDescriptionJSONTransformer];
}

+ (NSValueTransformer *)iconJSONTransformer {
    return [self conditionDescriptionJSONTransformer];
}

[/spoiler]

The final transformer is just a formality. OpenWeatherAPI uses meters-per-second for wind speed. Since your app uses the imperial system, you’ll need to convert this to miles-per-hour.

Add the following transformer method and macro definition to your implementation in WXCondition.m.

#define MPS_TO_MPH 2.23694f

+ (NSValueTransformer *)windSpeedJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSNumber *num) {
        return @(num.floatValue*MPS_TO_MPH);
    } reverseBlock:^(NSNumber *speed) {
        return @(speed.floatValue/MPS_TO_MPH);
    }];
}

There’s a small discrepancy with OpenWeatherMap’s API that you’ll have to deal with. Take a look at the temperature portion located between a current conditions response and a daily forecast response:

// current
"main": {
    "grnd_level": 1021.87,
    "humidity": 64,
    "pressure": 1021.87,
    "sea_level": 1030.6,
    "temp": 58.09,
    "temp_max": 58.09,
    "temp_min": 58.09
}

// daily forecast
"temp": {
    "day": 58.14,
    "eve": 58.14,
    "max": 58.14,
    "min": 57.18,
    "morn": 58.14,
    "night": 57.18
}

The condition’s first key is main and the high temperature is stored in the key temp_max, while the forecast’s first key is temp and the high is stored as max.

Temperature key differences aside, everything else is the same. So all you really need to do is change the key mapping for daily forecasts.

Open WXDailyForecast.m and override +JSONKeyPathsByPropertyKey as follows:

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    // 1
    NSMutableDictionary *paths = [[super JSONKeyPathsByPropertyKey] mutableCopy];
    // 2
    paths[@"tempHigh"] = @"temp.max";
    paths[@"tempLow"] = @"temp.min";
    // 3
    return paths;
}

Note that this will also override WXCondition’s method. Here’s what the above method does in a nutshell:

  1. Get WXCondition‘s map and create a mutable copy of it.
  2. Change the max and min key maps to what you’ll need for the daily forecast.
  3. Return the new mapping.

Build and run your app; there isn’t anything new to see since the last run, but this is a good spot to check that your app compiles and runs without any errors.

Labels and Views

Ryan Nystrom

Contributors

Ryan Nystrom

Author

Over 300 content creators. Join our team.