Developing iOS apps that can take advantage of the built-in GPS unit
is a great way to add a new dimension to the user experience. You can
quickly make your apps location-aware (and able to find the current
latitude/longitude coordinates) with minimal effort. This tutorial will
walk through the steps involved in finding a device’s current position.
In iOS, you can find the current lat/lng by making a request to the
Core Location component to start looking up location information. This
works via a series of delegate messages passed to our application when
Core Location starts updating. By default, the location hardware in our
device is usually turned off (or otherwise in an idle state) so as not
to needlessly drain power. In a nutshell we need to:
#import the correct Core Location header file
- Link against the Core Location framework
- Implement two delegate methods to receive information from Core Location
- Tell Core Location to start sending us updates
- Properly shut down Core Location when we don’t need it anymore.
Getting Started
Since we’re going to be using Core Location with our iOS app, we need
to add the header file (CoreLocation/CoreLocation.h) to our prefix
header (usually with the extension .pch). In this prefix header, locate
the
#ifdef __OBJC__ line:
#ifdef __OBJC__
#import
#import
#import // added for Core Location
#endif
|
If you have a larger project and are only using Core Location in a few places, you could alternatively just add the
#import line to the files where you actually need it. In our simple example, we’ll stick with the prefix file.
Next, we need to add the Core Location framework to our project’s
link phase. In Xcode, click on the project’s name at the top of the
project navigator view. In the Linked Frameworks and Libraries section,
click the “+” icon to add a new framework. Find the Core
Location.framework entry, and add it. When you next link your project,
the framework will be automatically included.
Setting up the Delegate
The begin setting up our class as a delegate, the first step is to
locate the class where we want to receive Core Location messages. Then,
we need to set that class up as a CLLocationManagerDelegate. You can do
this by adding
to the
@interface definition line.
Next, in the implementation file, we need to add two delegate methods:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
|
This method will be called when Core Location has new location data
for us to process. We receive these messages both when we have better
accuracy for a current location, or the device has moved. These messages
can also come in quickly one after the other, or as a trickle.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
If Core Location needs to report a failure of some kind, this method
will be called. We need to properly handle these. Note that this method
is technically optional, but it’s best to get into the habit of handling
these messages.
Next, we also need a way to store where we currently are located. Since there’s no guarantee of
when
we’ll be told of our location, we need to store this information for
the times when we need to reference it. We also need to keep track of
our Core Location manager instance. So, be sure to add the following to
your class’
@interface definition:
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) CLLocation *currentLocation;
|
And, in the implementation file, the corresponding synthesize methods:
@synthesize locationManager, currentLocation;
|
Receiving Messages
Now that the setup is out of the way, it’s on to the fun stuff. First, let’s implement the
didUpdateToLocation method. I’ll paste the entirety here and then we’ll step through it:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
self.currentLocation = newLocation;
if(newLocation.horizontalAccuracy <= 100.0f) { [locationManager stopUpdatingLocation]; }
}
|
First, note that we’re saving the
currentLocation.
Again, since we don’t know when we’re going to be given an update to our
location, we need to keep this around for whenever we might need it in
the future.
Next, we check the included accuracy against a pre-set expectation. Each time we’re given a
newLocation,
we’re given an estimation on how accurate it is. This float is in
meters, and usually starts out as pretty inaccurate (remember that when
we first start requesting location updates, Core Location needs to
determine the device’s location, which can take time).
You’ll notice in the Maps app on your iPhone that a blue circle is
initially drawn around the point marking your location. That blue
circle’s radius is determined by this
horizontalAccuracy
number. If you’re going to be showing a point on a map for the user’s
current location, you may want to consider adding a similar “accuracy
circle”.
The reason why we’re checking the provided accuracy against a pre-set
expectation is that we don’t want to leave Core Location running for
any longer than we absolutely need it to. It drains the battery life of
the device when it has to communicate with GPS satellites or power up
the radio for location triangulation, so we want to turn it back off
again as soon as we have a good-enough position. In the example above, a
position +/- 100m is plenty accurate for us. To stop Core Location from
sending us updates (and thus allowing it to go back into an idle
state), we send our
locationManager the
stopUpdatingLocation message.
Lastly, we need to implement the
didFailWithError method. Here it is:
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
if(error.code == kCLErrorDenied) {
[locationManager stopUpdatingLocation];
} else if(error.code == kCLErrorLocationUnknown) {
// retry
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error retrieving location"
message:[error description]
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
}
|
When handling errors from Core Location, we need to worry about two
special cases. The first is when the user specifically denies our app
access to finding its position. If that occurs, we want to immediately
stop trying to update by sending the
stopUpdatingLocation message.
The second special case is when we’re given a
kCLErrorLocationUnknown
error. This can occur when Core Location is first starting up and isn’t
able to immediately determine its position, or even make a guess as to
where it is. If we get this message, its best to ignore it and to keep
trying to update our location.
In other cases, we may want to alert the user to a problem with
getting a location. According to the Core Location docs, this might
occur when the device is near a particularly strong magnetic field. In
this example, we’re showing the user a problem but still retrying. You
will need to determine for your own app whether you want to stop trying
to get location updates at this point or to keep retrying.
Requesting Updates
After the delegate has been implemented, it’s time to actually setup
Core Location and request location updates. To setup Core Location,
you’ll want to do the following:
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startUpdatingLocation];
|
This sets up a new
CLLocationManager instance and saves a pointer to it in a class variable. We next set our current class up as the delegate to receive
CLLocationManagerDelegate messages. And finally, we tell Core Location to start figuring out our position.
In my example app, I’ve placed all of my Core Location-related code
in a single view controller’s class as I only need this information in
one section of my app. Thus, I placed the above three lines in my
viewDidLoad method, which is called
after
the view has been loaded, but before it’s being shown to the user. This
could occur right when the app opens up (if loaded from the XIB file),
but it could also occur at another point in the app, such as after a
low-memory condition and the view had been previously unloaded.
Since I put this setup code in the
viewDidLoad method, I
also need to remember to tell Core Location to stop updating should my
view get unloaded. Thus, I placed the following line in my
viewDidUnload method:
[locationManager stopUpdatingLocation];
|
It’s important to remember to tell Core Location to stop sending messages to the delegate
before
the delegate could be freed, which could cause Core Location to attempt
to send messages to a freed/nil object (which would cause a crash).
And that’s all there is to it! Implementing Core Location is an easy
thing to implement and can add an interesting new dimension to your iOS
apps. Be sure to post in the comments if you found this interesting or
have questions.