天天看點

iOS開發之-Core LocationGetting the User’s LocationGeocoding Location Data

Getting the User’s Location

Getting the User’s Current Location

The framework uses information obtained from the built-in cellular, Wi-Fi, or GPS hardware to triangulate a location fix for the device.

There are two different services you can use to get the user’s current location:

  • The standard location service is a configurable, general-purpose solution and is supported in all versions of iOS.
  • The significant-change location service offers a low-power location service for devices with cellular radios. This service is available only in iOS 4.0 and later and can also wake up an application that is suspended or not running.

Gathering location data is a power-intensive operation

Determining Whether Location Services Are Available

situations where location services may not be available:

  • The user can disable location services in the Settings application.
  • The user can deny location services for a specific application.
  • The device might be in Airplane mode and unable to power up the necessary hardware.

For these reasons, it is recommended that you always call the locationServicesEnabled class method of CLLocationManager before attempting to start either the standard or significant-change location services. If this class method returns YES, you can start location services as planned. If it returns NO and you attempt to start location services anyway, the system prompts the user to confirm whether location services should be reenabled. 

Starting the Standard Location Service

Before using this service, you configure it by specifying the desired accuracy of the location data and the distance that must be traveled before reporting a new location.

To use the standard location service, create an instance of the CLLocationManager class and configure its desiredAccuracy and distanceFilter properties. To begin receiving location notifications, assign a delegate to the object and call the startUpdatingLocation method. As location data becomes available, the location manager notifies its assigned delegate object. If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered.

- (void)startStandardUpdates
{
    // Create the location manager if this object does not
    // already have one.
    if (nil == locationManager)
        locationManager = [[CLLocationManager alloc] init];

    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;

    // Set a movement threshold for new events.
    locationManager.distanceFilter = 500;

    [locationManager startUpdatingLocation];
}
           

Starting the Significant-Change Location Service

In iOS 4.0 and later, you can use the significant-change location service to receive location events. This service offers a significant power savings and provides accuracy that is good enough for most applications. It uses the device’s cellular radio to determine the user’s location and report changes in that location, allowing the system to manage power usage much more aggressively than it could otherwise. This service is also capable of waking up an application that is currently suspended or not running in order to deliver new location data.

To use the significant-change location service, create an instance of the CLLocationManager class, assign a delegate to it, and call the startMonitoringSignificantLocationChanges method. As location data becomes available, the location manager notifies its assigned delegate object. If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered.

- (void)startSignificantChangeUpdates
{
    // Create the location manager if this object does not
    // already have one.
    if (nil == locationManager)
        locationManager = [[CLLocationManager alloc] init];

    locationManager.delegate = self;
    [locationManager startMonitoringSignificantLocationChanges];
}
           

Receiving Location Data from a Service

Whether you use the standard location service or the significant-change location service to get location events, the way you receive those events is the same. Whenever a new event is available, the location manager reports it to the locationManager:didUpdateToLocation:fromLocation: method of its delegate. If there is an error retrieving an event, the location manager calls the locationManager:didFailWithError: method of its delegate instead.

 Because the location manager object sometimes returns cached events, it is recommended that you check the timestamp of any location events you receive.

// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation
{
    // If it's a relatively recent event, turn off updates to save power
    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
    if (abs(howRecent) < 15.0)
    {
        NSLog(@"latitude %+.6f, longitude %+.6f\n",
                newLocation.coordinate.latitude,
                newLocation.coordinate.longitude);
    }
    // else skip the event and process the next one.
}
           

Monitoring Shape-Based Regions

Determining the Availability of Region Monitoring

There are several reasons why region monitoring might not be available:

  • The device may not have the hardware needed to support region monitoring.
  • The user may have disabled location services in the Settings application.
  • The device might be in Airplane mode and unable to power up the necessary hardware.

For these reasons, it is recommended that you always call the regionMonitoringAvailable and regionMonitoringEnabled class methods of CLLocationManager before attempting to monitor regions. The regionMonitoringAvailable method lets you know whether the underlying hardware supports region monitoring. If it returns NO, there is no chance that your application will ever be able to use region monitoring on the device. If region monitoring is available, the regionMonitoringEnabled method reports whether the feature is currently enabled. If region monitoring is available but not enabled when you attempt to monitor a region, the system prompts the user to confirm whether region monitoring should be reenabled. 

Defining a Region to Be Monitored

To begin monitoring a region, you must define the region and register it with the system. Regions are defined using the CLRegion class, which currently supports the creation of circular regions. Each region you create must include both the data that defines the desired geographic area and a unique identifier string. (The identifier string is required and is the only guaranteed way for your application to identify regions later.) To register a region, you call the startMonitoringForRegion:desiredAccuracy: method of your CLLocationManager object.

- (BOOL)registerRegionWithCircularOverlay:(MyCircle*)overlay andIdentifier:(NSString*)identifier
{
   // Do not create regions if support is unavailable or disabled.
   if ( ![CLLocationManager regionMonitoringAvailable] ||
        ![CLLocationManager regionMonitoringEnabled] )
      return NO;

   // If the radius is too large, registration fails automatically,
   // so clamp the radius to the max value.
   CLLocationDegrees radius = overlay.radius;
   if (radius > self.locationManager.maximumRegionMonitoringDistance)
      radius = self.locationManager.maximumRegionMonitoringDistance;

   // Create the region and start monitoring it.
   CLRegion* region = [[CLRegion alloc] initCircularRegionWithCenter:overlay.coordinate
                        radius:radius identifier:identifier];
   [self.locationManager startMonitoringForRegion:region
                    desiredAccuracy:kCLLocationAccuracyHundredMeters];

   [region release];
   return YES;
}
           

Monitoring of a region begins immediately after registration. However, do not expect to receive an event right away. Only boundary crossings can generate an event. Thus, if at registration time the user’s location is already inside the region, the location manager does not generate an event. Instead, you must wait for the user to cross the region boundary before an event is generated and sent to the delegate.

Core Location limits the number of regions that may be simultaneously monitored by a single application. If you attempt to register a region and space is unavailable, the location manager calls the locationManager:monitoringDidFailForRegion:withError: method of its delegate with the kCLErrorRegionMonitoringFailure error code.

Handling Boundary-Crossing Events for a Region

Applications can implement the following methods to handle boundary crossings:

  • locationManager:didEnterRegion:
  • locationManager:didExitRegion:

The system does not report boundary crossings until the boundary plus a designated cushion distance is exceeded. You specify the desired cushion distance for a region when you register it using the startMonitoringForRegion:desiredAccuracy: method. This cushion value prevents the system from generating numerous entered and exited events in quick succession while the user is traveling close the edge of the boundary.

Getting Location Events in the Background

..............

Tips for Conserving Battery Power

  • Turn off location services when you are not using them. 
  • Use the significant-change location service instead of the standard location service whenever possible. The significant-change location service provides significant power savings while still allowing you to leave location services running. This is highly recommended for applications that need to track changes in the user’s location but do not need the higher precision offered by the standard location services.
  • Use lower-resolution values for the desired accuracy unless doing so would impair your application. 
  • Turn off location events if the accuracy does not improve over a period of time. 

Getting Heading-Related Events

Heading events are available to applications running on a device that contains a magnetometer. 

Heading values can be reported relative either to magnetic north or true north on the map. Magnetic north represents the point on the Earth’s surface from which the planet’s magnetic field emanates. This location is not the same as the North Pole, which represents true north. Depending on the location of the device, magnetic north may be good enough for many purposes, but the closer to the poles you get, the less useful this value becomes.

The steps for receiving heading events are as follows:

  1. Create a CLLocationManager object.
  2. Determine whether heading events are available by calling the headingAvailable class method. 
  3. Assign a delegate to the location manager object.
  4. If you want true north values, start location services.
  5. Call the startUpdatingHeading method to begin the delivery of heading events.
- (void)startHeadingEvents {
   if (!self.locationManager) {
      CLLocationManager* theManager = [[[CLLocationManager alloc] init] autorelease];
 
      // Retain the object in a property.
      self.locationManager = theManager;
      self.locationManager.delegate = self;
   }
 
   // Start location services to get the true heading.
   locationManager.distanceFilter = 1000;
   locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
   [locManager startUpdatingLocation];
 
   // Start heading updates.
   if ([CLLocationManager headingAvailable]) {
      locManager.headingFilter = 5;
      [locManager startUpdatingHeading];
   }
}
           

When a new heading event arrives, the location manager object calls the locationManager:didUpdateHeading: method to deliver that event to your application. Upon receiving a new event, you should check the headingAccuracy property to ensure that the data you just received is valid

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
   if (newHeading.headingAccuracy < 0)
      return;
 
   // Use the true heading if it is valid.
   CLLocationDirection  theHeading = ((newHeading.trueHeading > 0) ?
            newHeading.trueHeading : newHeading.magneticHeading);
 
   self.currentHeading = theHeading;
   [self updateHeadingDisplays];
}
           

Getting Course Information While the User Is Moving

Devices that include GPS hardware can generate information indicating the device’s current course and speed. Course information is used to indicate the direction in which the device is moving and does not necessarily reflect the orientation of the device itself. As a result, it is primarily intended for applications that provide navigation information while the user is moving.

The actual course and speed information is returned to your application in the same CLLocation objects you use to get the user’s position. When you start location updates, Core Location automatically provides course and speed information when it is available. The framework uses the incoming location events to compute the current direction of motion. 

Geocoding Location Data

About Geocoder Objects

A geocoder object uses a network service to convert between latitude and longitude values and a user-friendly placemark, which is a collection of data such as the street, city, state, and country information. Many geocoding services allow you to perform conversions in either direction but iOS currently supports only reverse geocoding, which converts a latitude and longitude into a placemark. If you want to convert placemark information into a latitude and longitude—a process known as forward geocoding—you would need to find an appropriate third-party service to use.

Here are some rules of thumb to apply when creating your requests:

  • Send at most one reverse-geocoding request for any one user action.
  • If the user performs multiple actions that involve reverse-geocoding the same location, reuse the results from the initial reverse-geocoding request instead of starting individual requests for each action.
  • When you want to update the location automatically (such as when the user is moving), reissue the reverse-geocoding request only when the user's location has moved a significant distance and after a reasonable amount of time has passed. 
  • Do not start a reverse-geocoding request at a time when the user will not see the results immediately. 

Getting Placemark Information from the Reverse Geocoder

Reverse geocoding is done using the MKReverseGeocoder class of the Map Kit framework. This class uses a delegate-based approach for geocoding a single location. This means that you can use a single instance of the MKReverseGeocoder class only once. In addition, the Google terms of service require that the MKReverseGeocoder class be used in conjunction with a Google map.

To initiate a reverse geocoding request, create an instance of the MKReverseGeocoder class, assign an appropriate object to the delegate property, and call the start method. If the query completes successfully, your delegate’s reverseGeocoder:didFindPlacemark: method is called and passed an MKPlacemark object with the results. If there is a problem reverse geocoding the location, the reverseGeocoder:didFailWithError: method is called instead.

@implementation MyGeocoderViewController (CustomGeocodingAdditions)
- (void)geocodeLocation:(CLLocation*)location forAnnotation:(MapLocation*)annotation
{
    MKReverseGeocoder* theGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:location.coordinate];
 
    theGeocoder.delegate = self;
    [theGeocoder start];
}
 
// Delegate methods
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFindPlacemark:(MKPlacemark*)place
{
    MapLocation*    theAnnotation = [map annotationForCoordinate:place.coordinate];
    if (!theAnnotation)
        return;
 
   // Associate the placemark with the annotation.
    theAnnotation.placemark = place;
 
    // Add a More Info button to the annotation's view.
    MKPinAnnotationView*  view = (MKPinAnnotationView*)[map viewForAnnotation:annotation];
    if (view && (view.rightCalloutAccessoryView == nil))
    {
        view.canShowCallout = YES;
        view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    }
}
 
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFailWithError:(NSError*)error
{
    NSLog(@"Could not retrieve the specified place information.\n");
}
@end
 
@implementation MKMapView (GeocoderAdditions)
 
- (MapLocation*)annotationForCoordinate:(CLLocationCoordinate2D)coord
{
    // Iterate through the map view's list of coordinates
    // and return the first one whose coordinate matches
    // the specified value exactly.
    id<MKAnnotation> theObj = nil;
 
    for (id obj in [self annotations])
    {
        if (([obj isKindOfClass:[MapLocation class]]))
        {
            MapLocation* anObj = (MapLocation*)obj;
 
            if ((anObj.coordinate.latitude == coord.latitude) &&
                (anObj.coordinate.longitude == coord.longitude))
            {
                theObj = anObj;
                break;
            }
        }
    }

    return theObj;
}
@end