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:
1 2 3 4 5 6 |
@interface MyViewController : UIViewController <CLLocationManagerDelegate> { CLLocationManager *_locationManager; } @end |
I created in my ViewController two helper methods to show and hide spinner without writing implementation of it:
1 2 3 4 5 6 7 8 9 |
- (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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#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:
1 2 3 4 5 6 |
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { [_locationManager stopUpdatingLocation]; [self showRegionNotFoundError]; } |
This method shows an error if we failed to determine city automatically:
1 2 3 4 5 6 7 |
- (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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#import <MapKit/MapKit.h> #import <CoreLocation/CoreLocation.h> #import <AddressBook/AddressBook.h> ... - (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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 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]; |