Professional Documents
Culture Documents
IOS SDK
1 3
In the first article of this series, we laid the foundation of the project by setting up
the project and creating the application's structure. In this article, we leverage the
AFNetworking library to interact with the Forecast API.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Introduction
In the first installment of this series, we laid the foundation of our weather
application. Users can add their current location and switch between locations. In
this tutorial, we will use the AFNetworking library to ask the Forecast API for the
weather data of the currently selected location.
If you want to follow along, you will need a Forecast API key. You can obtain an API
key by registering as a developer at Forecast. Registration is free, so I encourage
you to try out the Forecast weather service. You can find your API key at the bottom
of your dashboard (figure 1).
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Figure 1: Obtaining Your API Key
1. Subclassing AFHTTPClient
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
As I wrote earlier in this article, we will be using theAFNetworking library for
communicating with the Forecast API. There are several options when working with
AFNetworking, but to make our application future proof, we will opt for the
AFHTTPClient class. This class is designed for consuming web services, such as
the Forecast API. Even though we will only access one API endpoint, it is still useful
to make use of the AFHTTPClient as you will learn in a few moments.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Figure 2: Subclassing AFHTTPClient
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
class in our project. This means that only one instance of the class is alive at any
one time for the lifetime of the application. Chances are that you are already familiar
with singleton pattern as it is a common pattern in many object-oriented
programming languages. At first glance, the singleton pattern seems very
convenient, but there are a number of caveats to watch out for. You can learn more
about singletons by reading this excellent article by Matt Gallagher.
1 #import "AFHTTPClient.h"
2
3 @interface MTForecastClient : AFHTTPClient
4
5 #pragma mark -
6 #pragma mark Shared Client
7 + (MTForecastClient *)sharedClient;
8
9 @end
The implementation of sharedClient may look daunting at first, but it isn't that
difficult once you understand what's going on. We first declare two static variables,
(1) predicate of type dispatch_once_t and (2) _sharedClient of type
MTForecastClient. As its name implies, predicate is a predicate that we use in
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
combination with the dispatch_once function. When working with a variable of type
dispatch_once_t, it is important that it is declared statically. The second variable,
_sharedClient, will store a reference to the singleton object.
01 + (MTForecastClient *)sharedClient {
02 static dispatch_once_t predicate;
03 static MTForecastClient *_sharedClient = nil;
04
05 dispatch_once(&predicate, ^{
06 _sharedClient = [self alloc];
07 _sharedClient = [_sharedClientinitWithBaseURL:[self baseURL]];
08 });
09
10 return _sharedClient;
11 }
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
The important thing to understand about the implementation of the sharedClient
class method is that the initializer, initWithBaseURL:, is invoked only once. The
singleton object is stored in the _sharedClient static variable, which is returned by
the sharedClient class method.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 - (id)initWithBaseURL:(NSURL *)url {
02 self = [super initWithBaseURL:url];
03
04 if (self) {
05 // Accept HTTP Header
06 [self setDefaultHeader:@"Accept" value:@"application/json"
];
07
08 // Register HTTP Operation Class
09 [self registerHTTPOperationClass
:[AFJSONRequestOperationclass
10 }
11
12 return self;
13 }
1 + (NSURL *)baseURL {
2 return [NSURL URLWithString:[NSString stringWithFormat:@"https://api.forecast.io/f
3 }
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
You may have noticed in the implementation of baseURL that I have used another
string constant for storing the API key. This might seem unnecessary since we only
use the API key in one location. However, it is good practice to store application
data in one location or in a property list.
1 #pragma mark -
2 #pragma mark Forecast API
3 extern NSString * const MTForecastAPIKey;
1 #pragma mark -
2 #pragma mark Forecast API
3 NSString * const MTForecastAPIKey = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
The block takes two arguments, (1) a boolean indicating whether the query was
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
successful and (2) a dictionary with the response from the query. The convenience
method, requestWeatherForCoordinate:completion:
, takes the coordinates of a
location ( CLLocationCoordinate2D) and a completion block. By using a completion
block, we can avoid creating a custom delegate protocol or fall back to using
notifications. Blocks are a perfect fit for this type of scenario.
01 #import "AFHTTPClient.h"
02
03 typedef void (^MTForecastClientCompletionBlock)(
BOOL success, NSDictionary
04
05 @interface MTForecastClient : AFHTTPClient
06
07 #pragma mark -
08 #pragma mark Shared Client
09 + (MTForecastClient *)sharedClient;
10
11 #pragma mark -
12 #pragma mark Instance Methods
13 - (void)requestWeatherForCoordinate:(CLLocationCoordinate
2D)coordinate
14
15 @end
In requestWeatherForCoordinate:completion:
, we invoke
getPath:success:failure:, a method declared in AFHTTPClient. The first
argument is the path that is appended to the base URL that we created
earlier. The second and third arguments are blocks that are executed when
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
the request succeeds and fails, respectively. The success and failure
blocks are pretty simple. If a completion block was passed to
requestWeatherForCoordinate:completion:
, we execute the block and pass a
boolean value and the response dictionary (ornil in the failure block).
In the failure block, we log the error from the failure block to the
console to facilitate debugging.
01 - (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate
02 NSString *path = [NSString stringWithFormat:@"%f,%f", coordinate.latitude
03 [self getPath:path parameters:nil success:^(AFHTTPRequestOperation
04 if (completion) {
05 completion(YES, response);
06 }
07
08 } failure:^(AFHTTPRequestOperation*operation, NSError *error) {
09 if (completion) {
10 completion(NO, nil);
11
12 NSLog(@"Unable to fetch weather data due to error %@ with user info %@
13 }
14 }];
15 }
You may be wondering what the response object in the success blocks is or
references. Even though the Forecast API returns a JSON response, the response
object in the success block is an NSDictionary instance. The benefit of working
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
with the AFJSONHTTPRequestOperationclass, which we registered in
initWithBaseURL:, is that it accepts the JSON response and automatically creates
an object from the response data, a dictionary in this example.
Armed with the MTForecastClient class, it is time to query the Forecast API and
fetch the weather data for the currently selected location. The most suitable place to
do this is in the setLocation: method of the MTWeatherViewControllerclass.
Amend the setLocation: method as shown below. As you can see, all we do is
invoke fetchWeatherData, another helper method.
01 - (void)setLocation:(NSDictionary *)location {
02 if (_location != location) {
03 _location = location;
04
05 // Update User Defaults
06 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults
];
07 [ud setObject:location forKey:MTRainUserDefaultsLocation];
08 [ud synchronize];
09
10 // Post Notification
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
10 // Post Notification
11 NSNotification *notification1 = [NSNotification notificationWithName
12 [[NSNotificationCenterdefaultCenter] postNotification:notification
13
14 // Update View
15 [self updateView];
16
17 // Request Location
18 [self fetchWeatherData];
19 }
20 }
Have you ever wondered why I use so many helper methods in my code? The reason is
simple. By wrapping functionality in helper methods, it is very easy to reuse code in
various places of a project. The main benefit, however, is that it helps battle code
duplication. Code duplication is something you should always try to avoid as much as
possible. Another advantage of using helper methods is that it makes your code much
more readable. By creating methods that do one thing and providing a well chosen method
name, it is easier to quickly read and process your code.
01 - (void)fetchWeatherData {
02 // Show Progress HUD
03 [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
04
05 // Query Forecast API
06 double lat = [[_location objectForKey:MTLocationKeyLatitude]doubleValue
07 double lng = [[_location objectForKey:MTLocationKeyLongitude]doubleValue
08 [[MTForecastClient sharedClient] requestWeatherForCoordinate
:CLLocationCoordinate
09 // Dismiss Progress HUD
10 [SVProgressHUD dismiss];
11
12 NSLog(@"Response > %@", response);
13 }];
14 }
Before you build and run your application, import the header file of the
MTForecastClient class in MTWeatherViewController.m.
01 #import "MTWeatherViewController.h"
02
03 #import "MTForecastClient.h"
04
05 @interface MTWeatherViewController() <CLLocationManagerDelegate> {
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
05 @interface MTWeatherViewController() <CLLocationManagerDelegate> {
06 BOOL _locationFound;
07 }
08
09 @property (strong, nonatomic) NSDictionary *location;
10
11 @property (strong, nonatomic) CLLocationManager *locationManager;
12
13 @end
What happens when the device is not connected to the web? Have you thought
about that scenario? In terms of user experience, it is good practice to notify the
user when the application is unable to request data from the Forecast API. Let me
show how to do this with the AFNetworking library.
3. Reachability
There are a number of libraries that provide this functionality, but we will stick with
AFNetworking. Apple also provides sample code, but it is a bit outdated and doesn't
support ARC.
AFNetworking has truly embraced blocks, which is definitely one of the reasons that
this library has become so popular. Monitoring for reachability changes is as simple
as passing a block to setReachabilityStatusChangeBlock:
, another method of the
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
AFHTTPClient class. The block is executed every time the reachability status
changes and it accepts a single argument of type AFNetworkReachabilityStatus.
Take a look at the updated initWithBaseURL: method of the MTForecastClient
class.
01 - (id)initWithBaseURL:(NSURL *)url {
02 self = [super initWithBaseURL:url];
03
04 if (self) {
05 // Accept HTTP Header
06 [self setDefaultHeader:@"Accept" value:@"application/json"
];
07
08 // Register HTTP Operation Class
09 [self registerHTTPOperationClass
:[AFJSONRequestOperationclass
10
11 // Reachability
12 __weak typeof(self)weakSelf = self;
13 [self setReachabilityStatusChangeBlock
:^(AFNetworkReachabilityStatus
14 [[NSNotificationCenterdefaultCenter] postNotificationName
15 }];
16 }
17
18 return self;
19 }
To avoid a retain cycle, we pass a weak reference to the singleton object in the
block that we pass to setReachabilityStatusChangeBlock:
. Even if you use ARC
in your projects, you still need to be aware of subtle memory issues like this. The
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
name of the notification that we post is another string constant declared in
MTConstants.h/.m.
The reason for posting a notification in the reachability status change block is to
make it easier for other parts of the application to update when the device's
reachability changes. To make sure that the MTWeatherViewControllerclass is
notified of reachability changes, instances of the class are added as an observer for
the notifications sent by the Forecast client as shown below.
This also means that we need to remove the instance as an observer in the
dealloc method. This is a detail that is often overlooked.
1 - (void)dealloc {
2 // Remove Observer
3 [[NSNotificationCenterdefaultCenter] removeObserver:self];
4 }
1 - (void)reachabilityStatusDidChange:(
NSNotification *)notification {
2 MTForecastClient *forecastClient = [notificationobject];
3 NSLog(@"Reachability Status > %i"
, forecastClient.networkReachabilityStatus
4 }
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
4. Refreshing Data
Before we wrap this post up, I want to add two additional features, (1) fetching
weather data whenever the application becomes active and (2) adding the ability to
manually refresh weather data. We could implement a timer that fetches fresh data
every hour or so, but this is not necessary for a weather application in my opinion.
Most users will launch the application, take a look at the weather and put the
application in the background. It is therefore only necessary to fetch fresh data
when the user launches the application. This means that we need to listen for
UIApplicationDidBecomeActiveNotificationnotifications in the
MTWeatherViewControllerclass. As we did for monitoring reachability changes, we
add instances of the class as observers of notifications of type
.
UIApplicationDidBecomeActiveNotification
1 - (void)applicationDidBecomeActive:(
NSNotification *)notification {
2 if (self.location) {
3 [self fetchWeatherData];
4 }
5 }
I have also amended fetchWeatherData to only query the Forecast API if the
device is connected to the web.
01 - (void)fetchWeatherData {
02 if ([[MTForecastClient sharedClient] networkReachabilityStatus
] == AFNetworkReach
03
04 // Show Progress HUD
05 [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
06
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
06
07 // Query Forecast API
08 double lat = [[_location objectForKey:MTLocationKeyLatitude]doubleValue
09 double lng = [[_location objectForKey:MTLocationKeyLongitude]doubleValue
10 [[MTForecastClient sharedClient] requestWeatherForCoordinate
:CLLocationCoordinate
11 // Dismiss Progress HUD
12 [SVProgressHUD dismiss];
13
14 // NSLog(@"Response > %@", response);
15 }];
16 }
Let's add a button to the weather view controller that the user can tap to manually
refresh the weather data. Create an outlet in MTWeatherViewController.h and
create a refresh: action in MTWeatherViewController.m.
01 #import <UIKit/UIKit.h>
02
03 #import "MTLocationsViewController.h"
04
05 @interface MTWeatherViewController: UIViewController <MTLocationsViewControllerDeleg
06
07 @property (weak, nonatomic) IBOutlet UILabel *labelLocation;
08 @property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
09
10 @end
1 - (IBAction)refresh:(id)sender {
2 if (self.location) {
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
2 if (self.location) {
3 [self fetchWeatherData];
4 }
5 }
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Figure 3: Adding a Refresh Button
1 - (void)reachabilityStatusDidChange:(
NSNotification *)notification {
2 MTForecastClient *forecastClient = [notificationobject];
3 NSLog(@"Reachability Status > %i"
, forecastClient.networkReachabilityStatus
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
NSLog(@"Reachability Status > %i"
, forecastClient.networkReachabilityStatus
4
5 // Update Refresh Button
6 self.buttonRefresh.enabled = (forecastClient.networkReachabilityStatus
7 }
It isn't necessary to temporarily disable the refresh button when a request is being
processed in fetchWeatherData because the progress HUD adds a layer on top of
the view controller's view that prevents the user from tapping the button more than
once. Build and run the application to test everything out.
Advertisement
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Bonus: Removing Locations
A reader asked me how to delete locations from the list so I am including it here for
the sake of completeness. The first thing that we need to do is tell the table view
which rows are editable by implementing tableView:canEditRowAtIndexPath:of
the UITableViewDataSource protocol. This method returns YES if the row at
indexPath is editable and NO if it is not. The implementation is simple as you can
see below. Every row is editable except for the first row and the currently selected
location.
To check whether location is the current location, we use another helper method,
isCurrentLocation:, in which we fetch the current location and compare the
locations coordinates. It would have been better (and easier) if we had assigned a
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
unique identifier to each location stored in the user defaults database. Not only
would it make it easier to compare locations, but it would also allow us to store the
current location's unique identifier in the user defaults database and look it up in the
array of locations. The problem with the current implementation is that locations with
the exact same coordinates cannot be distinguished from one another.
01 - (BOOL)isCurrentLocation:(
NSDictionary *)location {
02 // Fetch Current Location
03 NSDictionary *currentLocation = [[
NSUserDefaults standardUserDefaults
04
05 if ([location[MTLocationKeyLatitude]doubleValue] == [currentLocation[MTLocation
06 [location[MTLocationKeyLongitude]doubleValue] == [currentLocation[MTLocatio
07 return YES;
08 }
09
10 return NO;
11 }
When the user taps the delete button of a table view row, the table view data source
is sent a tableView:commitEditingStyle:forRowAtIndexPath:
message. In this
method, we need to (1) update the data source, (2) save the changes to the user
defaults database, and (3) update the table view. If editingStyle is equal to
, we remove the location from the locations
UITableViewCellEditingStyleDelete
array and store the updated array in the user defaults database. We also delete the
row from the table view to reflect the change in the data source.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
01 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditing
02 if (editingStyle == UITableViewCellEditingStyleDelete) {
03 // Update Locations
04 [self.locations removeObjectAtIndex:(indexPath.row - 1)];
05
06 // Update User Defaults
07 [[NSUserDefaults standardUserDefaults
] setObject:self.locations
08
09 // Update Table View
10 [tableView deleteRowsAtIndexPaths
:@[indexPath] withRowAnimation
11 }
12 }
To toggle the table view's editing style, we need to add an edit button to the user
interface. Create an outlet for the button in MTLocationsViewController.h and an
action named editLocations: in MTLocationsViewController.m. In
editLocations:, we toggle the table view's editing style.
01 #import <UIKit/UIKit.h>
02
03 @protocol MTLocationsViewControllerDelegate
;
04
05 @interface MTLocationsViewController: UIViewController <UITableViewDataSource
06
07 @property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
08
09 @property (weak, nonatomic) IBOutlet UITableView *tableView;
10 @property (weak, nonatomic) IBOutlet UIBarButtonItem *editButton;
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
@property (weak, nonatomic) IBOutlet UIBarButtonItem *editButton;
11
12 @end
13
14 @protocol MTLocationsViewControllerDelegate<NSObject>
15 - (void)controllerShouldAddCurrentLocation:(
MTLocationsViewController
16 - (void)controller:(MTLocationsViewController*)controller didSelectLocation
17 @end
1 - (IBAction)editLocations:(id)sender {
2 [self.tableView setEditing:![self.tableView isEditing] animated:YES
3 }
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Figure 4: Adding an Edit Button
You may be wondering why we created an outlet for the edit button. The reason is
that we need to be able to change the title of the edit button from Edit to Done, and
vice versa, whenever the editing style of the table view changes. In addition, when
the user deletes the last location (except for the current location) in the table view, it
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
would be nice to automatically toggle the table view's editing style. These features
are not hard to implement which is why I leave them up to you as an exercise. If
you run into problems or have questions, feel free to leave a comment in the
comments below this article.
Conclusion
We have successfully integrated the Forecast API in our weather application. In the
next tutorial, we will implement focus on the user interface and the design of the
application.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Advertisement
Translations Available:
Jobs
Advertisement
PHP Coder with Magento Knowledge
at Yoginet Web Solutions in New Delhi,
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Delhi, India
13 Comments Mobiletuts+
Sort by Best
just a question: how can I force a refresh of the current location at the application startup, in order to have
location always updated?
thanks
Reply Share
// Extract Data
NSString *city = [placemark locality];
NSString *country = [placemark country];
CLLocationDegrees lat = placemark.location.coordinate.latitude;
CLLocationDegrees lon = placemark.location.coordinate.longitude;
I'm a chinese, why the city is always nil, could you help me, thanks!
Reply Share
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
kevin chen > kevin chen 2 years ago
I'm in china, I have find a solution and I test in chinese and english of International setting. My devic
version is 6.1.4.
thanks!
1 Reply Share
thankss
1 Reply Share
open in browser PRO version Are you a developer? Try out the Bart Jacobs
HTML to PDF API
Mod > Sivikk a year ago pdfcrowd.com
Bart Jacobs Mod > Sivikk a year ago
The import statement imports the wrong header file. It should be #import
"AFNetworking.h".
Reply Share
see more
Reply Share
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Subscribe d Add Disqus to your site Privacy
Advertisement
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Teaching skills to millions worldwide.
FAQ
Subscribe
Terms of Use
Contact Support Privacy Policy
About Tuts+
Advertise
Teach at Tuts+
Build anything from social networks to file upload systems. Build faster
with pre-coded PHP scripts.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com
Browse PHP on CodeCanyon
2014 Envato Pty Ltd. Trademarks and brands are the property of their respective
owners.
open in browser PRO version Are you a developer? Try out the HTML to PDF API pdfcrowd.com