IT tutorials
 
Mobile
 

iphone Programming : Using Sensors - The Core Location Framework

12/29/2012 11:46:17 AM
- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire

The Core Location framework is an abstraction layer in front of several different methods to find the user’s location (and, by extrapolation, her speed and course). It can provide the latitude, longitude, and altitude of the device (along with the level of accuracy to which this is known). There are three levels of accuracy:

  • The least accurate level uses the cell network to locate the user (the process is similar to triangulation, but more complex). This can quickly provide a position to around 12 km accuracy, which can be reduced to 1–3 km after some time depending on the tower density at your current location.

  • The next accuracy level is obtained by utilizing Skyhook Wireless’s WiFi-based positioning system. This is much more precise, giving a position to approximately 100 m. However, it depends on the user being in range of a known wireless hotspot.

  • The highest level of accuracy is obtained by using GPS hardware, which should provide a position to less than 40 m.


Warning:

On the iPod touch, the user’s location is derived solely from WiFi positioning. The original iPhone will use WiFi and cell tower triangulation, and on the iPhone 3G and 3GS it will also make use of the built-in GPS hardware.


The actual method used to determine the user’s location is abstracted away from both the user and the developer. The only control the developer has over the chosen method is by requesting a certain level of accuracy, although the actual accuracy achieved is not guaranteed. Further, the battery power consumed and the time to calculate the position increase with increasing accuracy.


Warning:

Some users may choose to explicitly disable reporting of their position. You should therefore always check to see whether location services are enabled before attempting to turn on these services. This will avoid unnecessary prompting from your application.


The Core Location framework is implemented using the CLLocationManager class. The following code will create an instance of this class, and from then on will send location update messages to the designated delegate class:

CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
if( locationManager.locationServicesEnabled ) {
    [locationManager startUpdatingLocation];
} else {
    NSLog(@"Location services not enabled.");
}


Note:

To use this code, you will need to add the Core Location framework. In Groups & Files, right-click or Ctrl-click on Frameworks and select AddExisting Frameworks. Add CoreLocation. You will also need to declare your class as implementing the CLLocationManagerDelegate protocol and import CoreLocation in your declaration or implementation with the following code:

#import <CoreLocation/CoreLocation.h>


We can filter these location update messages based on a distance filter. Changes in position of less than this amount will not generate an update message to the delegate:

locationManager.distanceFilter = 1000;  // 1km

We can also set a desired level of accuracy; this will determine the location method(s) used by the Core Location framework to determine the user’s location:

locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;

The CLLocationManagerDelegate protocol offers two methods. The first is called when a location update occurs:

- (void)locationManager:(CLLocationManager *)manager
  didUpdateToLocation:(CLLocation *)newLocation
  fromLocation:(CLLocation *)oldLocation
{
   NSLog(@"Moved from %@ to %@", oldLocation, newLocation);
}

The second is called when an error occurs:

- (void)locationManager:(CLLocationManager *)manager
  didFailWithError:(NSError *)error {
    NSLog(@"Received Core Location error %@", error);
    [manager stopUpdatingLocation];
}

If the location manager is not able to ascertain the user’s location immediately, it reports a kCLErrorLocationUnknown error and keeps trying. In most cases, you can choose to ignore the error and wait for a new event. However, if the user denies your application access to the location service, the manager will report a kCLErrorDenied error. Upon receiving such an error, you should stop the location manager.

1. Location-Dependent Weather

We can use the Core Location framework to retrieve the user’s latitude and longitude. However, the Google Weather Service, which we used to back our Weather application, takes only city names, not latitude or longitude arguments.

There are several ways around this problem. For instance, the MapKit framework, offers reverse geocoding capabilities (which turn coordinates into postal addresses). However, for this example, I’m going to make use of one of the many web services offered by the GeoNames.org site to carry our reverse geocoding to retrieve the nearest city from the latitude and longitude returned by the Core Location framework.

1.1. Using the GeoNames reverse geocoding service

One of the RESTful web services offered by GeoNames.org will return an XML or JSON document listing the nearest populated place using reverse geocoding. Requests to the service take the form http://ws.geonames.org/findNearbyPlaceName?lat=<XX.X>&lng=<XX.X> if you want an XML document returned, or http://ws.geonames.org/findNearbyPlaceNameJSON?lat=<XX.X>&lng=<XX.X> if you prefer a JSON document. There are several optional parameters: radius (in km), max (maximum number of rows returned), and style (SHORT, MEDIUM, LONG, and FULL).

Passing the longitude and latitude of Cupertino, California, which is the location returned by Core Location in all cases for iPhone Simulator, the JSON service would return the following JSON document:

{
   "geonames":[
      {
         "countryName":"United States",
         "adminCode1":"CA",
         "fclName":"city, village,...",
         "countryCode":"US",
         "lng":-122.0321823,
         "fcodeName":"populated place",
         "distance":"0.9749",
         "fcl":"P",
         "name":"Cupertino",
         "fcode":"PPL",
         "geonameId":5341145,
         "lat":37.3229978,
         "population":50934,
         "adminName1":"California"
      }
   ]
}

1.2. Modifying the Weather application

Let’s modify our Weather application to make use of Core Location and (optionally) give us the weather where we are, rather than just for a hardwired single location. Open the Weather project in Xcode and click on the WeatherAppDelegate.h interface file to open it in the Xcode editor.

We’re going to use the application delegate to manage the CLLocationManager. I’ve highlighted the changes you need to make to this file in bold:

#import <CoreLocation/CoreLocation.h>

@class MainViewController;

@interface WeatherAppDelegate : NSObject
  <UIApplicationDelegate, CLLocationManagerDelegate>
{
    UIWindow *window;
    MainViewController *mainViewController;

    BOOL updateLocation;
						CLLocationManager *locationManager;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) MainViewController *mainViewController;
@property (nonatomic) BOOL updateLocation;
						@property (nonatomic, retain) CLLocationManager *locationManager;

@end

You will also need to add the Core Location framework to the project. In Groups & Files, right-click or Ctrl-click on Frameworks and select AddExisting Frameworks. Select CoreLocation and click Add.

In the corresponding implementation file (WeatherAppDelegate.m), we first need to synthesize the new variables we declared in the interface file:

@synthesize updateLocation;
@synthesize locationManager;

After that, add the code shown in bold to the applicationDidFinishLaunching: method. This creates an instance of the CLLocationManager class and sets the delegate for the class to be the current class (the application delegate).

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    // Create instance of Main View controller
    MainViewController *aController =
      [[MainViewController alloc]
      initWithNibName:@"MainView" bundle:nil];
    self.mainViewController = aController;
    [aController release];

    // Create instance of LocationManager object
						self.locationManager =
						[[[CLLocationManager alloc] init] autorelease];
						self.locationManager.delegate = self;

    // Create instance of WeatherForecast object
    WeatherForecast *forecast = [[WeatherForecast alloc] init];
    self.mainViewController.forecast = forecast;
    [forecast release];

    // Set the main view
    mainViewController.view.frame = [UIScreen mainScreen].applicationFrame;
    [window addSubview:[mainViewController view]];
    [window makeKeyAndVisible];
}

Finally, we have to make sure the CLLocationManager instance is released in the dealloc: method, and implement the two CLLocationManagerDelegate methods we’re going to need. Make the changes shown in bold:

- (void)dealloc {
    [locationManager release];
    [mainViewController release];
    [window release];
    [super dealloc];
}

#pragma mark CLLocationManager Methods
						- (void)locationManager:(CLLocationManager *)manager
						didUpdateToLocation:(CLLocation *)newLocation
						fromLocation:(CLLocation *)oldLocation {
						NSLog(@"Location: %@", [newLocation description]);
						if ( newLocation != oldLocation ) {
						// Add code here
						}
						}
						- (void)locationManager:(CLLocationManager *)manager
						didFailWithError:(NSError *)error {
						NSLog(@"Error: %@", [error description]);
						}

We’re going to modify the (currently unused) flip side of the Weather application and add a switch (UISwitch). This will toggle whether our application should be updating its location. However, let’s modify the FlipSideViewController interface file before we go to the NIB file, adding both a switch and a switchThrown: interface builder action that we’ll connect to the switch. I’ve also added a reference to the application delegate. Make the changes shown in bold to FlipSideViewController.h:

@protocol FlipsideViewControllerDelegate;

@class WeatherAppDelegate;

@interface FlipsideViewController : UIViewController {
    id <FlipsideViewControllerDelegate> delegate;
    IBOutlet UISwitch *toggleSwitch;
						WeatherAppDelegate *appDelegate;
}

@property (nonatomic, assign) id <FlipsideViewControllerDelegate> delegate;

- (IBAction)done;
- (IBAction)switchThrown;

@end

In the corresponding implementation (FlipSideViewController.m), import both the Core Location framework and the application delegate interface file:

#import <CoreLocation/CoreLocation.h>
#import "WeatherAppDelegate.h";

Then in the viewDidLoad: method, we need to populate the reference to the application delegate and use the value of the updateLocation Boolean declared earlier to set the state of the UISwitch. Add the lines shown in bold:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];

    appDelegate = (WeatherAppDelegate *)
						[[UIApplication sharedApplication] delegate];
						toggleSwitch.on = appDelegate.updateLocation;

}

In the done: method, which is called when the user clicks on the Done button to close the flipside view, we must set the same updateLocation Boolean variable in the application delegate to be that of the state of the switch. If the user has changed the switch state on the flip side, it will now be reflected in the application delegate. Add the line shown in bold:

- (IBAction)done {
    appDelegate.updateLocation = toggleSwitch.on;
    [self.delegate flipsideViewControllerDidFinish:self];
}

Next, provide an implementation of the switchThrown: method that you’ll attach to the UISwitch in Interface Builder:

-(IBAction)switchThrown {
    NSLog(@"Switch thrown");
    if ( toggleSwitch.on ) {
        [appDelegate.locationManager startUpdatingLocation];
    } else {
        [appDelegate.locationManager stopUpdatingLocation];
    }
}

Finally, remember to release the toggleSwitch inside the dealloc: method:

- (void)dealloc {
    [toggleSwitch release];
    [super dealloc];
}

Now let’s add that switch to the flipside view. Make sure you’ve saved all your changes and then double-click on the FlipsideView.xib file to open it in Interface Builder. Drag and drop a label (UILabel) and a switch (UISwitch) element from the Library window into the Flipside View window. Position them and adjust the attributes (⌘-1) of the label so that your layout looks like Figure 1.

Figure 1. Adding the UISwitch to the FlipsideView controller


Click File’s Owner, open the Connections Inspector (⌘-2), and connect the toggleS⁠witch outlet to the UISwitch. Then connect the switchThrown: action to the UISwitch’s Value Changed event. While you’re here, double-click on the navigation bar title and change the text to “Preferences”. Save your changes; we’re done here.

We’ve reached a natural point to take a break and test the application. Save FlipsideView.xib and return to Xcode. Then click the Build and Run button in the Xcode toolbar to compile and deploy the Weather application into the simulator. Once it’s running, click the Info button to go to the flip side of the application and toggle the switch. If you look at the Debugger Console (RunConsole in the Xcode menu bar), you should (after a small amount of time) see something that looks a lot like Figure 2.

Figure 2. The Weather application reporting the current location (of iPhone Simulator) when the flipside switch is thrown


iPhone Simulator will always report its location as being at Lat. +37.33168900, Long. –122.03073100, corresponding to 1 Infinite Loop, Cupertino, CA.

Quit the simulator. Back in Xcode, click on the MainViewController.h interface file to open it in the editor. Since we’re now going to have multiple locations, we need somewhere to store the name of the location that we’ll get back from the reverse geocoder. So, add an NSString to MainViewController.h (somewhere inside the opening and closing curly braces after the @interface directive) to store the location:

NSString *location;

Then expose this and the UIActivityIndicator (we’re going to use that shortly) as properties. Add the following just before the @end directive:

@property(nonatomic, retain) UIActivityIndicatorView *loadingActivityIndicator;
@property(nonatomic, retain) NSString *location;

					  

Since we’ve declared location and loadingActivityIndicator as properties, go back to the implementation file (MainViewController.m) and add these lines to synthesize those properties:

@synthesize loadingActivityIndicator;
@synthesize location;

Then in the viewDidLoad: method, initialize the location string:

- (void)viewDidLoad {
    [super viewDidLoad];
    location = [[NSString alloc] init];
    [self refreshView:self];
}

Make sure it is released in the dealloc: method:

- (void)dealloc {
    [location release];
						... rest of the method not shown ...
}

Next, in the refreshView: method, check whether the app is monitoring the device’s location so that you know whether to query the Google Weather Service with the default location (London, UK) or with the current location:

- (IBAction)refreshView:(id)sender {
    [loadingActivityIndicator startAnimating];

    WeatherAppDelegate *appDelegate =
						(WeatherAppDelegate *)[[UIApplication sharedApplication] delegate];
						if( appDelegate.updateLocation ) {
						NSLog( @"updating for location = %@", self.location );
						[forecast queryService:self.location withParent:self];
						} else {
        [forecast queryService:@"London,UK" withParent:self];
    }

}

Since we’ve made use of the application delegate, we need to make sure we import it into the MainViewController implementation. Add this line to the top of the file:

#import "WeatherAppDelegate.h"

Now we’re done with the view controller.

What’s left to do? First, we need to build a class to query the GeoNames reverse geocoder service, and then we need to pass the latitude and longitude to the reverse geocoder service from the CLLocationManager delegate method locationManager:didUpdateToLocation:fromLocation: in the application delegate.

Right-click on the Other Sources group in the Groups & Files pane of the Xcode interface and select AddNew Files. In the New File pop up, make sure Cocoa Touch Class (under iPhone OS) is selected. Next, choose “Objective-C class”, a subclass of NSObject, and click the Next button. Name the new class “FindNearbyPlace” when prompted and click Finish.

Click on the FindNearbyPlace.h interface file and modify the template so that it looks like the following code:

#import <Foundation/Foundation.h>

@class WeatherAppDelegate;

@interface FindNearbyPlace : NSObject {
    WeatherAppDelegate *appDelegate;
    NSMutableData *responseData;
    NSURL *theURL;
}

- (void)queryServiceWithLat:(NSString *)latitude
  andLong:(NSString *)longitude;

@end

Modify the FindNearbyPlace.m implementation file so that it looks like the following code. Apart from the connectionDidFi⁠⁠nishLoading: method, it’s almost identical to the Trends API code we wrote for the Twitter Trends application:

#import "WeatherAppDelegate.h"
#import "MainViewController.h"
#import "FindNearbyPlace.h"
#import "JSON/JSON.h"

@implementation FindNearbyPlace

- (void)queryServiceWithLat:(NSString *)latitude
  andLong:(NSString *)longitude
{

    appDelegate = (WeatherAppDelegate *)
      [[UIApplication sharedApplication] delegate];
    responseData = [[NSMutableData data] retain];

    NSString *url = [NSString stringWithFormat:
      @"http://ws.geonames.org/findNearbyPlaceNameJSON?lat=%@&lng=%@",
      latitude, longitude];
    theURL = [[NSURL URLWithString:url] retain];
    NSURLRequest *request = [NSURLRequest requestWithURL:theURL];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];

}

- (NSURLRequest *)connection:(NSURLConnection *)connection
  willSendRequest:(NSURLRequest *)request
  redirectResponse:(NSURLResponse *)redirectResponse
{
    [theURL autorelease];
    theURL = [[request URL] retain];
    return request;
}

- (void)connection:(NSURLConnection *)connection
  didReceiveResponse:(NSURLResponse *)response
{
    [responseData setLength:0];
}

- (void)connection:(NSURLConnection *)connection
  didReceiveData:(NSData *)data
{
    [responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
    // Handle Error
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
						NSString *content =
						[[NSString alloc] initWithBytes:[responseData bytes]
						length:[responseData length]
						encoding:NSUTF8StringEncoding];
						NSLog(@"Content = %@", content);
						SBJSON *parser = [[SBJSON alloc] init];
						NSDictionary *json = [parser objectWithString:content];
						NSArray *geonames = [json objectForKey:@"geonames"];
						NSString *city = [[NSString alloc] init];
						NSString *state = [[NSString alloc] init];
						NSString *country = [[NSString alloc] init];
						for (NSDictionary *name in geonames) {
						city = [name objectForKey:@"name"];
						state = [name objectForKey:@"adminCode1"];
						country = [name objectForKey:@"countryName"];
						}
						[parser release];
						NSLog( @"Location = %@, %@, %@", city, state, country );
						NSString *string = [NSString stringWithFormat:@"%@,%@", city, state];
						appDelegate.mainViewController.location = string;
						[appDelegate.mainViewController.loadingActivityIndicator
						stopAnimating];
						[appDelegate.mainViewController refreshView: self];
						}

-(void)dealloc {
    [appDelegate release];
    [responseData release];
    [theURL release];
    [super dealloc];
}

@end

					  

Now we have the class to query and parse the reverse geocoder service; we just need to write the code in the locationManager:didUpdateToLocation:fromLocation: delegate method.

Click on the application delegate implementation file (WeatherAppDelegate.m) to open it in the Xcode editor and import the geocoder class by adding this line at the top:

#import "FindNearbyPlace.h"

Next, in the didUpdateToLocation: method, add the code shown in bold:

- (void)locationManager:(CLLocationManager *)manager
  didUpdateToLocation:(CLLocation *)newLocation
  fromLocation:(CLLocation *)oldLocation
{
    NSLog(@"Location: %@", [newLocation description]);

    if ( newLocation != oldLocation ) {

        [self.mainViewController.loadingActivityIndicator
						startAnimating];
						FindNearbyPlace *find = [[FindNearbyPlace alloc] init];
						NSString *latitude = [NSString stringWithFormat:@"%f",
						newLocation.coordinate.latitude];
						NSString *longitude = [NSString stringWithFormat:@"%f",
						newLocation.coordinate.longitude];
						[find queryServiceWithLat:latitude andLong:longitude];
    }
}

Here we simply retrieve the latitude and longitude from the CLLocation object, and we pass them to our FindNearbyPlace class to resolve. There the connectionDidFinishLoading: method takes care of updating the main view controller.

We’re done. Save your changes and click Build and Run to compile and deploy the application in iPhone Simulator. Once it’s running, click the Info button to go to the flip side of the application and toggle the switch. Click the Done button and return to the main view. After a little while the activity indicator in the top-righthand corner should start spinning and the weather information should change from being for London to being for Cupertino, California.

1.3. Tidying up

Don’t be fooled. The application has many dangling loose ends to clean up before it can be considered “ready for release.” For instance, in the FindNearbyPlace class we concatenate the city and state to create the location we pass to the Google Weather Service:

city = [name objectForKey:@"name"];
state = [name objectForKey:@"adminCode1"];
NSString *string = [NSString stringWithFormat:@"%@,%@", city, state];

appDelegate.mainViewController.location = string;

While this works for U.S. locations (Cupertino, CA), it fails for British locations where you end up with a string of the form London,ENG, which the Weather service can’t understand.

However, as it stands, it’s a nice starting point for integrating multiple web services into a single application.
 
Others
 
- iPhone SDK 3 : SDK and IDE Basics
- Bluetooth on the iPad : Pairing with a Bluetooth Device
- Personalize & Secure Your iPad : How to Secure Your iPad with a Passcode
- Windows Phone 8 : XAML Overview - What Is XAML?
- Windows Phone 8 : Writing Your First Phone Application - Working with the Phone
- Setting Up Your Android Development Environment : Hello, Android
- Setting Up Your Android Development Environment : Setting Up Your Development Environment
- Symbian OS : Error-Handling Strategies - Escalate Errors
- Symbian OS : Error-Handling Strategies - Fail Fast
- BlackBerry Tablet Applications : Exploring the APIs - GPS
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
programming4us programming4us
 
Popular tags
 
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS