I want to find out photos location using reverseGeocodeLocation. I use the below function
#interface GalleryPhotosViewController ()
{
CLPlacemark *placemark;
}
#end
-(NSString *)getLocation:(CLLocation *)locations{
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:locations completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
placemark = [placemarks lastObject];
}];
return placemark.name;
}
I could not get that name when call the function but I get that after execution of other parts of code. I know that reverseGeocodeLocation has a completion block, it is handed off to another thread when execution reaches it. But I need to get that name when call the function. I go through many solutions but could not solve my problem.
I want to receive location name in currentLocation.
CLLocation *loc = asset.location;
NSString *currentLocation = [self getLocation:loc];
Where should I change my code. Please help.
This is because you are returning value before you get it in completion handler of reverseGeocodeLocation. Now, you can't return the value from any completion block so what you need to do is create your own completionblock and call it when you get your result something like,
-(void)getLocation:(CLLocation *)locations withcompletionHandler : (void(^)(NSString *name))completionHandler{
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:locations completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
placemark = [placemarks lastObject];
completionHandler(placemark.name);
}];
}
and call it like,
[self getLocation:location withcompletionHandler:^(NSString *name) {
NSLog(#"your name : %#",name); // or do whatever you want with name!
}];
Update :
If you want whole array in completion handler as you have mentioned in comment then your method should be like,
-(void)getLocation:(CLLocation *)locations withcompletionHandler : (void(^)(NSArray *arr))completionHandler{
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:locations completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks,
NSError * _Nullable error) {
// placemark = [placemarks lastObject];
completionHandler(placemarks);
}];
}
and you can call it like,
[self getLocation:location withcompletionHandler:^(NSArray *arr) {
// you will get whole array here in `arr`, now you can manipulate it according to requirement
NSLog(#"your response array : %#",arr);
CLPlacemark *placemark = [arr lastObject];
}];
The method reverseGeocodeLocation of class CLGeocoder runs asynchronously hence you will not get the location data as a return value from the called function. It uses the completion handler where the location data is returned and you can use that completion handler block to update the UI or any further processing of your location data.
Get city,state and address you have to pass latitude and longitude,
so pass latitude and longitude will pass to geocoder block and
within block you will placemarks array.
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocation *myLocation = [[CLLocation alloc]initWithLatitude:23.0225
longitude:72.5714];
[geocoder reverseGeocodeLocation:myLocation
completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"Geocode failed with error: %#", error);
return;
}
if (placemarks && placemarks.count > 0)
{
CLPlacemark *placemark = placemarks[0];
NSDictionary *addressDictionary =
placemark.addressDictionary;
NSLog(#"%# ", addressDictionary);
NSString *address = [addressDictionary
objectForKey:(NSString *)kABPersonAddressStreetKey];
NSString *city = [addressDictionary
objectForKey:(NSString *)kABPersonAddressCityKey];
NSString *state = [addressDictionary
objectForKey:(NSString *)kABPersonAddressStateKey];
NSLog(#"%# %# %# %#", address,city, state);
}
}];
Related
I'm using the below body of code to find the distances between a logged in user and a list of other users in my app's database.
ViewController.h
#property (nonatomic) CLLocationDistance kilometers;
ViewController.m
NSString *myLocation = self.currentUser[0][#"street_address"];
NSLog(#"MY LOCATION %#", myLocation);
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:myLocation completionHandler:^(NSArray* placemarks2, NSError* error){
for (CLPlacemark* aPlacemark in placemarks2)
{
for (NSMutableDictionary *multiplelocationsFriend in self.closeByNeighbours) {
NSString *location = [multiplelocationsFriend objectForKey:#"address"];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
self.endLocation = [[CLLocation alloc] initWithLatitude:placemark.location.coordinate.latitude longitude:placemark.location.coordinate.longitude] ;
NSString *myLocation = self.currentUser[0][#"street_address"];
NSLog(#"MY LOCATION %#", myLocation);
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:myLocation completionHandler:^(NSArray* placemarks2, NSError* error){
for (CLPlacemark* aPlacemark in placemarks2)
{
self.startLocation = [[CLLocation alloc] initWithLatitude:aPlacemark.location.coordinate.latitude longitude:aPlacemark.location.coordinate.longitude] ;
self.kilometers = [self.startLocation distanceFromLocation:self.endLocation] / 1000;
}
}];
}
}
];
}
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.neighboursView reloadData];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
Is there any way to add self.kilometers into an NSMutableArray? When I try doing it, XCode throws me an error saying:
Sending 'CLLocationDistance' (aka 'double') to parameter of
incompatible type 'id _Nonnull'
You have to convert it to an NSNumber.
NSMutableArray *a = [NSMutableArray new];
[a addObject:[NSNumber numberWithDouble:self.kilometers]];
The entries in an NSArray or NSMutableArray must all be object pointers. nil pointers and non-object types (e.g. int, double, CGPoint) are not allowed.
To put a placeholder into a mutable array, use the NSNull class
(instead of trying to put a nil into the array).
To put things like CGPoint into a mutable array, use the NSValue
class.
To put primitive types like int and double into a mutable array,
use the NSNumber class.
So in your case, since CLLocationDistance is actually a double, you need to encapsulate the distance in a NSNumber object, and then add the NSNumber object to the array. To retrieve the distance, take the NSNumber object from the array, and extract the double.
Here's an example that shows the whole process:
// create a mutable array
NSMutableArray *array = [NSMutableArray new];
// encapsulate a CLLocationDistance (actually a 'double') in a NSNumber, and add the NSNumber to the array
CLLocationDistance distance1 = 2.1;
NSNumber *number1 = [NSNumber numberWithDouble:distance1];
[array addObject:number1];
NSLog(#"%#", array);
// get the NSNumber from the array and unpack the 'double' value
NSNumber *number2 = [array firstObject];
CLLocationDistance distance2 = [number2 doubleValue];
NSLog(#"the distance is %lf", distance2);
Here's the output:
2019-08-28 14:02:32.197 TestSomething[1195:476743] (
"2.1"
)
2019-08-28 14:02:32.198 TestSomething[1195:476743] the distance is 2.100000
I am very new to xcode please guide me to correct the error, My code is,
//Set our mapView
[MapViewC setRegion:myRegion animated:NO];
CLLocation *someLocation=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:someLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSDictionary *dictionary = [[placemarks objectAtIndex:0] addressDictionary];
addressOutlet=[dictionary valueForKey:#"Street"];
City=[dictionary valueForKey:#"City"];
State=[dictionary valueForKey:#"State"];
if (addressOutlet!=NULL&&City!=NULL)
{
NSString *SubTitle=[NSString stringWithFormat:#"%#,%#,%#",addressOutlet,City,State];
cell.detailTextLabel.text=SubTitle;
}
else if (addressOutlet==NULL&&City!=NULL)
{
NSString *SubTitle=[NSString stringWithFormat:#"%#,%#,",City,State];
cell.detailTextLabel.text=SubTitle;
}
else if (addressOutlet!=NULL&&City==NULL)
{
NSString *SubTitle=[NSString stringWithFormat:#"%#,%#,",addressOutlet,State];
cell.detailTextLabel.text=SubTitle;
}
else if(addressOutlet==NULL&&City==NULL)
{
NSString *SubTitle=[NSString stringWithFormat:#"%#",State];
cell.detailTextLabel.text=SubTitle;
}
}];
Thanks in advance.
OK an array can be empty, in which case the following statement will cause an exception:
NSDictionary *dictionary = [[placemarks objectAtIndex:0] addressDictionary];
// ^
So guard against it, with something like:
if ([placemarks count] > 0) {
NSDictionary *dictionary = [[placemarks objectAtIndex:0] addressDictionary];
....
}
I haven't read the API docs for that completion handler, but you can probably test if error is non-nil and act accordingly (i.e. if error != nil respond to error, else process the placemarks array).
I have a feeling that my problem here is really with blocking, but maybe it's something else too. I am trying to forward geocode an address and place the coordinates into an array to use later.
An exception is raised down at the bottom when I try to call on one of the objects I tried added to the array in the block. The exception also gets raised before any of the NSLogs ever print out within the block text.
What's the proper way to handle this? Thanks.
- (NSMutableArray *)convertAddressToGeocode:(NSString *)addressString
{
//return array with lat/lng
__block NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:addressString
completionHandler:^ (NSArray* placemarks, NSError* error) {
for (CLPlacemark* aPlacemark in placemarks)
{
// Process the placemark.
if (error){
NSLog(#"Geocode failed with error: %#", error);
[self displayError:error];
return;
}
else {
NSArray const *keys = #[#"coordinate.latitude",
#"coordinate.longitude",
#"altitude",
#"horizontalAccuracy",
#"verticalAccuracy",
#"course",
#"speed",
#"timestamp"];
NSString *keylat = keys[0];
NSString *keylng = keys[1];
if (aPlacemark.location == nil)
{
NSLog(#"location is nil.");
}
else if ([keylat isEqualToString:#"coordinate.latitude"] && [keylng isEqualToString:#"coordinate.longitude"])
{
NSString *lat = #"";
NSString *lng = #"";
lat = [self displayStringForDouble: [aPlacemark.location coordinate].latitude];
lng = [self displayStringForDouble: [aPlacemark.location coordinate].longitude];
NSLog(#"This never gets executed"): //THIS NEVER GETS EXECUTED
[coordinates addObject:[NSString stringWithFormat:#"%#",lat]];
[coordinates addObject:[NSString stringWithFormat:#"%#",lng]];
}}}}];
NSLog(#"Array: %#", coordinates[0]); //EXCEPTION RAISED HERE, Nothing ever gets added
return coordinates;
}
Here is the code this method is supposed to be plugged into, but I'm not getting the coordinates out of convertAddresstoGeocode to pass to convertCoordinatestoRepModel:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSString *addressToSearch = self.addressSearchField.text;
NSMutableArray *coordinateArray = [self convertAddressToGeocode:addressToSearch];
NSMutableArray *repModelArray = [self convertCoordinatesToRepModel:coordinateArray];
...
}
if geocodeAddressString is async operation than your block will be performed after
NSLog(#"Array: %#", coordinates[0]);
also, after call of your method ends (when event already handled) the coordinates array
released (it is because of __block modifier - blocks do not retain objects with __block modifier), and in your block you try to use dealloced coordinates array.
Once again:
Your block will be called after NSLog(#"Array: %#", coordinates[0]);
f.e.:
Remove NSLog(#"Array: %#", coordinates[0]); - it is normal that in that moment array is empty.
Store your coordinates array in some #property , you can release it after using in block
UPDATE:
in .h file
typedef void (^ConverteArrayCallback)(NSArray *coordinates);
under #intrerface
- (void)convertAddressToGeocode:(NSString *)addressString callback:(ConverteArrayCallback) callback;
in .m file
- (void)convertAddressToGeocode:(NSString *)addressString callback:(ConverteArrayCallback) callback {
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:addressString
completionHandler:^ (NSArray* placemarks, NSError* error) {
for (CLPlacemark* aPlacemark in placemarks)
{
// Process the placemark.
if (error){
NSLog(#"Geocode failed with error: %#", error);
[self displayError:error];
return;
}
else {
NSArray const *keys = #[#"coordinate.latitude",
#"coordinate.longitude",
#"altitude",
#"horizontalAccuracy",
#"verticalAccuracy",
#"course",
#"speed",
#"timestamp"];
NSString *keylat = keys[0];
NSString *keylng = keys[1];
if (aPlacemark.location == nil)
{
NSLog(#"location is nil.");
}
else if ([keylat isEqualToString:#"coordinate.latitude"] && [keylng isEqualToString:#"coordinate.longitude"])
{
NSString *lat = #"";
NSString *lng = #"";
lat = [self displayStringForDouble: [aPlacemark.location coordinate].latitude];
lng = [self displayStringForDouble: [aPlacemark.location coordinate].longitude];
NSLog(#"This never gets executed"): //THIS NEVER GETS EXECUTED
[coordinates addObject:[NSString stringWithFormat:#"%#",lat]];
[coordinates addObject:[NSString s
tringWithFormat:#"%#",lng]];
}}}
if (callback != NULL) {
callback(coordinates);
}
}];
}
That should works!
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSString *addressToSearch = self.addressSearchField.text;
[self convertAddressToGeocode:addressToSearch callback:^(NSArray *coordinates)
{
self.textView.text = [coordinates objectAtIndex:0];
}];
}
__block NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];
The problem is here; just replace the above code with:
NSMutableArray *coordinates = [[NSMutableArray alloc] init];
At the moment, I have a place dictionary that contains values for all the different parts of a typical address, that I then pass through a geocoder. It looks like this:
[self.placeDictionary setValue:#"166 Bovet Rd" forKey:#"Street"];
[self.placeDictionary setValue:#"San Mateo" forKey:#"City"];
[self.placeDictionary setValue:#"CA" forKey:#"State"];
[self.placeDictionary setValue:#"94402" forKey:#"ZIP"];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
PFGeoPoint* userLocation = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
NSLog(#"%f,%f", userLocation.latitude, userLocation.longitude);
} else {
NSLog(#"location error");
return;
}
}];
Instead of having a separate dictionary entry for each individual part of the address, could I merge them into one string to pass through the geocoder? Something of this effect:
[self.placeDictionary setValue:#"166 Bovet Rd San Mateo CA 94402" forKey:#"Address"];
The reason I want to do this is I have a search bar in which a user is supposed to enter a location into for geocoding, and I can't divide it and extract each individual part of the address, so is there a way that I can pass the entire address like so, for geocoding?
edit: I've tried the following code and the terminal prints "location error":
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
[self.placeDictionary setValue:#"166 Bovet Rd San Mateo CA 94402" forKey:#"Address"];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
PFGeoPoint* userLocation = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
NSLog(#"%f,%f", userLocation.latitude, userLocation.longitude);
} else {
NSLog(#"location error");
return;
}
}];
[self.view endEditing:YES];
}
After taking four values for keys Street, City, State & Zip you can create an additional key manually and store complete address in it otherwise you can store all these four values in four different strings and then save them collectively for any key in dictionary.
I figured it out. I simply used the method geocodeAddressString instead.
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
NSString* address = [NSString stringWithFormat:#"166 Bovet Road San Mateo CA 94402"];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
PFGeoPoint* userLocation = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
NSLog(#"%f,%f", userLocation.latitude, userLocation.longitude);
} else {
NSLog(#"location error");
return;
}
}];
[self.view endEditing:YES];
}
I have a geocoder method and I want it to return the CLLocationCoordinate2D that it generates for me.
- (CLLocationCoordinate2D)geocode{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(0,0);
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
coordinate = location.coordinate;
} else {
NSLog(#"error");
}
}];
return coordinate;
}
The line coordinate = location.coordinate produces an error however. XCode says coordinate is an unassignable variable. Anyone see what I'm doing wrong?
update:
After following sebastian's advice, I got the code to compile, however, coordinate is not being properly set. If you take a look at both of the NSLog statements i put in the method, the first one prints out the correct coordinates that I need assigned to coordinate, however as soon as the if statement exits, coordinate goes back to being set to (0,0). The second NSLog statement prints (0,0). Anyone know how I can fix this?
- (CLLocationCoordinate2D)geocode{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
__block CLLocationCoordinate2D geocodedCoordinate = CLLocationCoordinate2DMake(0,0);
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
geocodedCoordinate = location.coordinate;
NSLog(#"%f, %f", coordinate.longitude, coordinate.latitude);
} else {
NSLog(#"error");
}
}];
NSLog(#"%f, %f", coordinate.longitude, coordinate.latitude);
return coordinate;
}
You have to use the __block keyword if you want to assign to a variable that was defined outside the block's scope:
__block CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(0,0);
Have a look at the Blocks and Variables section of Apple's Block Programming Topics
About the variable not getting set when it compiles:
geocodeAddressDictionary:completionHandler: runs asynchronously. That means it returns immediately but the block gets executed later, when the results are available. You need to change the way you call your methods. At the moment you are probably doing something like this
self.myCoordinate = [self geocode];
What you have to do is more like this:
- (void)geocode{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
self.myCoordinate = location.coordinate;
NSLog(#"%f, %f", coordinate.longitude, coordinate.latitude);
} else {
NSLog(#"error");
}
}];
}
Running [self geocode] returns immediately and myCoordinate will be set when the block runs.
If you are doing it that way, note that this could lead to a reference cycle, because self is being retained by the block.