by

This recipe will cover building a basic weather application for the iPhone from start to finish (no graphics needed, all code). Initially, this application will be very basic, but many enhancements/improvements may be the topic of future recipes. This recipe will move fast! If you find yourself having trouble keeping up you should go back and work through our earlier recipes before attempting this one. When you’re done with this recipe, you should have an app that looks like the following:

Before We Get Started

This application will be built using CocoaPods which is an Objective-C dependency manager. If you are not familiar with CocoaPods or just need some help getting it installed please visit their website. Additionally, there is a very good video tutorial on CocoaPods that NSScreencast has done which is currently free to view. Make sure you get CocoaPods installed on your system before starting this article, because installation will not be covered in the recipe .

. The application will also use API provided by Weather Underground which will require you to sign up for a free developer API Key (limited to 500 requests per day I believe). Don’t worry this is very simple and will be covered in the recipe.

The source code to this recipe is available online through GitHub, and it may help to check it out when working through this tutorial.

Let’s Get Started

Creating The Project

Open Xcode and create a new Single View Application.

Name the application Weather (choose your own name if you like but, we will refer to it as weather from now on). This application will be for iPhone only so select iPhone from the devices drop-down menu. Make sure Automatic Reference Counting has been checked.

Setting Up CocoaPods

Close the project you just created.

Open Terminal and navigate to the directory containing weather.xcodeproj

and navigate to the directory containing Make sure CocoaPods is installed on your system then type pod install. This will fail with the following error if you have CocoaPods properly installed.

The error basically says CocoaPods did not find a Podfile in our project root directory, which is because we haven’t created one. So let’s create one!

As the image above shows, we are using TextMate to create this file, but use whatever text editor you like.

If you are familiar with CocoaPods, the following Podfile syntax should look familiar to you. Unfortunately there is not enough time to cover Podfiles in this recipe, please refer to NSScreenCast’s CocoaPods Video Tutorial for that.

In your text editor type the following Podfile, then save it in the project root direct as Podfile (no extension).

platform : ios, '6.0'

pod 'AFNetworking' , '~> 1.1'

pod 'SVProgressHUD' , '~> 0.8.1'

The Podfile describes to CocoaPods which open source dependencies our application needs. AFNetworking will be used for networking (communicating with Weather Underground Web Services) SVProgressHUD will be used in place of the default UIActivityIndicatorView

Once you have that Podfile in place, run pod install from the terminal again. You should now see the following output.

CocoaPods created a Pods folder, a Podfile.lock file, and a new workspace file for your application. It is important that you now open your app using the new weather.xcworkspace file instead of weather.xcodeproj. The workspace will contain the necessary dependencies that your app will need.

Registering With Weather Underground

You need to obtain a developer API key from Weather Underground in order to access weather data through their services. This is very easy just click here to get started.

All you need is a free developer account (of course if you plan on making a commercial weather app, you will need more than that).

After registering make sure you copy your Developer API Key, you will need it shortly.

Add Necessary Frameworks

Open weather.xcworkspace , you will see 2 projects, one called Pods and the one you created called weather . The Pods project contains both AFNetworking and SVProgressHUD, all we have to do to use them in weather is import the their respective header file! Isn’t that great!

, you will see 2 projects, one called and the one you created called . The Pods project contains both AFNetworking and SVProgressHUD, all we have to do to use them in is import the their respective header file! Isn’t that great! We will also need to add a few frameworks to our target so let’s do that.

You will need to make sure all of the frameworks listed below have been added to your weather target. QuartzCore CoreLocation MobileCoreServices SystemConfiguration UIKit Foundation CoreGraphics

Once those frameworks have been added, open weather-Prefix.pch (the pre-compiled header) and a few of those newly added frameworks as shown below.

#import



#ifndef __IPHONE_4_0

#warning "This project uses features only available in iOS SDK 4.0 and later."

#endif



#ifdef __OBJC__

#import <UIKit/UIKit.h>

#import <Foundation/Foundation.h>

#import <SystemConfiguration/SystemConfiguration.h>

#import <MobileCoreServices/MobileCoreServices.h>

#import <CoreLocation/CoreLocation.h>

#import <QuartzCore/QuartzCore.h>

#endif

Build your weather target, everything should build without warnings.

Setting Up An AFNetworking HTTPClient

First we are going to need the API key from Weather Underground in our app, so let’s create a header file with a const for that. Create a new header file named WeatherAPIKey.h.

#ifndef Weather_WeatherAPIKey_h

#define Weather_WeatherAPIKey_h



//Sign up at http://www.wunderground.com/weather/api to get a free developer key

static NSString * const kWeatherUndergroundAPIKey = @ "your_api_key_goes_here" ;



#endif kWeatherUndergroundAPIKey

Make sure you replace the default text with your API key.

Next, add another new class to the project called WeatherClient and tell Xcode to inherit from NSObject to create it. We are going to change the inheritance to AFHTTPClient after it has been created.

and tell Xcode to inherit from to create it. We are going to change the inheritance to after it has been created. Open WeatherClient.h and add the following interface. Notice we changed the inheritance to AFHTTPClient and imported AFNetworking.

#import <AFNetworking/AFNetworking.h>



@interface WeatherClient : AFHTTPClient



+ ( instancetype ) sharedClient;



@end

Now open WeatherClient.m, let’s implement the singleton class method we created in the header. We are also going to import the WeatherAPIKey.h header we created so the HTTPClient can access the API key.

#import "WeatherClient.h"

#import "WeatherAPIKey.h"



static NSString * const kWeatherUndergroundAPIBaseURLString = @ "http://api.wunderground.com/api/" ;



@implementation WeatherClient



#pragma mark - Singleton



+ ( instancetype ) sharedClient

{

static WeatherClient * sharedClient = nil ;

static dispatch_once_t onceToken;

dispatch_once ( & amp;onceToken, ^ {

NSString * baseURLString = [ kWeatherUndergroundAPIBaseURLString stringByAppendingString : kWeatherUndergroundAPIKey ] ;

sharedClient = [ [ self alloc ] initWithBaseURL : [ URLWithString : baseURLString ] ] ;

} ) ;



return sharedClient;

}



#pragma mark - Initialization



- ( id ) initWithBaseURL : ( NSURL * ) url

{

self = [ super initWithBaseURL : url ] ;

if ( self )

{

[ self registerHTTPOperationClass : [ AFJSONRequestOperation class ] ] ;

[ self setDefaultHeader : @ "Accept" value : @ "application/json" ] ;

}



return self;

} kWeatherUndergroundAPIBaseURLStringWeatherClientinstancetypesharedClientWeatherClientsharedClientdispatch_once_t onceToken;dispatch_onceamp;onceToken,baseURLStringkWeatherUndergroundAPIBaseURLString stringByAppendingStringkWeatherUndergroundAPIKeysharedClientself allocinitWithBaseURL NSURL URLWithStringbaseURLStringsharedClient;initWithBaseURLurlselfsuper initWithBaseURLurlselfself registerHTTPOperationClassAFJSONRequestOperation classself setDefaultHeadervalueself;

The code above implements a singleton class method that calls the AFNetworking default initializer initWithBaseURL: to create a HTTP Client. We define the Weather Underground API base URL as a constant as well. For the Base URL parameter we pass to initWithBaseURL: we append our API key to the base URL constant.

to create a HTTP Client. We define the Weather Underground API base URL as a constant as well. For the Base URL parameter we pass to we append our API key to the base URL constant. We then override initWithBaseURL: to set our HTTP client up for talking to JSON services.

Creating A Data Model

Weather Underground has documentation for all of the API they support and what responses will be given. Since this is a simple app to start with, we will only need to use their conditions API. Here is an example of how this API can be called.

http : //api.wunderground.com/api/your_api_key/conditions/q/CA/San_Francisco.json

This would request the current conditions for San Francisco. We can also pass in raw latitude and longitude values like so.

http : //api.wunderground.com/api/your_api_key/conditions/q/37.78953934,-122.40170288.json

Unfortunately, this is not exactly standard REST style so we won’t be able to take full advantage of the simplicity of AFNetworking’s API, but it will still be very easy.

Each of these calls will return a JSON result as specified in their docs. Basically the response has two top level keys, response and current_observation . The second will be of interest to us, because it contains all the weather data for the observation. Visit the conditions API reference for details and more examples.

and . The second will be of interest to us, because it contains all the weather data for the observation. Visit the conditions API reference for details and more examples. Let’s create a class to model this observation data. Add a new class to the project named Observation , have it inherit from NSObject.

, have it inherit from NSObject. Open Observation.h add the following interface.

#import <Foundation/Foundation.h>



@interface Observation : NSObject



@property ( nonatomic, strong ) NSDictionary * location;

@property ( nonatomic, strong ) NSDictionary * observationLocation;

@property ( nonatomic, strong ) NSDictionary * weatherUndergroundImageInfo;



@property ( nonatomic, strong ) NSString * timeString;

@property ( nonatomic, strong ) NSString * timeStringRFC822;

@property ( nonatomic, strong ) NSString * weatherDescription;

@property ( nonatomic, strong ) NSString * windDescription;

@property ( nonatomic, strong ) NSString * temperatureDescription;

@property ( nonatomic, strong ) NSString * feelsLikeTemperatureDescription;

@property ( nonatomic, strong ) NSString * relativeHumidity;

@property ( nonatomic, strong ) NSString * dewpointDescription;

@property ( nonatomic, strong ) NSString * iconName;

@property ( nonatomic, strong ) NSString * iconUrl;



@property ( nonatomic, strong ) NSNumber * temperatureF;

@property ( nonatomic, strong ) NSNumber * temperatureC;

@property ( nonatomic, strong ) NSNumber * feelsLikeTemperatureF;

@property ( nonatomic, strong ) NSNumber * feelsLikeTemperatureC;



+ ( instancetype ) observationWithDictionary : ( NSDictionary * ) dictionary;



@end Observationnonatomic, stronglocation;nonatomic, strongobservationLocation;nonatomic, strongweatherUndergroundImageInfo;nonatomic, strongtimeString;nonatomic, strongtimeStringRFC822;nonatomic, strongweatherDescription;nonatomic, strongwindDescription;nonatomic, strongtemperatureDescription;nonatomic, strongfeelsLikeTemperatureDescription;nonatomic, strongrelativeHumidity;nonatomic, strongdewpointDescription;nonatomic, strongiconName;nonatomic, strongiconUrl;nonatomic, strongtemperatureF;nonatomic, strongtemperatureC;nonatomic, strongfeelsLikeTemperatureF;nonatomic, strongfeelsLikeTemperatureC;instancetypeobservationWithDictionarydictionary;

The properties are just a subset of the data that the conditions endpoint returns, but it is all we will need for now. The class method gives us a way to create an observation object from a dictionary (specifically the JSON object returned from the web service).

endpoint returns, but it is all we will need for now. The class method gives us a way to create an observation object from a dictionary (specifically the JSON object returned from the web service). Now open Observation.m and add the following implementation

#import "Observation.h"



@implementation Observation



+ ( NSDictionary * ) keyMapping

{

static NSDictionary * keyMapping = nil ;

static dispatch_once_t onceToken;

dispatch_once ( & amp;onceToken, ^ {



keyMapping = @ {

@ "display_location" : @ "location" ,

@ "observation_location" : @ "observationLocation" ,

@ "image" : @ "weatherUndergroundImageInfo" ,

@ "observation_time" : @ "timeString" ,

@ "observation_time_rfc822" : @ "timeStringRFC822" ,

@ "weather" : @ "weatherDescription" ,

@ "wind_string" : @ "windDescription" ,

@ "temperature_string" : @ "temperatureDescription" ,

@ "feelslike_string" : @ "feelsLikeTemperatureDescription" ,

@ "relative_humidity" : @ "relativeHumidity" ,

@ "dewpoint_string" : @ "dewpointDescription" ,

@ "icon" : @ "iconName" ,

@ "icon_url" : @ "iconUrl" ,

@ "temp_f" : @ "temperatureF" ,

@ "temp_c" : @ "temperatureC" ,

@ "feelslike_f" : @ "feelsLikeTemperatureF" ,

@ "feelslike_c" : @ "feelsLikeTemperatureC"

} ;

} ) ;



return keyMapping;

}



+ ( instancetype ) observationWithDictionary : ( NSDictionary * ) dictionary

{

Observation * observation = nil ;

if ( dictionary )

{

observation = [ [ Observation alloc ] init ] ;

NSDictionary * keyMapping = [ self keyMapping ] ;

for ( NSString * key in keyMapping )

{

id value = dictionary [ key ] ;

if ( value )

{

[ observation setValue : value forKey : keyMapping [ key ] ] ;

}

}

}



return observation;

}



@end ObservationkeyMappingkeyMappingdispatch_once_t onceToken;dispatch_onceamp;onceToken,keyMappingkeyMapping;instancetypeobservationWithDictionarydictionaryObservationobservationdictionaryobservationObservation allocinitkeyMappingself keyMappingkeykeyMappingvaluedictionarykeyvalueobservation setValuevalue forKeykeyMappingkeyobservation;

The first class method keyMapping maps our property names to the parameter names returned from the server. While most servers have a standard convention that would allow us to bypass this mapping, I have found the mapping to be helpful in understanding the relation between model and server. Additionally, if we use this mapping we can name our properties whatever we want!

maps our property names to the parameter names returned from the server. While most servers have a standard convention that would allow us to bypass this mapping, I have found the mapping to be helpful in understanding the relation between model and server. Additionally, if we use this mapping we can name our properties whatever we want! The second method observationWithDictionary: takes a dictionary of key values returned from the server and uses the mapping and KVC to filter those into our properties.

Integrating The Model With Our HTTPClient

In our WeatherClient we need to create a method that makes the conditions request and returns one of a our Observation model objects.

model objects. Open WeatherClient.h and add the following method declaration. The method takes a CLLocation object and returns a block which has an Observation object and an error.

#import <AFNetworking/AFNetworking.h>

#import "Observation.h"



@interface WeatherClient : AFHTTPClient



+ ( WeatherClient * ) sharedClient;



- ( void ) getCurrentWeatherObservationForLocation : ( CLLocation * ) location completion : ( void ( ^ ) ( Observation * observation, * error ) ) completion;



@end WeatherClientAFHTTPClientWeatherClientsharedClient;getCurrentWeatherObservationForLocationCLLocationlocation completionObservationobservation, NSError errorcompletion;

Open WeatherClient.m and add the following implementation for getCurrentWeatherObservationForLocation:completion:

- ( void ) getCurrentWeatherObservationForLocation : ( CLLocation * ) location completion : ( void ( ^ ) ( Observation * observation, * error ) ) completion

{

if ( location )

{

// We have to do this because their API is not exactly rest

NSString * getPath = [ stringWithFormat : @ "conditions/q/%.6f,%.6f.json" , location.coordinate.latitude, location.coordinate.longitude ] ;

WeatherClient * client = [ WeatherClient sharedClient ] ;

[ client getPath : getPath

parameters : nil

success :^ ( AFHTTPRequestOperation * operation, id responseObject ) {

Observation * observation = [ Observation observationWithDictionary : responseObject [ @ "current_observation" ] ] ;

completion ( observation, nil ) ;

}

failure :^ ( AFHTTPRequestOperation * operation, * error ) {

completion ( nil , error ) ;

}

] ;

}

else

{

completion ( nil , [ errorWithDomain : @ "Invalid Location as argument" code :- 1 userInfo : nil ] ) ;

}

} getCurrentWeatherObservationForLocationCLLocationlocation completionObservationobservation, NSError errorcompletionlocationgetPath NSString stringWithFormat, location.coordinate.latitude, location.coordinate.longitudeWeatherClientclientWeatherClient sharedClientclient getPathgetPathparameterssuccessAFHTTPRequestOperationoperation,responseObjectObservationobservationObservation observationWithDictionaryresponseObjectcompletionobservation,failureAFHTTPRequestOperationoperation, NSError errorcompletion, errorcompletion NSError errorWithDomaincodeuserInfo

The method first checks for a valid location, then creates the path for the conditions endpoint as specified by Weather Underground’s API Reference. We add the latitude and longitude from the CLLocation object as parameters of the request. We cannot pass these in as parameters to the AFNetworking getPath:parameters:success:failure: because that method expects a more standard format like the following.

http : //website.com/api?lat=12.2&lon=39

We then provide two blocks along with the getPath to getPath:parameters:success:failure:, one that is called if the request is successful and one if it is not. If the request is successful we create an Observation object and pass it on in our completion block, if it is not we pass nil for the observation along with the error received.

Getting A Location

Now that we have our WeatherClient complete, we need to get a location from the iPhone in order to use getCurrentWeatherObservationForLocation:completion: .

. Create a new class that inherits from NSObject called LocationManager . This locationManager will help us manage using CoreLocation to retrieve the current location of the device.

. This locationManager will help us manage using CoreLocation to retrieve the current location of the device. Open LocationManager.h and add the following interface.

#import <Foundation/Foundation.h>



extern NSString * const kLocationDidChangeNotificationKey;



@interface LocationManager : NSObject



@property ( nonatomic, readonly ) CLLocation * currentLocation;

@property ( nonatomic, readonly ) BOOL isMonitoringLocation;



+ ( instancetype ) sharedManager;



- ( void ) startMonitoringLocationChanges;

- ( void ) stopMonitoringLocationChanges;



@end kLocationDidChangeNotificationKey;LocationManagernonatomic, readonlyCLLocationcurrentLocation;nonatomic, readonlyisMonitoringLocation;instancetypesharedManager;startMonitoringLocationChanges;stopMonitoringLocationChanges;

The string constant at the top defines a notification name. This will be used to signal location changes.

We then add a singleton class method, a couple properties, and methods for starting and stopping location monitoring.

Open LocationManager.m and add the following implementation.

#import "LocationManager.h"



NSString * const kLocationDidChangeNotificationKey = @ "locationManagerlocationDidChange" ;



@interface LocationManager ( ) <CLLocationManagerDelegate>



@property ( nonatomic, strong ) CLLocationManager * locationManager;

@property ( nonatomic, readwrite ) BOOL isMonitoringLocation;



@end



@implementation LocationManager



+ ( instancetype ) sharedManager

{

static LocationManager * _sharedLocationManager = nil ;

static dispatch_once_t onceToken;

dispatch_once ( & amp;onceToken, ^ {

_sharedLocationManager = [ [ LocationManager alloc ] init ] ;

} ) ;



return _sharedLocationManager;

}



#pragma mark - Public API



- ( void ) startMonitoringLocationChanges

{

if ( [ CLLocationManager locationServicesEnabled ] )

{

if ( ! self.isMonitoringLocation )

{

self.isMonitoringLocation = YES ;

self.locationManager.delegate = self;

[ self.locationManager startMonitoringSignificantLocationChanges ] ;

}

}

else

{

UIAlertView * servicesDisabledAlert = [ [ UIAlertView alloc ] initWithTitle : @ "Location Services Disabled" message : @ "This app requires location services to be enabled" delegate : nil cancelButtonTitle : @ "OK" otherButtonTitles : nil ] ;

[ servicesDisabledAlert show ] ;

}

}



- ( void ) stopMonitoringLocationChanges

{

if ( _locationManager )

{

[ self.locationManager stopMonitoringSignificantLocationChanges ] ;

self.locationManager.delegate = nil ;

self.isMonitoringLocation = NO ;

self.locationManager = nil ;

}

}



#pragma mark - Accessors



- ( CLLocationManager * ) locationManager

{

if ( ! _locationManager )

{

_locationManager = [ [ CLLocationManager alloc ] init ] ;

}



return _locationManager;

}



- ( CLLocation * ) currentLocation

{

return self.locationManager.location;

}



#pragma mark - CLLocationManagerDelegate



- ( void ) locationManager : ( CLLocationManager * ) manager didUpdateToLocation : ( CLLocation * ) newLocation fromLocation : ( CLLocation * ) oldLocation

{

NSMutableDictionary * userInfo = [ dictionaryWithCapacity : 2 ] ;

if ( newLocation ) {

userInfo [ @ "newLocation" ] = newLocation;

}

if ( oldLocation ) {

userInfo [ @ "oldLocation" ] = oldLocation;

}



[ [ defaultCenter ] postNotificationName : kLocationDidChangeNotificationKey

object : self

userInfo : userInfo ] ;

}



- ( void ) locationManager : ( CLLocationManager * ) manager didFailWithError : ( NSError * ) error

{

if ( [ error code ] == kCLErrorDenied )

{

UIAlertView * servicesDisabledAlert = [ [ UIAlertView alloc ] initWithTitle : @ "Location Services Denied" message : @ "This app requires location services to be allowed" delegate : nil cancelButtonTitle : @ "OK" otherButtonTitles : nil ] ;

[ servicesDisabledAlert show ] ;

}

}



@end kLocationDidChangeNotificationKeyLocationManager nonatomic, strongCLLocationManagerlocationManager;nonatomic, readwriteisMonitoringLocation;LocationManagerinstancetypesharedManagerLocationManager_sharedLocationManagerdispatch_once_t onceToken;dispatch_onceamp;onceToken,_sharedLocationManagerLocationManager allocinit_sharedLocationManager;startMonitoringLocationChangesCLLocationManager locationServicesEnabledself.isMonitoringLocationself.isMonitoringLocationself.locationManager.delegateself;self.locationManager startMonitoringSignificantLocationChangesUIAlertViewservicesDisabledAlertUIAlertView allocinitWithTitlemessagedelegatecancelButtonTitleotherButtonTitlesservicesDisabledAlert showstopMonitoringLocationChanges_locationManagerself.locationManager stopMonitoringSignificantLocationChangesself.locationManager.delegateself.isMonitoringLocationself.locationManagerCLLocationManagerlocationManager_locationManager_locationManagerCLLocationManager allocinit_locationManager;CLLocationcurrentLocationself.locationManager.location;locationManagerCLLocationManagermanager didUpdateToLocationCLLocationnewLocation fromLocationCLLocationoldLocationuserInfo NSMutableDictionary dictionaryWithCapacitynewLocationuserInfonewLocation;oldLocationuserInfooldLocation; NSNotificationCenter defaultCenterpostNotificationNamekLocationDidChangeNotificationKeyobjectselfuserInfouserInfolocationManagerCLLocationManagermanager didFailWithErrorerrorerror codekCLErrorDeniedUIAlertViewservicesDisabledAlertUIAlertView allocinitWithTitlemessagedelegatecancelButtonTitleotherButtonTitlesservicesDisabledAlert show

The first method is the class singleton, which you should now be comfortable with. The second and third, startMonitoringLocationChanges and stopMonitoringLocationChanges respectively, call off to our private CLLocationManager property (defined in the class extension at the top) to start and stop location monitoring. The start monitoring method also performs a permissions check to ensure we have been granted location monitoring authorization.

and respectively, call off to our private CLLocationManager property (defined in the class extension at the top) to start and stop location monitoring. The start monitoring method also performs a permissions check to ensure we have been granted location monitoring authorization. Next, you will see the locationManager lazy accessor which creates our CLLocationManager if it doesn’t yet exist.

lazy accessor which creates our CLLocationManager if it doesn’t yet exist. Finally, we get to the two CLLocationManager delegate methods, the first of which locationManager:didUpdateToLocation:fromLocation: fires off a notification each time a new location is determined. The second alerts the user that the app needs location services to be allowed in the event we are denied.

fires off a notification each time a new location is determined. The second alerts the user that the app needs location services to be allowed in the event we are denied. We also need to add a few methods to AppDelegate.m in order to properly stop location monitoring.

- ( void ) applicationDidEnterBackground : ( UIApplication * ) application

{

// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.

// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

[ [ LocationManager sharedManager ] stopMonitoringLocationChanges ] ;

}



- ( void ) applicationWillEnterForeground : ( UIApplication * ) application

{

// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.

[ [ LocationManager sharedManager ] startMonitoringLocationChanges ] ;

}



- ( void ) applicationWillTerminate : ( UIApplication * ) application

{

// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.

[ [ LocationManager sharedManager ] stopMonitoringLocationChanges ] ;

}

Setting Up The Requests

Now that we have location information, we need to pass it to the WeatherClient in order to request weather data from Weather Underground. We will do this in a reloadData method in ViewController.m

- ( void ) reloadData

{

WeatherClient * client = [ WeatherClient sharedClient ] ;

CLLocation * location = [ [ LocationManager sharedManager ] currentLocation ] ;



[ SVProgressHUD showWithMaskType : SVProgressHUDMaskTypeGradient ] ;



__weak ViewController * weakSelf = self;

[ client getCurrentWeatherObservationForLocation : location completion :^ ( Observation * observation, * error ) {

if ( error )

{

NSLog ( @ "Web Service Error: %@" , [ error description ] ) ;

}

else

{

[ weakSelf updateUIWithObservation : observation ] ;

}



[ SVProgressHUD dismiss ] ;

} ] ;

}



- ( void ) updateUIWithObservation : ( Observation * ) observation

{

// We will update our UI here...

} reloadDataWeatherClientclientWeatherClient sharedClientCLLocationlocationLocationManager sharedManagercurrentLocationSVProgressHUD showWithMaskTypeSVProgressHUDMaskTypeGradient__weak ViewControllerweakSelfself;client getCurrentWeatherObservationForLocationlocation completionObservationobservation, NSError errorerrorNSLogerror descriptionweakSelf updateUIWithObservationobservationSVProgressHUD dismissupdateUIWithObservationObservationobservation

InreloadData we first create a reference to our weather client and then get the current location from the locationManager and pass it in a call to getCurrentWeatherObservationForLocation:completion:. Once the request has finished, the completion block will be called with the retrieved Observation object. We use SVProgressHUD here to indicate background activity so you will also need to add an import for that to ViewController.m. The weakSelf prevents any retain cycles that could arise because of the block (even though it is probably unnecessary in this app). Finally, after we have the observation and there wasn’t an error we call updateUIWithObservation: which will update the user interface, then we dismiss the progress HUD.

Creating The User Interface

First we Need to create a view that displays a nice gradient background. Add a new UIView subclass to the project named GradientView .

. Open GradientView.m and add the following implementation, which will draw a nice blue gradient.

#import "GradientView.h"



#define GRADIENT_COLOR_1 [[UIColor colorWithRed:80/255.0 green:148/255.0 blue:180/255.0 alpha:1.0] CGColor]

#define GRADIENT_COLOR_2 [[UIColor colorWithRed:13/255.0 green:94/255.0 blue:180/255.0 alpha:1.0] CGColor]



@implementation GradientView



+ ( Class ) layerClass

{

return [ CAGradientLayer class ] ;

}



- ( void ) awakeFromNib

{

CAGradientLayer * gradientLayer = ( CAGradientLayer * ) self.layer;

gradientLayer.colors = @ [ ( id ) GRADIENT_COLOR_1, ( id ) GRADIENT_COLOR_2 ] ;

}



@end

Most of the UI will be done using ViewController.xib, but let’s add the following outlets and actions to ViewController.h first.

#import <UIKit/UIKit.h>

#import "GradientView.h"



@interface ViewController : UIViewController



@property ( strong, nonatomic ) IBOutlet UIView * shadowContainerView;

@property ( strong, nonatomic ) IBOutlet GradientView * observationContainerView;



@property ( strong, nonatomic ) IBOutlet UILabel * locationLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * currentTemperatureLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * feelsLikeTemperatureLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * weatherDescriptionLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * windDescriptionLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * humidityLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * dewpointLabel;

@property ( strong, nonatomic ) IBOutlet UILabel * lastUpdatedLabel;



@property ( strong, nonatomic ) IBOutlet UIImageView * currentConditionImageView;

@property ( strong, nonatomic ) IBOutlet UIImageView * weatherUndergroundImageView;



- ( IBAction ) refresh : ( id ) sender;



@end

These will be all the properties that we will need to create the user interface.

Now Open ViewController.m and add the following (final implementation).

#import "ViewController.h"



#import <SVProgressHUD/SVProgressHUD.h>



#import "WeatherClient.h"

#import "LocationManager.h"



@implementation ViewController



#pragma mark - UIViewController



- ( void ) viewDidLoad

{

[ super viewDidLoad ] ;

[ self setupViews ] ;



__weak ViewController * weakSelf = self;

[ [ defaultCenter ] addObserverForName : kLocationDidChangeNotificationKey

object : nil

queue : [ mainQueue ]

usingBlock :^ ( NSNotification * note ) {

NSLog ( @ "Note: %@" , note ) ;

[ weakSelf reloadData ] ;

} ] ;



[ [ LocationManager sharedManager ] startMonitoringLocationChanges ] ;

}



- ( void ) viewDidLayoutSubviews

{

// Need to adjust the shadow path when the views bounds change

self.shadowContainerView.layer.shadowPath = [ [ UIBezierPath bezierPathWithRoundedRect : self.shadowContainerView.bounds cornerRadius : 6.0f ] CGPath ] ;

}



- ( void ) didReceiveMemoryWarning

{

[ super didReceiveMemoryWarning ] ;

}



- ( void ) dealloc

{

[ [ defaultCenter ] removeObserver : self ] ;

[ [ LocationManager sharedManager ] stopMonitoringLocationChanges ] ;

}



#pragma mark - Private



- ( void ) setupViews

{

self.observationContainerView.clipsToBounds = YES ;

self.observationContainerView.layer.cornerRadius = 6.0f;

self.observationContainerView.layer.borderColor = [ [ UIColor whiteColor ] CGColor ] ;

self.observationContainerView.layer.borderWidth = 3.0f;



self.shadowContainerView.backgroundColor = [ UIColor clearColor ] ;

self.shadowContainerView.layer.shadowColor = [ [ UIColor blackColor ] CGColor ] ;

self.shadowContainerView.layer.shadowOffset = CGSizeZero;

self.shadowContainerView.layer.shadowOpacity = 0.65f;

self.shadowContainerView.layer.shadowRadius = 4.0f;

self.shadowContainerView.hidden = YES ;

}



- ( void ) updateUIWithObservation : ( Observation * ) observation

{

if ( observation )

{

self.shadowContainerView.hidden = NO ;



[ self.currentConditionImageView setImageWithURL : [ URLWithString : observation.iconUrl ] ] ;

[ self.weatherUndergroundImageView setImageWithURL : [ URLWithString : observation.weatherUndergroundImageInfo [ @ "url" ] ] ] ;



self.locationLabel.text = observation.location [ @ "full" ] ;

self.currentTemperatureLabel.text = observation.temperatureDescription;

self.feelsLikeTemperatureLabel.text = [ @ "Feels like " stringByAppendingString : observation.feelsLikeTemperatureDescription ] ;

self.weatherDescriptionLabel.text = observation.weatherDescription;

self.windDescriptionLabel.text = observation.windDescription;

self.humidityLabel.text = observation.relativeHumidity;

self.dewpointLabel.text = observation.dewpointDescription;

self.lastUpdatedLabel.text = observation.timeString;

}

else

{

self.shadowContainerView.hidden = YES ;

}

}



- ( void ) reloadData

{

WeatherClient * client = [ WeatherClient sharedClient ] ;

CLLocation * location = [ [ LocationManager sharedManager ] currentLocation ] ;



[ SVProgressHUD showWithMaskType : SVProgressHUDMaskTypeGradient ] ;



__weak ViewController * weakSelf = self;

[ client getCurrentWeatherObservationForLocation : location completion :^ ( Observation * observation, * error ) {

if ( error )

{

NSLog ( @ "Web Service Error: %@" , [ error description ] ) ;

}

else

{

[ weakSelf updateUIWithObservation : observation ] ;

}



[ SVProgressHUD dismiss ] ;

} ] ;

}



#pragma mark - Actions



- ( IBAction ) refresh : ( id ) sender

{

[ self reloadData ] ;

}



@end ViewControllerviewDidLoadsuper viewDidLoadself setupViews__weak ViewControllerweakSelfself; NSNotificationCenter defaultCenteraddObserverForNamekLocationDidChangeNotificationKeyobjectqueue NSOperationQueue mainQueueusingBlocknoteNSLog, noteweakSelf reloadDataLocationManager sharedManagerstartMonitoringLocationChangesviewDidLayoutSubviewsself.shadowContainerView.layer.shadowPathUIBezierPath bezierPathWithRoundedRectself.shadowContainerView.bounds cornerRadius6.0fCGPathdidReceiveMemoryWarningsuper didReceiveMemoryWarningdealloc NSNotificationCenter defaultCenterremoveObserverselfLocationManager sharedManagerstopMonitoringLocationChangessetupViewsself.observationContainerView.clipsToBoundsself.observationContainerView.layer.cornerRadius6.0f;self.observationContainerView.layer.borderColorUIColor whiteColorCGColorself.observationContainerView.layer.borderWidth3.0f;self.shadowContainerView.backgroundColorUIColor clearColorself.shadowContainerView.layer.shadowColorUIColor blackColorCGColorself.shadowContainerView.layer.shadowOffsetCGSizeZero;self.shadowContainerView.layer.shadowOpacity0.65f;self.shadowContainerView.layer.shadowRadius4.0f;self.shadowContainerView.hiddenupdateUIWithObservationObservationobservationobservationself.shadowContainerView.hiddenself.currentConditionImageView setImageWithURL NSURL URLWithStringobservation.iconUrlself.weatherUndergroundImageView setImageWithURL NSURL URLWithStringobservation.weatherUndergroundImageInfoself.locationLabel.textobservation.locationself.currentTemperatureLabel.textobservation.temperatureDescription;self.feelsLikeTemperatureLabel.textstringByAppendingStringobservation.feelsLikeTemperatureDescriptionself.weatherDescriptionLabel.textobservation.weatherDescription;self.windDescriptionLabel.textobservation.windDescription;self.humidityLabel.textobservation.relativeHumidity;self.dewpointLabel.textobservation.dewpointDescription;self.lastUpdatedLabel.textobservation.timeString;self.shadowContainerView.hiddenreloadDataWeatherClientclientWeatherClient sharedClientCLLocationlocationLocationManager sharedManagercurrentLocationSVProgressHUD showWithMaskTypeSVProgressHUDMaskTypeGradient__weak ViewControllerweakSelfself;client getCurrentWeatherObservationForLocationlocation completionObservationobservation, NSError errorerrorNSLogerror descriptionweakSelf updateUIWithObservationobservationSVProgressHUD dismissIBActionrefreshsenderself reloadData

This is the final implementation for ViewController.m . We added setupViews which we call from viewDidLoad , this is where we add a shadow to the shadowView and round the corners of the observationContainerView. We also set the initial hidden state that hides the content if we don’t have any weather data. Additionally, in viewDidLoad we will set ourselves as a listener to the locationManger’s location change notification (we add a block that calls reloadData ).

. We added which we call from , this is where we add a shadow to the shadowView and round the corners of the observationContainerView. We also set the initial hidden state that hides the content if we don’t have any weather data. Additionally, in we will set ourselves as a listener to the locationManger’s location change notification (we add a block that calls ). We also added an implementation for viewDidLayoutSubviews which is where we set the shadowPath for the shadowView.

which is where we set the shadowPath for the shadowView. Then we added the implementation of updateUIWithObservation: This sets our user interface properties using the observation model returned from our weatherClient. Notice the easy convenience method setImageWithURL: that AFNetworking supplies us (in a category on UIImageView) for loading images asynchronously.

This sets our user interface properties using the observation model returned from our weatherClient. Notice the easy convenience method that AFNetworking supplies us (in a category on UIImageView) for loading images asynchronously. An implementation was then added for refresh: which calls reloadData .

which calls . Finally, A call to reloadData was added to the CLLocationManagerDelegate method locationManager:didUpdateToLocation:fromLocation: .

was added to the CLLocationManagerDelegate method . Open ViewController.xib and add views to match the following interface build file.

Add a static label to the root view with the following text: No Data Available

Add a navigation Bar to the top, title it NSCookbook Weather add a UIBarButtonItem in the right of this navigation bar. Have it use system item refresh and link it to your refresh: action.

Add a label to the top directly under the navigation bar, link it to locationLabel

Add a UIView in the center, link it to shadowContainerView

Add a subview to shadowContainerView change it’s class to GradientView make it span the entire size of it’s superview and link it to observationContainerView .

change it’s class to make it span the entire size of it’s superview and link it to . All of the following UIElements will be subviews of observationContainerView Add a UIImageView to the upper left corner, link it to currentConditionImageView Add a label to the right of the imageView, link it to currentTemperatureLabel Add a label directly below that label, link it to feelsLikeTemperatureLabel Add a label directly below the imageView, link it to weatherDescriptionLabel Add a static label with the text Wind Add a label directly below that, link it to windDescriptionLabel Add a static directly below that label with the text Relative Humidity Add a label to the right of that label, link it to humidityLabel Add a static directly below that label with the text Dewpoint Add a label to the right of that label, link it to dewpointLabel Add a UIImageView to the bottom left corner, link it to weatherUndergroundImageView Add a static label in the bottom left corner above the imageView with the text Powered By Add a label to the right of the bottom image view, link it to lastUpdatedLabel

Refer to the image of interface builder above for fonts, colors, and sizes.

The Finished Application

Build & Run, your app should resemble the following.

As always comments, questions, and concerns are much appreciated!

The source code to this recipe is now available online through GitHub!