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

Ryan Nystrom
Finished Weather App

Finished Weather App

Every developer has their own ideas as to the best way to create top-notch iOS apps. Some developers take advantage of Auto-Layout, some like to create all of their graphics in code, and some developers even like to code in Vim!

With the recent release of iOS 7 and Xcode 5, I thought it would be a great time to provide a case study using a unique set of approaches and tools to create a basic weather app; you can look upon these as my recommended iOS 7 best practices. The first stepping-stone to iOS development used to be building a Todo List app. However, as iOS has matured new iOS developers are expected to be proficient in modern techniques such as data management and consuming web requests.

In this 2-part tutorial series, you’ll explore how to create your own apps using the following tools and techniques:

This tutorial is designed for intermediate-level developers who know the basics, but haven’t moved into too many advanced topics. This tutorial is also a great start for anyone wanting to explore Functional Programming in Objective-C.

Getting Started

Fire up Xcode and go to File\New\Project. Select Application\Empty Application. Name your project SimpleWeather, click Next, select a directory to save your project, and then click Create.

You now have your base project set up. The next step is to set up your third-party tools — but first make sure you close Xcode so it won’t interfere with the next steps.

Cocoapods

You’ll be using Cocoapods to take care of downloading the code, adding files to your Xcode project, and configuring any project settings that those projects require. Let’s first cover what projects you’ll be using.

Mantle

Mantle is a project by the Github team that helps remove all of the boilerplate code that Objective-C requires to turn JSON data into NSObject subclasses. Mantle also does value transformation, which is a fancy way to turn JSON primitive values (strings, ints, floats) into complex values like NSDate, NSURL, or even custom classes.

LBBlurredImage

LBBlurredImage is a simple project that extends UIImageView and makes blurring images a breeze. You’ll be creating a fancy blur with just a single line of code. If you’re interested in seeing how the blur works, check out the source code.

TSMessages

TSMessages is another wonderfully simple library that takes care of displaying overlay alerts and notifications. When presenting error messages that don’t directly impact the user, it’s better to present an overlay instead of a modal view (like UIAlertView) so that you irritate the user as little as possible.

You’ll use TSMessages when a network connection is lost or the API encounters some other error. If something were to go wrong, you’d see an overlay like the one below:

TSMessages Error

ReactiveCocoa

The last library you’ll use is ReactiveCocoa, also made by the Github team. ReactiveCocoa brings functional programming to Objective-C by following patterns introduced by Reactive Extensions in .NET. You’ll be spending a good portion of your time in Part 2 implementing ReactiveCocoa.

Setting Up Your Cocoapods Libraries

To set up your Cocoapods libraries, first ensure that you have Cocoapods installed. To do that, open the Terminal application, type the following, and hit Enter.

which pod

You should see something similar to the following output:

/usr/bin/pod

Depending on how you manage your Ruby gems, for instance if you use rbenv or RVM, then your path may differ.

If the terminal simply returns to the prompt, or states pod not found, Cocoapods isn’t installed on your machine; check out our tutorial on Cocoapods for installation instructions. It’s also a great resource if you just want to learn more about Cocoapods.

Podfiles are used to tell Cocoapods which Pods, or open-source projects, to import.

To create your first Cocoapod, first use the cd command in Terminal to navigate to the the folder where you saved your Xcode project. Launch the Pico editor by entering the pico command in Terminal.

Once pico has opened, paste in the following lines:

platform :ios, '7.0'
 
pod 'Mantle'
pod 'LBBlurredImage'
pod 'TSMessages'
pod 'ReactiveCocoa'

This file does two things:

  • It tells Cocoapods which platform and which version you’re targeting. Here you’re targeting iOS 7.0.
  • It gives Cocoapods a list of all the projects you want to import and set up.

To save your file, press Control-O, name the file Podfile and hit Enter. Now press Control-X to exit Pico.

To install the four Pods noted in your Podfile, type the following into Terminal and hit Enter.

pod install

Be patient — it could take a minute or two for pod to finish installing the various packages. Your Terminal output should look like the following:

You should see output like this:

$ pod install
Analyzing dependencies
 
CocoaPods 0.28.0 is available.
 
Downloading dependencies
Installing HexColors (2.2.1)
Installing LBBlurredImage (0.1.0)
Installing Mantle (1.3.1)
 
Installing ReactiveCocoa (2.1.7)
Installing TSMessages (0.9.4)
Generating Pods project
Integrating client project
 
[!] From now on use `SimpleWeather.xcworkspace`.

Cocoapods will create a bunch of new files in your project directory; however, the only one that you’ll be concerned about is SimpleWeather.xcworkspace.

Open SimpleWeather.xcworkspace in Xcode. Take a look at your project setup; there’s now a Pods project in the workspace, as well as folders for each of the libraries you imported in the Pods folder, like so:

Cocoapods Project

Make sure you have the SimpleWeather project selected, as shown below:

Select SimpleWeather Project

Build and run your app to make sure everything is working properly:

Blank App

It doesn’t look like much right now, but you’ll add some content shortly.

Note: You might notice a few projects have build warnings. Projects imported with Cocoapods are made by many different developers, and different developers have different tolerances for build warnings. Most of the time you should be able to ignore them. Just make sure there are no compiler errors!

Creating Your Main View Controller

Although the app looks complex, it will be powered by a single view controller. You’ll add that now.

With the SimpleWeather project selected, click File\New\File and select Cocoa Touch\Objective-C class. Name your class WXController and make it a subclass of UIViewController.

Make sure that both Targeted for iPad and With XIB for user interface are both unchecked, as shown below:

Create WXController

Open WXController.m and replace the boilerplate -viewDidLoad method with the following:

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // Remove this later
    self.view.backgroundColor = [UIColor redColor];
}

Now open AppDelegate.m and import the following two classes:

#import "WXController.h"
#import <TSMessage.h>

Sharp-eyed readers will note that WXController is imported with quotes, while TSMessage is imported with angle brackets. What gives?

Look back to when you created the Podfile; you imported TSMessage with Cocoapods. Cocoapods created the TSMessage Pod project and added it to your workspace. Since you’re importing from other projects in the workspace, you use angle brackets instead of quotes.

Replace the contents of -application:didFinishLaunchingWithOptions: with:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 1
    self.window.rootViewController = [[WXController alloc] init];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    // 2
    [TSMessage setDefaultViewController: self.window.rootViewController];
    return YES;
}

Walking through the numbered comments, you’ll see the following:

  1. Initialize and set the WXController instance as the application’s root view controller. Usually this controller is a UINavigationController or UITabBarController, but in this case you’re using a single instance of WXController.
  2. Set the default view controller to display your TSMessages. By doing this, you won’t need to manually specify which controller to use to display alerts.

Build and run to see your new view controller in action:

WXController

The status bar is a little difficult to read against the red background. Fortunately, there’s an easy way to make the status bar a lot more legible.

There’s a new API in UIViewController in iOS 7 to control the appearance of the status bar. Open WXController and add the following code directly below -viewDidLoad:

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

Build and run again and you’ll see the following change to the status bar:

Create WXController with Light Status Bar

Setting Up Your App’s Views

It’s time to make your app come to life. Download the images for this project here and unzip them to a convenient location. This package contains a background image by Flickr user idleformat and weather icons by Dribbble user heeyeun.

Note: The background image is of San Francisco since the iPhone Simulator defaults its geolocation to that city. If you want to personalize your app, feel free to substitute an image of your own hometown — or any image you choose, for that matter.

Switch back back to Xcode and click File\Add Files to “SimpleWeather”…. Locate the Images folder you just unzipped and select it. Check the Copy items into destination group’s folder (if needed) option and click Add.

Open WXController.h and add the following delegate protocols directly below the @interface line:

<UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>

Now open WXController.m. Protip: you can use the hotkey Control-Command-Up to quickly toggle between the .h and .m files.

Add the following import to the top of WXController.m:

#import <LBBlurredImage/UIImageView+LBBlurredImage.h>

LBBlurredImage.h is contained in the LBBlurredImage project you imported with Cocoapods; you’ll use this library to blur your background image.

There should be an empty private interface boilerplate for WXController beneath the imports. Fill it in with the following properties:

@interface WXController ()
 
@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *blurredImageView;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign) CGFloat screenHeight;
 
@end

Now it’s time to create and set the views in your project. “But wait,” you say. “Where are the IBOutlets?”

Be forewarned: all of the views are created and set up… in code! :]

Table Flip

Hang on — don’t freak out just yet. There are lots of ways to create views, and everyone has their favorite. We even held a debate on the RayWenderlich.com team on the different styles and what each person prefers. Storyboards, NIBs, and code all have their pros and cons.

The views in this app aren’t terribly complicated and don’t necessarily have the performance hit that Auto-Layout can cause. However, since this is a case study, you’ll stick to using code.

You’ll create three views and stack them on top of each other to create the effect that you saw in the GIF at the beginning of this tutorial. Here’s an exploded view of what you’ll make, bearing in mind that the table view will be transparent:

Exploded Screens

To provide the dynamic blur effect, you’ll change the alpha of the blurred image as you scroll through your app.

Open up WXController.m and replace the code in -viewDidLoad that sets the background color with the following code:

// 1
self.screenHeight = [UIScreen mainScreen].bounds.size.height;
 
UIImage *background = [UIImage imageNamed:@"bg"];
 
// 2
self.backgroundImageView = [[UIImageView alloc] initWithImage:background];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.backgroundImageView];
 
// 3
self.blurredImageView = [[UIImageView alloc] init];
self.blurredImageView.contentMode = UIViewContentModeScaleAspectFill;
self.blurredImageView.alpha = 0;
[self.blurredImageView setImageToBlur:background blurRadius:10 completionBlock:nil];
[self.view addSubview:self.blurredImageView];
 
// 4
self.tableView = [[UITableView alloc] init];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.2];
self.tableView.pagingEnabled = YES;
[self.view addSubview:self.tableView];

This is pretty straightforward code:

  1. Get and store the screen height. You’ll need this later when displaying all of the weather data in a paged manner.
  2. Create a static image background and add it to the view.
  3. Create a blurred background image using LBBlurredImage, and set the alpha to 0 initially so that backgroundImageView is visible at first.
  4. Create a tableview that will handle all the data presentation. WXController will be the delegate and data source, as well as the scroll view delegate. Note that you’re also setting pagingEnabled to YES.

Add the following code for the UITableView delegate and data source to the @implementation block of WXController.m:

// 1
#pragma mark - UITableViewDataSource
 
// 2
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // TODO: Return count of forecast
    return 0;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (! cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
    }
 
    // 3
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.detailTextLabel.textColor = [UIColor whiteColor];
 
    // TODO: Setup the cell
 
    return cell;
}
 
#pragma mark - UITableViewDelegate
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // TODO: Determine cell height based on screen
    return 44;
}

Although some of the above code is placeholders, here’s what you have so far:

  1. Pragma marks are a great way to help organize your code..
  2. Your table view has two sections, one for hourly forecasts and one for daily. You always return 2 for the number of table view sections.
  3. Forecast cells shouldn’t be selectable. Give them a semi-transparent black background and white text.
Note: Using the formatted comment // TODO: helps Xcode find code you need to complete later. You can even view TODO items using Show Document Items (Control-6).

Finally, add the following method to WXController.m:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
 
    CGRect bounds = self.view.bounds;
 
    self.backgroundImageView.frame = bounds;
    self.blurredImageView.frame = bounds;
    self.tableView.frame = bounds;
}

Your view controller calls the above method in order to lay out its subviews in WXController.m.

Build and run your app to see how your views stack up — (pardon the pun!) :

Image Background

Look closely and you’ll see the individual cell separators for all of your empty table cells.

Still in -viewDidLoad, add the following code to set up your layout frames and margins:

// 1
CGRect headerFrame = [UIScreen mainScreen].bounds;
// 2
CGFloat inset = 20;
// 3
CGFloat temperatureHeight = 110;
CGFloat hiloHeight = 40;
CGFloat iconHeight = 30;
// 4
CGRect hiloFrame = CGRectMake(inset, 
                              headerFrame.size.height - hiloHeight,
                              headerFrame.size.width - (2 * inset),
                              hiloHeight);
 
CGRect temperatureFrame = CGRectMake(inset, 
                                     headerFrame.size.height - (temperatureHeight + hiloHeight),
                                     headerFrame.size.width - (2 * inset),
                                     temperatureHeight);
 
CGRect iconFrame = CGRectMake(inset, 
                              temperatureFrame.origin.y - iconHeight, 
                              iconHeight, 
                              iconHeight);
// 5
CGRect conditionsFrame = iconFrame;
conditionsFrame.size.width = self.view.bounds.size.width - (((2 * inset) + iconHeight) + 10);
conditionsFrame.origin.x = iconFrame.origin.x + (iconHeight + 10);

This is fairly routine setup code, but here’s what’s going on:

  1. Set the header of your table to be the same size of your screen. You’ll be taking advantage of UITableView’s paging which will page the header and the daily and hourly forecast sections.
  2. Create an inset (or padding) variable so that all your labels are evenly spaced and centered.
  3. Create and initialize the height variables for your various views. Setting these values as constants makes it easy to configure and changing your view setup if required.
  4. Create frames for your labels and icon view based on the constant and inset variables.
  5. Copy the icon frame, adjust it so the text has some room to expand, and move it to the right of the icon. You’ll see how this layout math works once we add the label to the view below.

Add the following code below the frame code in -viewDidLoad:

// 1
UIView *header = [[UIView alloc] initWithFrame:headerFrame];
header.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = header;
 
// 2
// bottom left
UILabel *temperatureLabel = [[UILabel alloc] initWithFrame:temperatureFrame];
temperatureLabel.backgroundColor = [UIColor clearColor];
temperatureLabel.textColor = [UIColor whiteColor];
temperatureLabel.text = @"0°";
temperatureLabel.font = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:120];
[header addSubview:temperatureLabel];
 
// bottom left
UILabel *hiloLabel = [[UILabel alloc] initWithFrame:hiloFrame];
hiloLabel.backgroundColor = [UIColor clearColor];
hiloLabel.textColor = [UIColor whiteColor];
hiloLabel.text = @"0° / 0°";
hiloLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:28];
[header addSubview:hiloLabel];
 
// top
UILabel *cityLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 30)];
cityLabel.backgroundColor = [UIColor clearColor];
cityLabel.textColor = [UIColor whiteColor];
cityLabel.text = @"Loading...";
cityLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
cityLabel.textAlignment = NSTextAlignmentCenter;
[header addSubview:cityLabel];
 
UILabel *conditionsLabel = [[UILabel alloc] initWithFrame:conditionsFrame];
conditionsLabel.backgroundColor = [UIColor clearColor];
conditionsLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
conditionsLabel.textColor = [UIColor whiteColor];
[header addSubview:conditionsLabel];
 
// 3
// bottom left
UIImageView *iconView = [[UIImageView alloc] initWithFrame:iconFrame];
iconView.contentMode = UIViewContentModeScaleAspectFit;
iconView.backgroundColor = [UIColor clearColor];
[header addSubview:iconView];

That’s quite a chunk of code, but it’s really just doing the heavy lifting of setting up the various controls in your view. In short:

  1. Set the current-conditions view as your table header.
  2. Build each required label to display weather data.
  3. Add an image view for a weather icon.

Build and run your app; you should see all of your views laid out before you. The screenshot below shows all of the labels along with their frames for a visual on manual layout:

Labels and Views

Give the table a nudge with your finger; it should bounce when you scroll it.

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.

Solution Inside: Solution SelectShow

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.

Solution Inside: Solution SelectShow

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

Where To Go From Here?

Feel free to download a copy of the completed project up to this point.

In this part of the tutorial, you set up your project with Cocoapods, added views to the controller, laid out those views and built models to reflect the weather data you will be fetching. The app still isn’t fully functional, but you’ve survived creating views directly in code, and learned how to map and transform JSON data using Mantle.

Next check out part 2 of this tutorial, where you’ll flesh out your app to fetch data from the weather API and wire up your UI. You’ll be using the new iOS 7 NSURLSession to download data along with ReactiveCocoa to tie together the location finding, weather fetching, and UI updating events.

If you have any questions or comments, please share them with us in the forums!

Ryan Nystrom

Ryan is a Software Engineer at Facebook. He is a passionate open source contributor where he builds UI controls and frameworks for others to use and learn from. In his spare time he enjoys flying planes as a private pilot. You can reach Ryan on Twitter or email.

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Come check out Alt U

Our Books

Our Team

Video Team

... 9 total!

Swift Team

... 15 total!

iOS Team

... 47 total!

Android Team

... 15 total!

OS X Team

... 12 total!

Apple Game Frameworks Team

... 15 total!

Unity Team

... 11 total!

Articles Team

... 8 total!