Pergunta

I have three textfields in my view. 1. Zip code, 2. City and 3. State.

How to autofill city and state field from the zip code in iOS?

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSString *currentString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    int length = [currentString length];
    if(length > 5)
    {
        return NO;
    }
    if(length == 5)
    {
        [self getCityAndState];
    }
    return YES;
}

- (void) getCityAndState
{
    //How to use google (or any) api to autofill city and state in objective - c?
}
Foi útil?

Solução 2

Use the Google GeoCoding API to extract Information, if you want to send zip code to receive other information, use this:

NSString *strRequestParams = [NSString stringWithFormat:@"http://maps.googleapis.com/maps/api/geocode/json?address=&components=postal_code:%@&sensor=false",zipCode];

strRequestParams = [strRequestParams stringByAddingPercentEscapesUsingEncoding:NSStringEncodingConversionExternalRepresentation];

NSURL *url = [NSURL URLWithString:strRequestParams];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

[request setHTTPMethod:@"GET"];

NSError *error;
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!response) {
    // "Connection Error", "Failed to Connect to the Internet"
}

NSString *respString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] ;
//NSLog(@"RECEIVED DATA : %@", respString);

If your zipcode variable is 32000, you will get the this JSON result:

You can parse this json to extract any information you want including Country, City, longitude, latitude etc

Outras dicas

I try to avoid Google's services because they tend to charge at a certain level of usage. Here's the solution using Apple's frameworks:

 #import <CoreLocation/CoreLocation.h>
 #import <AddressBookUI/AddressBookUI.h>

- (void)didEnterZip:(NSString*)zip
{
    CLGeocoder* geoCoder = [[CLGeocoder alloc] init];
    [geoCoder geocodeAddressDictionary:@{(NSString*)kABPersonAddressZIPKey : zip} 
      completionHandler:^(NSArray *placemarks, NSError *error) {
        if ([placemarks count] > 0) {
            CLPlacemark* placemark = [placemarks objectAtIndex:0];

            NSString* city = placemark.addressDictionary[(NSString*)kABPersonAddressCityKey];
            NSString* state = placemark.addressDictionary[(NSString*)kABPersonAddressStateKey];
            NSString* country = placemark.addressDictionary[(NSString*)kABPersonAddressCountryCodeKey];

        } else {
            // Lookup Failed
        }
    }];
}

The answer by a-r-studios is spot on, since it doesn't introduce a dependency on Google's service.

However, I would also restrict the country code based either on the user's input or US-only if it makes sense. Not restricting it gives unpredictable results because the geocoder can return multiple hits from different countries.

 #import <CoreLocation/CoreLocation.h>
 #import <AddressBookUI/AddressBookUI.h>

- (void)didEnterZip:(NSString*)zip
{
    CLGeocoder* geoCoder = [[CLGeocoder alloc] init];
    [geoCoder geocodeAddressDictionary:@{(NSString*)kABPersonAddressZIPKey : zip, 
          (NSString*)kABPersonAddressCountryCodeKey : @"US"} 
      completionHandler:^(NSArray *placemarks, NSError *error) {
        if ([placemarks count] > 0) {
            CLPlacemark* placemark = [placemarks objectAtIndex:0];

            NSString* city = placemark.addressDictionary[(NSString*)kABPersonAddressCityKey];
            NSString* state = placemark.addressDictionary[(NSString*)kABPersonAddressStateKey];
            NSString* country = placemark.addressDictionary[(NSString*)kABPersonAddressCountryCodeKey];

        } else {
            // Lookup Failed
        }
    }];
}

While the answers from alex_c and a-r-studios work well, if you don't feel like fussing with AddressBookUI or dictionaries, you can simply use the geocodeAddressString:completionHandler: method on the geocoder passing in the zip code alone which is sufficient for the lookup:

[[CLGeocoder new] geocodeAddressString:zip completionHandler:^(NSArray *placemarks, NSError *error) {
    if (placemarks.count) {
        CLPlacemark *placemark = placemarks.firstObject;

        NSString *city = placemark.locality;
        NSString *state = placemark.administrativeArea;
    }
}];

In Swift:

CLGeocoder().geocodeAddressString(zip) { (placemarks, error) in
    if let result = placemarks?.first {
        let city = result.locality
        let state = result.administrativeArea
    }
}

Here is the Swift 3 version with all above corrections.

func zipToAddress(zip: String, onSuccess: @escaping (String, String, String) -> Void, onFail: @escaping () -> Void) {
    let geoCoder = CLGeocoder();

    let params = [
        String(CNPostalAddressPostalCodeKey): zip,
        String(CNPostalAddressISOCountryCodeKey): "US",
        ]

    geoCoder.geocodeAddressDictionary(params) {
        (plasemarks, error) -> Void in

        if let plases = plasemarks {

            if plases.count > 0 {
                let firstPlace = plases[0]

                print( "City \(firstPlace.locality) state \(firstPlace.administrativeArea) and country \(firstPlace.country) and iso country \(firstPlace.country)")

                let city = firstPlace.locality
                let state = firstPlace.administrativeArea
                let country = firstPlace.country

                onSuccess(city != nil ? city! : "", state != nil ? state! : "", country ?? "Not found")
                return;
            }
        }

        onFail()
    }
}

Here is the Swift 4 version, with a proper optionals handling as a bonus. Use this if you want to manually specify the country of the queried ZIP code.

private func zipToAddress(zip: String?, onSuccess: @escaping (String, String) -> Void, onFail: ((Error?) -> Void)?) {

    guard let zip = zip else {
        onFail?(nil)
        return
    }

    let geoCoder = CLGeocoder()

    let params: [String: Any] = [
        String(CNPostalAddressPostalCodeKey): zip,
        String(CNPostalAddressISOCountryCodeKey): "US"
        ]

    geoCoder.geocodeAddressDictionary(params) { placemarks, error -> Void in
        /// Read CLPlacemark documentation to see all available fields
        if let place = placemarks?[0], let city = place.locality, let state = place.administrativeArea {
            onSuccess(city, state)
        } else {
            onFail?(error)
        }
    }
}

And here is the solution based on the Nathan's answer. Use this to query for the city and the administrative area, based on user locale.

 private func localZipToAddress(zip: String?, onSuccess: @escaping (String, String) -> Void, onFail: ((Error?) -> Void)?) {

    guard let zip = zip else {
        onFail?(nil)
        return
    }

    CLGeocoder().geocodeAddressString(zip) { placemarks, error in
        if let result = placemarks?.first, let city = result.locality, let state = result.administrativeArea {
            onSuccess(city, state)
        } else {
            onFail?(error)
        }
    }
}
static func zipToAddress(zip: String, onSuccess: (String, String) -> Void, onFail: () -> Void) {
    var geoCoder = CLGeocoder();

    var params = [
            String(kABPersonAddressZIPKey): zip,
            String(kABPersonAddressCountryCodeKey): "US",
    ]

    geoCoder.geocodeAddressDictionary(params) {
        (plasemarks, error) -> Void in
        var plases = plasemarks as? Array<CLPlacemark>

        if plases != nil && plases?.count > 0 {
            var firstPlace = plases?[0]

            var city = firstPlace?.addressDictionary[String(kABPersonAddressCityKey)] as? String
            var state = firstPlace?.addressDictionary[String(kABPersonAddressStateKey)] as? String
            var country = firstPlace?.addressDictionary[String(kABPersonAddressCountryKey)] as? String // US

            onSuccess(city != nil ? city! : "", state != nil ? state! : "")
            return;
        }

        onFail()
    }
}

same with swift, I can't add this as comment(points doesn't enoght)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top