Tag: Core Location

  • Using Yandex Maps API & Core Location In iOS To Determine City Of a User By Reverse Geocoding

    My goal was to determine the city or town name of a user to fill a region text field automatically in iOS app. At first I tried to use Core Location and MapKit, but I got only city names in English, for example, ‘Ufa’.  I had to get city names in Russian, so I decided to use Yandex Maps API. This is a final result – interface with a button to get city name automatically in Russian and a textField with a city name:

    First, we add CLLocationManagerDelegate interface and an instance variable of CLLocationManager to get coordinates to our View Controller:

    @interface MyViewController : UIViewController 
    {
      CLLocationManager *_locationManager;
    }
    
    @end

    I created in my ViewController two helper methods to show and hide spinner without writing implementation of it:

    - (void)showSpinner
    {
      self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    }
    
    - (void)hideSpinner
    {
      [self.hud hide:YES];
    }

    I have a button on my ViewController. When it’s clicked a TextField is filled by a city name automatically after it is determined.

    #pragma mark - Location determination
    
    // Button click handler
    - (void)determineRegionClicked:(id)sender
    {
      [self showSpinner];
    
      // We create CLLocationManager and start  updating location
      _locationManager = [[CLLocationManager alloc] init];
      _locationManager.delegate = self;
      _locationManager.distanceFilter = kCLDistanceFilterNone;
      _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
      [_locationManager startUpdatingLocation];
    }

    Here we tell, that our ViewController is a delegate of CLLocationManager.

    This is a delegate method, that does all the remaining work. It is called after coordinates are determined. I used SBJson framework to parse JSON.

    #import 
    
    ...
    
    #define YANDEX_MAPS_API_DOMAIN @"http://geocode-maps.yandex.ru/1.x/"
    
    ...
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    {
      // Switching off location updates
      [_locationManager stopUpdatingLocation];
    
      // Reverse geocoding using Yandex Maps API
      CLLocationCoordinate2D coordinate = newLocation.coordinate;
      NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", coordinate.longitude, coordinate.latitude];
    
      // Creating HTTP request. Here kind=locality means that we will be searching for a city.
      NSString *urlString = [NSString stringWithFormat:@"%@?geocode=%@&results=1&lang=ru-RU&format=json&kind=locality", YANDEX_MAPS_API_DOMAIN, coordinatesString];
      NSURL *url = [NSURL URLWithString:urlString];
      NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
    
      // Sending synchronous request in background queue using dispatch queues
      dispatch_queue_t backgroundQueue = dispatch_queue_create("bgQueue", nil);
      dispatch_async(backgroundQueue, ^{
        __autoreleasing NSHTTPURLResponse *urlResponse = nil;
        __autoreleasing NSError *error = nil;
        NSData *resultData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&urlResponse error:&error];
    
        // Parsing a result
        if (! error) {
          NSLog(@"Status Code: %d %@", [urlResponse statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[urlResponse statusCode]]);
    
          NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
          // NSLog(@"resultString:%@", resultString);
    
          SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
          NSDictionary *jsonObject = [jsonParser objectWithString:resultString];
    
          // If count is zero, then region is not found
          int count = [jsonObject[@"response"][@"GeoObjectCollection"][@"metaDataProperty"][@"GeocoderResponseMetaData"][@"found"] integerValue];
          if (count == 0) {
            dispatch_async(dispatch_get_main_queue(), ^{
              [self showRegionNotFoundError];
            });
            return;
          }
    
          // Searching in a JSON
          NSString *city = [jsonObject[@"response"][@"GeoObjectCollection"][@"featureMember"] firstObject][@"GeoObject"][@"name"];
          NSLog(@"Yandex API City returned: %@", city);
    
          dispatch_async(dispatch_get_main_queue(), ^{
            // Here I have a code that updates my UI after city is determined
          });
    
        }
        else {
          // If failed to get data from Yandex API
          NSLog(@"An error occured, Status Code: %i", urlResponse.statusCode);
          NSLog(@"Description: %@", [error localizedDescription]);
          NSLog(@"Response Body: %@", [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]);
    
          dispatch_async(dispatch_get_main_queue(), ^{
            [self showRegionNotFoundError];
          });
        }
    
        dispatch_async(dispatch_get_main_queue(), ^{
          [self hideSpinner];
        });
    
      });
    }
    

    This is an error handling for Core Location:

    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
      [_locationManager stopUpdatingLocation];
    
      [self showRegionNotFoundError];
    }

    This method shows an error if we failed to determine city automatically:

    - (void)showRegionNotFoundError
    {
      [self hideSpinner];
    
      UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Failed to determine your city automatically, please, select the one from a list." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
      [alertView show];
    }

    The same goal can be achieved using MapKit instead of Yandex Maps API for English city names:

    #import 
    #import 
    #import 
    
    ... 
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    
    ... 
    
      CLGeocoder *geocoder = [[CLGeocoder alloc] init] ;
      
      [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
        
        [self hideSpinner];
        
        if (! error )
        {
          CLPlacemark *placemark = [placemarks objectAtIndex:0];
          NSString *city = [[placemark addressDictionary] objectForKey:(NSString *)kABPersonAddressCityKey];
          NSLog(@"determinedRegion: %@", city);
        
          NSLog(@"Full placemark address dictionary: %@", placemark.addressDictionary);
        }
        else
        {
          [self showRegionNotFoundError];
          
          NSLog(@"Geocode failed with error %@", error);
          NSLog(@"\nCurrent Location Not Detected\n");
        }
      }];
    

    Finally, some test locations. Replace the appropriate string to simulate another coordinates. This could be done in Simulator settings also in Debug/Geo section.

      // Test for Novgorod
      // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 44.0000f, 56.3333f];
    
      // Test for Ufa city
      // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 55.9667f, 54.7500f];
    
      // Test for a small town Meleuz in the Republic of Bashkortostan, Russia
      // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 55.9333f, 52.9500f];
    
      // Test for a random location on a map
      // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 56.9333f, 53.9500f];
    
      // Test for Moscow
      // NSString *coordinatesString = [NSString stringWithFormat:@"%lf,%lf", 37.6167f, 55.7500f];