How to Add a Current Location Button to Your iOS App - dummies

How to Add a Current Location Button to Your iOS App

By Neal Goldstein, Dave Wilson

It is easy it is to add a button to the toolbar bar in your iOS app to zoom in to the current location and then back to the map region and span you’re currently displaying.

Add the bolded code to add the button in the MapController method viewDidLoad.

- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
self.mapView.showsUserLocation = YES;
[self setInitialRegion];
RTAppDelegate* appDelegate = [[UIApplication
sharedApplication] delegate];
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
UILabel * titleLabel = [[UILabel alloc]
initWithFrame:CGRectMake (0,0,250,44)];
titleLabel.textColor = [UIColor yellowColor];
titleLabel.font = [UIFont boldSystemFontOfSize:17];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.text = [self mapTitle];
UIBarButtonItem *titleView = [[UIBarButtonItem alloc]
initWithCustomView:titleLabel];
UIBarButtonItem *flexibleSpace=
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:
UIBarButtonSystemItemFlexibleSpace
target:nil action:nil];
flexibleSpace.width = 1.0;
NSMutableArray *itemsArray =
[self.toolbar.items mutableCopy];
[itemsArray insertObject:flexibleSpace atIndex:
[itemsArray count]-2];
[itemsArray insertObject:titleView atIndex:
[itemsArray count]-2];
[self.toolbar setItems:itemsArray animated:NO];
}
else {
self.title = [appDelegate.trip mapTitle];
}
[self addAnnotations];
UIBarButtonItem *locateButton = [[UIBarButtonItem alloc]
initWithTitle:@"Locate"
style:UIBarButtonItemStyleBordered target:self
action:@selector(goToLocation:)];;
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
NSMutableArray *itemsArray =
[self.toolbar.items mutableCopy];
[itemsArray insertObject:locateButton
atIndex:[itemsArray count]];
[self.toolbar setItems:itemsArray animated:NO];
}
else {
self.navigationItem.rightBarButtonItem = locateButton ;
}
}

When the user taps the Locate button you create here, you’ve specified that the goToLocation: message is to be sent [action:@selector(goToLocation:)] to the MapController ( target:self).

UIBarButtonItem *locateButton =  [[UIBarButtonItem alloc] initWithTitle: @"Locate"  style:UIBarButtonItemStylePlain target:self  action:@selector(goToLocation:)];self.navigationItem.rightBarButtonItem = locateButton;

Don’t forget, to go back to a location you need to choose a simulated location if you are using the Simulator.

Next, add the goToLocation: method to MapController.m.

MKUserLocation *annotation = self.mapView.userLocation;
CLLocation *location = annotation.location;
if (nil == location)
return;
CLLocationDistance distance =
MAX(4*location.horizontalAccuracy,500);
MKCoordinateRegion region =
MKCoordinateRegionMakeWithDistance
(location.coordinate, distance, distance);
[self.mapView setRegion:region animated:NO];
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
NSArray *itemsArray = self.toolbar.items;
UIBarButtonItem *locateButton = [itemsArray
objectAtIndex:[itemsArray count]-1];
locateButton.action = @selector(goToDestination:);
locateButton.title = @"Destination";
}
else {
self.navigationItem.rightBarButtonItem.action =
@selector(goToDestination:);
self.navigationItem.rightBarButtonItem.title =
@"Destination";
}
}

When the user taps the Locate button, your app first checks to see whether the location is available. (It may take a few seconds after the application starts for the location to become available.) If not, you simply return from the method without changing the region. (You could, of course, show an alert informing the user what’s happening and to try again in 10 seconds or so)

If the location is available, your app computes the span for the region the user is moving to. In this case, the following code

CLLocationDistance distance =    MAX(4*location.horizontalAccuracy,500);

computes the span to be four times the horizontalAccuracy of the device (but no less than 1,000 meters). horizontalAccuracy is a radius of uncertainty given the accuracy of the device; that is, the user is somewhere within that circle.

You then call the MKCoordinateRegionMakeWithDistance function that creates a new MKCoordinateRegion from the specified coordinate and distance values.

If you didn’t want to change the span, you could’ve simply set the Map view’s centerCoordinate property to userLocation, and that would’ve centered the region at the userLocation coordinate without changing the span.

When the user taps the Location button, you change the title on the button to the Map title and change the @selector to (goToDestination:). You access the button on the iPad in the toolbar itemsArray and on the iPhone via the navigationItem.

This means that the next time the user touches the button, the goToDestination: message will be sent, so you’d better add the code to MapController.m. This sets the region back to the Destination region and toggles the button title back to Locate.

- (void)goToDestination:(id)sender {
[self setInitialRegion];
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
NSArray *itemsArray = self.toolbar.items;
UIBarButtonItem *locateButton = [itemsArray
objectAtIndex:[itemsArray count]-1];
locateButton.action = @selector(goToLocation:);
locateButton.title = @"Locate";
}
else {
self.navigationItem.rightBarButtonItem.action =
@selector(goToLocation:);
self.navigationItem.rightBarButtonItem.title =
@"Locate";
}
}

Now run your app. Because you already have two annotations on the map of New York, you might want to set the Simulator to use a distant location such as San Francisco. That way, you can easily see that the map is working.

image0.jpg

Because you have the user location, you might be tempted to use that to center the map, and that would work fine, as long as you start the location-finding mechanism stuff as soon as the program launches.

The problem is that the hardware may take a while to find the current location, and if you don’t wait long enough, you get an error. You can add the code to center the map to a method that executes later, such as

-(void)observeValueForKeyPath:(NSString *)keyPath  ofObject:(id)object change:(NSDictionary *)change         context:(void *)context {

This message is sent as soon as the map starts getting location information, but you’ll see an initial view and then a redisplay of the centered view. For aesthetic reasons, you really need to initialize MapController and MapView at program startup — an exercise for the reader.