I am trying to store a String Value inside the completion handler but its scope is restricted to that block only. How to resolve it ?
// Do any additional setup after loading the view, typically from a nib.
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[locationManager startUpdatingLocation];
CLGeocoder *geocoder = [[CLGeocoder alloc] init] ;
NSString *co;
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"placemark.country %#",placemark.country);
co = placemark.country;
//
}];
NSLog(#"%#",co);
At this line the value of co becomes null again. Please let me know, how can retain the value outside the completion handler, which I store inside completion handler.
The issue isn't a matter of scope, it's that the log is called before the completion block. The reverse geocode call is asynchronous. It will return the block whenever it's done with what it's doing, but in the meantime, the rest of your method will execute. If you print co the line after you set its value but within the completion block, it will display the correct value.
Example:
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"placemark.country %#",placemark.country);
co = placemark.country;
// The completion block has returned and co has been set. The value equals placemark.country
NSLog(#"%#",co);
}];
// This log is executed before the completion handler. co has not yet been set, the value is nil
NSLog(#"%#",co);
If you need to use the co variable outside of the block, you should call the methods it will be used in from within the completion block:
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
[self myMethodWithCountry:placemark.country];
}];
- (void)myMethodWithCountry:(NSString *)country {
// country == placemark.country
}
The NSLog command you wrote will work before you complete the block. Since this happens you will get null. One thing you can do is print the value of co inside the block instead of doing it outside.
OR
change the declaration of co as follows:
__block NSString *co= nil;
As Julie suggested, add __block before NSString *co; i.e. __block NSString *co;. That's two underscores btw.
Related
In my application I want the exact lattitude and longitude of given address using forward geocoding in IOS in Objective-C.
I had used forward geocoding in my application but it is not giving the exact address. this is my code of forward geocoding
-(void)detectlocation:(NSString*)address
{
CLGeocoder *geocoder=[[CLGeocoder alloc]init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error)
{
if(!error)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"%f",placemark.location.coordinate.latitude);
NSLog(#"%f",placemark.location.coordinate.longitude);
self.latitude4=placemark.location.coordinate.latitude;
self.longitude4=placemark.location.coordinate.longitude;
NSLog(#"%#",[NSString stringWithFormat:#"%#",[placemark description]]);
}
}];
}
Thanks In Advance
You can access placemark properties for a more accurate location.
This is swift, but the same is for objective-c
Declare and initiate your CLLocationMager* inside the viewDidLoad method.
I recommend to use:
#property YourCustomLocation* foundedLocation;
in order to "save" data of the place that has been found.
Then try this inside your method:
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
//if 1+ "places" have been found:
if ([placemarks count] > 0) {
//save the first place that has been found
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//save location from the placemark
CLLocation *location = placemark.location;
//save coordinates from location
CLLocationCoordinate2D coordinate = location.coordinate;
//Do more stuffs...like:
self->_foundedLocation = [[YouCustomLocation alloc]initLocationWithLatitude:coordinate.latitude Longitude:coordinate.longitude];}
}];
Remember that forward-geocoding works with blocks, so unless you use __block directive near the variable you won't be able to "save the location" on a variable declared outside the block.
Hai I am writing the code inside the block to get the placemarks of the vehicle. My problem is I stored the placemarks inside the NSMutablearray but i can't access the array outside the block. kindly advice me to overcome this problem. Thanks in advance. My code is below...
CLLocation *someLocation=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:someLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if ([placemarks count] > 0) {
NSDictionary *dictionary = [[placemarks objectAtIndex:0] addressDictionary];
addressOutlet=[dictionary valueForKey:#"Street"];
City=[dictionary valueForKey:#"City"];
State=[dictionary valueForKey:#"State"];
if (addressOutlet!=NULL&&City!=NULL)
{
SubTitle=[NSString stringWithFormat:#"%#,%#,%#",addressOutlet,City,State];
}
else if (addressOutlet==NULL&&City!=NULL)
{
SubTitle=[NSString stringWithFormat:#"%#,%#",City,State];
}
else if (addressOutlet!=NULL&&City==NULL)
{
SubTitle=[NSString stringWithFormat:#"%#,%#",addressOutlet,State];
}
else if(addressOutlet==NULL&&City==NULL&&State!=NULL)
{
SubTitle=[NSString stringWithFormat:#"%#",State];
}
else if (addressOutlet==NULL&&City==NULL&&State==NULL)
{
SubTitle=[NSString stringWithFormat:#"%#",#""];
}
[storeArray addObject:SubTitle];
}
NSLog(#"%#",storeArray);// I can access here
}];
NSLog(#"%#",storeArray);// Here it shows empty array
}
What you should know is the execution-flow.
In ancient programs, the code execution flown always from top to bottom. Anyway, this premise broken a long time ago. Modern programs are built with several components, such as function, class, and blocks(closure), and the program does not always flow top to bottom. (well, though it mostly does)
A block(closure) is one of this non-sequential program. You save a code block into a variable to execute it later. The point is, the code block is not being executed when it is being defined.
What you did here is:
Make an empty array.
Create and pass a code block which fills the array into a method.
Print the array using NSLog.
At the point of #2, the code block is not executed immediately, but will be executed when the operation will be completed. Then, the array is not yet filled when you printed it, then it prints just an empty array.
You should treat the code block defined at #2 will be executed at unknown future point of time, and you really can't control the timing. This is a bad point of asynchronous programming, and something you need to be familiar with.
This is due to block.
The Array you collected is on asynch executed some time after the process happens.
But the outer log gets executed before the completion and collection of data and hence it returns no value and doesn't print anything
[geocoder reverseGeocodeLocation:someLocation completionHandler:^(NSArray *placemarks, NSError *error) {
//Some process collecting values in array
[storeArray addObject:SubTitle];
}
NSLog(#"%#",storeArray);// I can access here
//Do everything here after data gets populated
[tableview reloadData];
}];
//Here no values exist since execuion of this happens before the inner block execution
}
That's because reverseGeocodeLocation:completionHandler method is executed asynchronously, and in your case the second NSLog get called earlier then the first one which is in completion block.
Check this: Obj-C blocks
---------EDIT-----------
You must continue your logic in completion block (i.e. when you call NSLog at first time).
-(void)downloadLocations:(void(^)(void))callback
{
storeArray = [[NSMutableArray alloc] init];
//...
for(int f=0;f<Vehicle_No.count;f++){
//...
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:someLocation completionHandler:^(NSArray *placemarks, NSError *error) {
//...
NSLog(#"%#",storeArray);// I can access here
if (callback) {
callback();
}
}];
}
}
- (void)foo
{
//...
[self downloadLocations:^{
//do something here with storeArray
}];
}
I need to convert my latitude and longitude, received from the server to location. As I user Parse API I don't find that's a good idea to convert them in viewDidLoad, so I decided to make it in cellForRowAtIndexPath.
I tried this approach: Synchronization of concurrent processes execution
Here:
...
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meeting.location.latitude
longitude:meeting.location.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meeting.location.displayName = [placemark.addressDictionary objectForKey:#"Street"];
NSLog(#"%#", meeting.location.displayName);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
cell.subInfo.text = meeting.location.displayName;
return cell;
But it seems to hang endlessly. Where am I wrong?
UPD: I've tried to move all this stuff back to viewDidLoad. In my for loop, where I parse downloaded from Parse.com information I now have:
...
_location.latitude = ((PFGeoPoint *)entry[#"location"]).latitude;
_location.longitude = ((PFGeoPoint *)entry[#"location"]).longitude;
_semaphore = dispatch_semaphore_create(0);
[self reverseGeocode:_location];
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
...
Where _semaphore is #property (strong) dispatch_semaphore_t semaphore;
reverseGeocode::
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meetingLocation.latitude
longitude:meetingLocation.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meetingLocation.displayName = [placemark.addressDictionary objectForKey:#"Street"];
dispatch_semaphore_signal(_semaphore);
}];
Now it seems to hang, but I have no idea where, because it doesn't even enter the geocoded completionHandler block.
You tried to get the value for meeting.location.displayName in the block, and then set cell.subInfo.text with the value of meeting.location.displayName after the block. The problem will be the line cell.subInfo.text = meeting.location.displayName; can be executed before the block is returned. You can try to put the line cell.subInfo.text = meeting.location.displayName; inside the block.
But I don't see why you want to reverse geolocation in this method, the reverse function may be called every time when the user scroll to particular if you don't set the condition when to do reversion. If viewDidLoad is not a good idea for your design, try viewWillAppear.
I am trying to find a user's location from a coordinate to save into my database.
To find the location name I am using reverseGeocode. However since it is a block method my self.locationName will return (and save as nil) into the database. So I have tried to find a solution to the problem, and tried to put together the following solution using semaphores to try and block until I get a locationName I can save, but the app just hangs when the save button is pressed. Should I even be going about this problem in this way or is there a better way?
dispatch_semaphore_t semaphore;
- (void)reverseGeocode:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Finding address");
if (error) {
NSLog(#"Error %#", error.description);
} else {
CLPlacemark *placemark = [placemarks lastObject];
self.locationName = [NSString stringWithFormat:#"%#", ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO)];
dispatch_semaphore_signal(semaphore);
}
}];
}
-(NSString *)findLocation:(CLLocation *)startingLocation
{
semaphore = dispatch_semaphore_create(0);
[self reverseGeocode:startingLocation];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //should maybe timeout
return self.locationName;
}
You are thinking about this all wrong. That is not how asynchronous code works. Do NOT block until the code returns. Just launch the code to start the reverse geocode, and finish up. Now when the reverse geocode finishes, it calls you back and you can do whatever you want with the returned info. That is the whole point of the completion handler: it doesn't run until the reverse geocode has completed.
Just get rid of the semaphores and let things happen asynchronously. Here is a complete example without the secondary method:
CLLocation* loc = userLocation.location;
[geo reverseGeocodeLocation:loc
completionHandler:^(NSArray *placemarks, NSError *error)
{
if (placemarks) {
CLPlacemark* p = [placemarks objectAtIndex:0];
NSLog(#"%#", p.addressDictionary); // do something with address
}
}];
As you've already been told, if you really want to call this from another method and then do something further, then pass a block to this method and call the block inside the completion handler. That means the block you passed is code that will run when the geocoding has completed, which is exactly what you want - without semaphores and without freezing the app.
Freezing the app is bad form and the WatchDog will kill your app dead if you do it for too long. Just don't do it.
In the following block of code, the first method calls the second method, which should return the results of a geocoding process:
- (void)foo {
CLPlacemark *currentMark = [self reverseGeocodeLocation:locationManager.location];
}
- (CLPlacemark *)reverseGeocodeLocation:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
__block CLPlacemark *placeMark;
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if (!error) {
if ([placemarks firstObject])
placeMark = [placemarks firstObject];
}
}];
return placeMark;
}
However, since the program's execution, doesn't wait for the geocoding to complete before continuing (hence the completion block), there is always a danger that the placeMark variable will be returned uninstantiated before the geocoding process finishes and the completion block is called. I've faced the same predicament when making HTTP requests to web services whose results will not return for an indeterminate amount of time.
The only solution I've seen thus far is to nest the all the code from foo in the geocoder's completion block, which quickly becomes very ugly and difficult to maintain.
What is the best way for the currentMark variable in foo to be set to the result of the second method's completion block without nesting it in the block?
Instead of having the function return a value, just add a callback block to return the value.
Try this:
- (void)reverseGeocodeLocation:(CLLocation *)location withCallback:(void(^)(CLPlacemark *resultPlacemark, NSError *error))_block {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
_block([placemark objectAtIndex:0], error);
}];
}
and then foo would be
- (void)foo {
__block CLPlacemark *currentMark;
[self reverseGeocodeLocation:(CLLocation *)location withCallback:(CLPlacemark *mark, NSError *error) {
currentMark = mark;
}];
}
It seems that the best way to handle this issue overall is to use delegates. That way, the flow of the program isn't obstructed by waiting an indeterminate amount of time for the completion block to return.
Here's a short example of what I've decided on:
- (void)reverseGeocodeLocation:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if (!error) {
if ([placemarks firstObject])
[delegate performSelectorOnMainThread:#selector(done:) withObject:[placemarks firstObject];
}
}];
return placeMark;
}
In hindsight, this actually seems rather elegant. The control flow of the main thread (i.e. UI presentation), isn't hindered in any way, and the view controller querying data can essentially be "notified" that data has loaded, rather than directly demanding a returned value.