Autofill city and state from zip code in iOS - ios

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?
}

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
}
}];
}

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

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)

Related

How to get a value from dictionary which is inside an array?

I want to store a message - "hi this is John KL", in some string, how to parse following example.
[
{
"message": "hi this is John KL"
}
]
Swift:
guard let anArray = input as? [[String:String]],
let message = anArray.first["message"] else {
print("unable to fetch data"
}
Objective-C:
- (void) readJSON {
NSError *result;
NSURL *url = [[NSBundle mainBundle] URLForResource: #"sample"
withExtension: #"JSON"];
NSData *data = [NSData dataWithContentsOfURL: url
options: 0
error: &result];
if (result != nil) {
NSLog(#"Error reading file: %#", result);
return;
}
NSArray<NSDictionary<NSString*, NSString*> *> *array = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &result];
if (result != nil) {
NSLog(#"Error converting JSON: %#", result);
return;
}
else {
NSLog(#"\nJSON data = \n%#", array);
if (array.count < 1) {
NSLog(#"Not enough elements in array");
return;
}
NSString *message = array[0][#"message"];
if (message == nil) {
NSLog(#"Unable to fetch message");
} else {
NSLog(#"Message = \"%#\"", message);
}
}
}
The above Objective-C code does not test to make sure the object read from the JSON file is the correct type. It will crash if it is not an array containing a dictionary with a string key and string value. For a production app you'll want to add code to type-check the data.
Swift
Assign your json array to a variable type of [String : String] or [String : Any] dictionary array. [String : Any] is most commonly used dictionary but according to your data it suites with [String : String]
if let array = [
{
“message” : “hi this is John KL”
}
] as [Any]
Now, get your json/dictionary from array using index value and then get string from json using json key.
if let dictionary = array.first as? [String : Any] {
if let stringMessage = dictionary["message"] as? String {
print("stringMessage - \(stringMessage)")
}
}
Objective-C
NSArray * array = [
{
“message” : “hi this is John KL”
}
];
NSDictionary * dictionary = (NSDictionary *)[array objectAtIndex: 0];
NSString * stringMessage = (NSString *)[dictionary valueForKey: "message"];
NSLog(#"stringMessage - %#",stringMessage);
You can try this answer.
NSArray *result = [json objectForKey:#"result"];
for(NSString *currenObject in result){
NSLog(#"%#",currenObject);
NSString *currentValue = [currenObject valueForKey:#"message"];
}
NSLog(#"%#",currentValue);

Error in PDKPin.m for Pinterest iOS SDK

I am currently using the iOS SDK's "getAuthenticatedUserPinsWithFields" method to return a user's pins:
let fields = ["id","note","url","image"] as NSSet
PDKClient.sharedInstance().getAuthenticatedUserPinsWithFields(fields as Set<NSObject>,
success: { (responseObject :PDKResponseObject!) -> Void in
I believe I traced the error to PDKPin.m initWithDictionary:
(instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super initWithDictionary:dictionary];
if (self) {
// _url = [NSURL URLWithString:dictionary[#"link"]];
_url = [NSURL URLWithString:dictionary[#"url"]];
_descriptionText = dictionary[#"note"];
_board = [PDKBoard boardFromDictionary:dictionary[#"board"]];
_creator = [PDKUser userFromDictionary:dictionary[#"creator"]];
_metaData = dictionary[#"metadata"];
_repins = [self.counts[#"repins"] unsignedIntegerValue];
_likes = [self.counts[#"likes"] unsignedIntegerValue];
_comments = [self.counts[#"comments"] unsignedIntegerValue];
}
return self;
}
The #"link" reference always returns nil but using "url" will return the url to the pin. Is the SDK wrong in this case?
Thanks, Anita

How to convert CLLCoordinate2D lat/long into NSNumber

I'm trying to use Yelp API, and i'm trying to have the lat/long as the params for the API search. However, it does not take the type double, it only accepts Objective-C objects. Having no knowledge in Objective-C, what do you suggest the type for the parameter of lat and long be? I tried NSNumber, but when i try to turn my lat/long coordinates of type CLLocationCoordinate2D to an NSNumber that takes in a double, its value is nil
Here is my Yelp API that I am using:
- (void)queryTopBusinessInfoForTerm:(NSString *)term location:(NSString *)location latitude:(NSNumber *)latitude longitude:(NSNumber *)longitude completionHandler:(void (^)(NSDictionary *topBusinessJSON, NSError *error))completionHandler {
NSLog(#"Querying the Search API with term \'%#\' and location \'%#'", term, location);
//Make a first request to get the search results with the passed term and location
NSURLRequest *searchRequest = [self _searchRequestWithTerm:term location:location latitude:latitude longitude:longitude];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:searchRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (!error && httpResponse.statusCode == 200) {
NSDictionary *searchResponseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSArray *businessArray = searchResponseJSON[#"businesses"];
if ([businessArray count] > 0) {
NSDictionary *firstBusiness = [businessArray firstObject];
NSString *firstBusinessID = firstBusiness[#"id"];
NSLog(#"%lu businesses found, querying business info for the top result: %#", (unsigned long)[businessArray count], firstBusinessID);
[self queryBusinessInfoForBusinessId:firstBusinessID completionHandler:completionHandler];
} else {
completionHandler(nil, error); // No business was found
}
} else {
completionHandler(nil, error); // An error happened or the HTTP response is not a 200 OK
}
}] resume];
}
And here is the params
- (NSURLRequest *)_searchRequestWithTerm:(NSString *)term location:(NSString *)location latitude:(NSNumber *) latitude longitude:(NSNumber *)longitude {
NSDictionary *params = #{
#"term": term,
#"location": location,
#"cll": latitude,
#"cll": longitude,
#"limit": kSearchLimit
};
return [NSURLRequest requestWithHost:kAPIHost path:kSearchPath params:params];
}
And here is my current Swift method calling the Query from YelpAPi:
func yelpApi() {
var latitude = NSNumber(double: businessStreetAddress.latitude)
var longitude = NSNumber(double: businessStreetAddress.longitude)
var searchTerm: NSString = "Asian Food";
var defaultLocation: NSString = "New York"
var APISample:YPAPISample = YPAPISample();
var requestGroup:dispatch_group_t = dispatch_group_create();
APISample.queryTopBusinessInfoForTerm(searchTerm as String, location: defaultLocation as String, latitude: latitude, longitude: longitude) { (topBusinessJSON: [NSObject: AnyObject]!, error: NSError!) -> Void in
if((error) != nil) {
println("Error happened during the request" + error.localizedDescription);
} else if((topBusinessJSON) != nil) {
println("Top business info",topBusinessJSON);
} else {
println("No business was found");
}
dispatch_group_leave(requestGroup);
}
dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
}
To convert the CLLocationCoordinate2D to NSNumber, You cannot have it in a single NSNumber. You can convert to two NSNumber objects like follows:
CLLocationCoordinate2D location; // This is the location you have.
NSNumber *latitude = [NSNumber numberWithDouble:location.latitude];
NSNumber *longitude = [NSNumber numberWithDouble:location.longitude];
with Modern ObjC you can covert as:
CLLocationCoordinate2D location; // This is the location you have.
NSNumber *latitude = #(location.latitude);
NSNumber *longitude = #(location.longitude);
and call your
NSURLRequest *searchRequest = [self _searchRequestWithTerm:term location:location latitude:latitude longitude:longitude];
Don't confuse with the variable named like location which just I have named it. because your function searchRequestWithTerm:location:latitude:longitude: is having one parameter named location which accepts NSString.
This may help you.

Google Maps iOS SDK, Getting Directions between 2 locations

While I am using Google Maps SDK, I am trying to get driving direction between two locations on iOS. I know we can do this using two methods:-
1.) Using URL Scheme, for which it is necessary that Google Maps App is installed on your device.
2.) Using Directions API, via Request-Response and then parsing the JSON. Displaying markers to show the direction.
Now, my question is there any other way by which I can do this on iOS? I need to show the direction from my current location to a particular location of which i have the Lat/Long.
I mean is it really not possible to simply pass 2 location as parameter and Google Maps SDK, will give me the directions?
Thanks,
NSString *urlString = [NSString stringWithFormat:
#"%#?origin=%f,%f&destination=%f,%f&sensor=true&key=%#",
#"https://maps.googleapis.com/maps/api/directions/json",
mapView.myLocation.coordinate.latitude,
mapView.myLocation.coordinate.longitude,
destLatitude,
destLongitude,
#"Your Google Api Key String"];
NSURL *directionsURL = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:directionsURL];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
NSLog(#"%#",response);
NSDictionary *json =[NSJSONSerialization JSONObjectWithData:[request responseData] options:NSJSONReadingMutableContainers error:&error];
GMSPath *path =[GMSPath pathFromEncodedPath:json[#"routes"][0][#"overview_polyline"][#"points"]];
GMSPolyline *singleLine = [GMSPolyline polylineWithPath:path];
singleLine.strokeWidth = 7;
singleLine.strokeColor = [UIColor greenColor];
singleLine.map = self.mapView;
}
else NSLog(#"%#",[request error]);
Note: make Sure Your Google Direction API Sdk Is Enable in Your google developer Console.
It sounds like you are looking for UI Chrome like the Google Maps app has for showing directions. Google Maps SDK for iOS will paint you a map, but you are responsible for the additional navigation chrome.
You can use the Google Directions API to request directions, and then use the encoded path returned from the service to draw a GMSPolyline using GMSPath's pathFromEncodedPath: method.
These lines shows location between a given latitude / longitude and user location;
NSString *googleMapUrlString = [NSString stringWithFormat:#"http://maps.google.com/?saddr=%f,%f&daddr=%#,%#", mapView.userLocation.coordinate.latitude, mapView.userLocation.coordinate.longitude, destinationLatitude, destinationLongtitude];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:googleMapUrlString]];
Swift 3.0 & XCode 8.0
Using AFNetworking & SwiftJson
let destLatitude="26.9124"
let destLongitude="75.7873"
mapView.isMyLocationEnabled = true
var urlString = "\("https://maps.googleapis.com/maps/api/directions/json")?origin=\("28.7041"),\("77.1025")&destination=\(destLatitude),\(destLongitude)&sensor=true&key=\("Your-Api-key")"
urlString = urlString.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)!
let manager=AFHTTPRequestOperationManager()
manager.responseSerializer = AFJSONResponseSerializer(readingOptions: JSONSerialization.ReadingOptions.allowFragments) as AFJSONResponseSerializer
manager.requestSerializer = AFJSONRequestSerializer() as AFJSONRequestSerializer
manager.responseSerializer.acceptableContentTypes = NSSet(objects:"application/json", "text/html", "text/plain", "text/json", "text/javascript", "audio/wav") as Set<NSObject>
manager.post(urlString, parameters: nil, constructingBodyWith: { (formdata:AFMultipartFormData!) -> Void in
}, success: { operation, response -> Void in
//{"responseString" : "Success","result" : {"userId" : "4"},"errorCode" : 1}
//if(response != nil){
let parsedData = JSON(response)
print_debug("parsedData : \(parsedData)")
var path = GMSPath.init(fromEncodedPath: parsedData["routes"][0]["overview_polyline"]["points"].string!)
//GMSPath.fromEncodedPath(parsedData["routes"][0]["overview_polyline"]["points"].string!)
var singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 7
singleLine.strokeColor = UIColor.green
singleLine.map = self.mapView
//let loginResponeObj=LoginRespone.init(fromJson: parsedData)
// }
}, failure: { operation, error -> Void in
print_debug(error)
let errorDict = NSMutableDictionary()
errorDict.setObject(ErrorCodes.errorCodeFailed.rawValue, forKey: ServiceKeys.keyErrorCode.rawValue as NSCopying)
errorDict.setObject(ErrorMessages.errorTryAgain.rawValue, forKey: ServiceKeys.keyErrorMessage.rawValue as NSCopying)
})
Swift 4.1, Xcode 9.4.1
//Here you need to set your origin and destination points and mode
let url = NSURL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=Machilipatnam&destination=Vijayawada&mode=driving")
//OR if you want to use latitude and longitude for source and destination
//let url = NSURL(string: "\("https://maps.googleapis.com/maps/api/directions/json")?origin=\("17.521100"),\("78.452854")&destination=\("15.1393932"),\("76.9214428")")
let task = URLSession.shared.dataTask(with: url! as URL) { (data, response, error) -> Void in
do {
if data != nil {
let dic = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableLeaves) as! [String:AnyObject]
// print(dic)
let status = dic["status"] as! String
var routesArray:String!
if status == "OK" {
routesArray = (((dic["routes"]!as! [Any])[0] as! [String:Any])["overview_polyline"] as! [String:Any])["points"] as! String
// print("routesArray: \(String(describing: routesArray))")
}
DispatchQueue.main.async {
let path = GMSPath.init(fromEncodedPath: routesArray!)
let singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 6.0
singleLine.strokeColor = .blue
singleLine.map = mapView
}
}
} catch {
print("Error")
}
}
task.resume()
Here, you need to add your key (google api key) to the above API.
I had done it as it also shows PINS DISTANCE AND DURATION on map with DIRECTION ROUTE. But dont forget to set your GOOGLE DIRECTION API TO ENABLED in your GOOGLE DEVELOPER CONSOLE
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
NSString *urlString =#"https://maps.googleapis.com/maps/api/directions/json";
NSDictionary *dictParameters = #{#"origin" : [NSString stringWithFormat:#"%#",_sourceAdd], #"destination" : [NSString stringWithFormat:#"%#",_destinationAdd], #"mode" : #"driving", #"key":#"AIzaSyD9cWTQkAxemELVXTNUCALOmzlDv5b9Dhg"};
[manager GET:urlString parameters:dictParameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
GMSPath *path =[GMSPath pathFromEncodedPath:responseObject[#"routes"][0][#"overview_polyline"][#"points"]];
NSDictionary *arr=responseObject[#"routes"][0][#"legs"];
NSMutableArray *loc=[[NSMutableArray alloc]init];
loc=[[arr valueForKey:#"start_location"]valueForKey:#"lat"];
_sourceloc.latitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"start_location"]valueForKey:#"lng"];
_sourceloc.longitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"end_location"]valueForKey:#"lat"];
_destinationloc.latitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"end_location"]valueForKey:#"lng"];
_destinationloc.longitude=[loc[0] doubleValue];
NSString *dis,*dur;
loc=[[arr valueForKey:#"distance"]valueForKey:#"text"];
dis=loc[0];
loc=[[arr valueForKey:#"duration"]valueForKey:#"text"];
dur=loc[0];
NSString *sa,*da;
loc=[arr valueForKey:#"start_address"];
sa=loc[0];
loc=[arr valueForKey:#"end_address"];
da=loc[0];
UIAlertView *av=[[UIAlertView alloc]initWithTitle:#"Route Info" message:[NSString stringWithFormat:#"Distance:%# \nDuration:%#",dis,dur] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil, nil];
[av show];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:_sourceloc.latitude longitude:_sourceloc.longitude zoom:10];
mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
GMSMarker *marker = [GMSMarker markerWithPosition:_sourceloc];
marker.title=#"Source";
marker.snippet =sa;
marker.appearAnimation = kGMSMarkerAnimationPop;
marker.map = mapView;
GMSMarker *marker2 = [GMSMarker markerWithPosition:_destinationloc];
marker2.title=#"Destination";
marker2.snippet =da;
marker2.appearAnimation = kGMSMarkerAnimationPop;
marker2.map = mapView;
GMSPolyline *singleLine = [GMSPolyline polylineWithPath:path];
singleLine.strokeWidth = 4;
singleLine.strokeColor = [UIColor blueColor];
singleLine.map = mapView;
self.view = mapView;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Using Swift I definitely solved in this way.
My purpose was finding distance between two coordinates:
import AFNetworking
/**
Calculate distance between two valid coordinates
- parameter origin: origin coordinates
- parameter destination: destination coordinates
- parameter completion: completion callback
*/
func calculateDistance(origin origin: CLLocation, destination: CLLocation, completion: (distance: Double?) -> Void) {
let service = "https://maps.googleapis.com/maps/api/directions/json"
let originLat = origin.coordinate.latitude
let originLong = origin.coordinate.longitude
let destLat = destination.coordinate.latitude
let destLong = destination.coordinate.longitude
let urlString = "\(service)?origin=\(originLat),\(originLong)&destination=\(destLat),\(destLong)&mode=driving&units=metric&sensor=true&key=<YOUR_KEY>"
let directionsURL = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: directionsURL!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let operation = AFHTTPRequestOperation(request: request)
operation.responseSerializer = AFJSONResponseSerializer()
operation.setCompletionBlockWithSuccess({ (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) -> Void in
if let result = responseObject as? NSDictionary {
if let routes = result["routes"] as? [NSDictionary] {
if let lines = routes[0]["overview_polyline"] as? NSDictionary {
if let points = lines["points"] as? String {
let path = GMSPath(fromEncodedPath: points)
let distance = GMSGeometryLength(path)
print("wow \(distance / 1000) KM")
}
}
}
}
}) { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
print("\(error)")
}
operation.start()
}
(void)viewDidLoad {
[super viewDidLoad];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:30.692408
longitude:76.767556
zoom:14];
GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
mapView.myLocationEnabled = YES;
// Creates markers in the center of the map.
GMSMarker *marker = [[GMSMarker alloc] init];
marker.position = CLLocationCoordinate2DMake(30.6936659, 76.77201819999999);
marker.title = #"Chandigarh 47c";
marker.snippet = #"Hello World";
marker.map = mapView;
GMSMarker *marker1 = [[GMSMarker alloc] init];
marker1.position = CLLocationCoordinate2DMake(30.742138, 76.818756);
marker1.title = #"Sukhna Lake";
marker1.map = mapView;
//creating a path
GMSMutablePath *path = [GMSMutablePath path];
[path addCoordinate:CLLocationCoordinate2DMake(#(30.6936659).doubleValue,#(76.77201819999999).doubleValue)];
[path addCoordinate:CLLocationCoordinate2DMake(#(30.742138).doubleValue,#(76.818756).doubleValue)];
GMSPolyline *rectangle = [GMSPolyline polylineWithPath:path];
rectangle.strokeWidth = 2.f;
rectangle.map = mapView;
self.view=mapView;
}
If someone is looking to parse the distance from routes array following is the way to get the distance in swift 4/5
let distance = responseJSON["routes"][0]["legs"][0]["distance"]["text"]
As #iOS suggested I'm also posting my answer to show you a way how to accomplish it using Codable and Alamofire/Moya.
To do so, you'd have to remodel the GoogleMaps Response entities, like so:
/// Struct for modelling a response from the Google Maps Directions API. This is the "root level"
struct GMSDirectionsResponse: Codable {
/// The suggested routes
let routes: [GMSRoute]
/// Status telling if request was okay
let status: String
}
/// Struct for modelling a Route suggested by GoogleMaps Directions API.
struct GMSRoute: Codable {
/// Represents an area in the map
struct Bounds: Codable {
// You can omit these structs for your case.
// Just to give an idea how it would look like if you model the entire response
// ...
}
struct Leg: Codable {
// ...
}
/// A single step of a route
struct Step: Codable {
// ...
}
/// Structure around the coded representation of the GMSPath
struct OverviewPolyline: Codable {
/// A coded representation of the GMSPath
let points: String
}
/// The bounds to show on the map to include the whole route
let bounds: Bounds?
/// All legs of this route, including the single steps
let legs: [Leg]?
/// The path to walk/drive/etc. this route
let overview_polyline: OverviewPolyline?
/// A textual summary of the most important roads to take
let summary: String?
}
You can see how a response object consists of an array of routes and a status string (e.g. "OK"). Each route has a few properties again, including the overview_polyline field. For being able to have that object decoded by a JSONDecoder you also need to model that class (it simply contains a string value for the key points.
Now if you only need the overview_polyline it's perfectly fine to omit all other unneeded properties and structs as long as you still model the hierarchical structure of the response (e.g. GMSDirectionsResponse > GMSRoute > OverviewPolyline > points).
What you can do now is to ask a JSONDecoder to decode a GMSDirectionsResponse from the body data with a single line! In my project I used Moya but I'm sure you can also do it with URLSession's data object.
// let moya do request
let moya = MoyaProvider<YourGMSService>()
moya.request(.getDirections(origin: origin, destination: destination)) { (result) in
switch result {
case .success(let response):
// check if request was successful
if
// makes sure status is code is 2xx
(try? response.filterSuccessfulStatusCodes()) != nil,
// this line tries to decode a GMSDirectionsResponse object from response.data
let directions = try? JSONDecoder().decode(GMSDirectionsResponse.self, from: response.data)
{
// successful request, converted response to JSON Dictionary
NSLog("GET DIRECTIONS: Success")
// here we can check for the directions properites already!
NSLog("GoogleMaps Directions request finished with status %#", directions.status)
// check if we have at least one GMSRoute with an overview_polyline
guard let encodedPath = directions.routes.first?.overview_polyline else { return }
// now let's use the encoded path:
DispatchQueue.main.async {
let path = GMSPath.init(fromEncodedPath: encodedPath.points)
// continue as in other answers (Y)
let singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 6.0
singleLine.strokeColor = .blue
singleLine.map = mapView
}
return
}
// some error handling if we couldn't parse the data to a GMSDirectionsResponse object
NSLog("Could not parse JSON from Directions API Response:\n%#", response.debugDescription)
case .failure(let error):
// we had an error
NSLog(error.localizedDescription)
}
// log and complete with nil
NSLog("GET DIRECTIONS: Failed")
}
This might look like a huge load of code but it's totally convenient and it keeps you from messing around with JSON subscript members and lots of [] braces. 😊
If you have questions, I'm happy to help!
If you are using a restricted key, you have to add X-Ios-Bundle-Identifier header with the bundle you restricted the key to. With that header it works also from Postman.
Create a key in google developer console make sure your project is created with App bundleID after that add the following code
NSString *KEY=#"";
NSString *Origin=#"";
NSString *Destination=#"";
NSString *str_maps=[NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/directions/json?origin=%#&destination=%#&key=%#",Origin,Destination,KEY];
NSURL *url=[NSURL URLWithString:str_maps];
NSData *dta=[NSData dataWithContentsOfURL:url];
NSDictionary *dict=(NSDictionary *)[NSJSONSerialization JSONObjectWithData:dta options:kNilOptions error:nil];
NSLog(#"%#",dict);

Get current city and country from CLGeocoder?

I've been all over the internet trying to find out how to get the city and country from CLGeocoder. I can get the longitude and latitude easily but I need the city and country information, and I keep running into deprecated methods and such, any ideas? It basically needs to get the location, then have an NSString for the country, and an NSString for the city, so I can use them to look up more info or put them on labels, etc.
You need to revise your terminology a bit - CLGeocoder (and most geocoders) won't give you a 'city' per-se - it uses terms such as 'Administrative Area', 'Subadministrative Area', etc. The CLGeocoder object will return an array of CLPlacemark objects which you can then query for the information you need. You init a CLGeocoder and call the reverseGeocodeLocation function with a location and a completion block. Here's an example:
if (osVersion() >= 5.0){
CLGeocoder *reverseGeocoder = [[CLGeocoder alloc] init];
[reverseGeocoder reverseGeocodeLocation:self.currentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
DDLogVerbose(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
DDLogError(#"Geocode failed with error: %#", error);
return;
}
DDLogVerbose(#"Received placemarks: %#", placemarks);
CLPlacemark *myPlacemark = [placemarks objectAtIndex:0];
NSString *countryCode = myPlacemark.ISOcountryCode;
NSString *countryName = myPlacemark.country;
DDLogVerbose(#"My country code: %# and countryName: %#", countryCode, countryName);
}];
}
Now note that the CLPlacemark doesn't have a 'city' property. The full list of properties can be found here: CLPlacemark Class Reference
You can get city, country and iso country code using this (Swift 5):
private func getAddress(from coordinates: CLLocation) {
CLGeocoder().reverseGeocodeLocation(coordinates) { placemark, error in
guard error == nil,
let placemark = placemark
else
{
// TODO: Handle error
return
}
if placemark.count > 0 {
let place = placemark[0]
let city = place.locality
let country = place.country
let countryIsoCode = place.isoCountryCode
}
}
}

Resources