saveEventually - Save when application is minimised with Parse - ios

I am using saveEventually in the section of code below. The issue is, saveEventually doesn't work when the application is minimised and reopened, only when the application is completely closed and reopened. Is there any way to counteract this?
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// Generate Random Number
int random = arc4random_uniform(1000000000);
// Updates Location
NSLog(#"Location: %#",newLocation);
CLLocation *curentLocation = newLocation;
if (curentLocation != nil) {
// Submit through Parse
PFObject *object = [PFObject objectWithClassName:#"Issue"];
object[#"ID"] = [NSString stringWithFormat:#"%i",random];
object[#"Latitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
object[#"Longitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
object[#"Signal"] = [NSString stringWithFormat:#"%#",_avgNumber];
[object saveEventually];
// Update Text Fields
self.latitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
self.longitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
}
// Stop the Location Update
[manager stopUpdatingLocation];
}

saveEventually will work under both of the conditions you described. From Parse: "Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse.
They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is available."
It should work, but as a temporary workaround if it's not, you could check for connectivity before saving the object. If you have a internet connection, use saveInBackground, if not use saveEventually. So something like this:
if (isNetworkAvailable)
[object saveInBackground];
else
[object saveEventually];
- (BOOL)isNetworkAvailable
{
CFNetDiagnosticRef dReference;
dReference = CFNetDiagnosticCreateWithURL (NULL, (__bridge CFURLRef)[NSURL URLWithString:#"www.apple.com"]);
CFNetDiagnosticStatus status;
status = CFNetDiagnosticCopyNetworkStatusPassively (dReference, NULL);
CFRelease (dReference);
if ( status == kCFNetDiagnosticConnectionUp )
{
NSLog (#"Connection is Available");
return YES;
}
else
{
NSLog (#"Connection is down");
return NO;
}
}

Related

How to get current location at the instant UIImagePickerController captures an image?

I have researched on how to get location data from images returned from UIImagePickerController camera. However, I think that the easiest way is to get the current location from CLLocationManager at the instant UIImagePickerController captures an image.
Is there a way of doing this? Is there a way of listening for the "capturePhoto" event, for example?
Just to clarify, the users using my app will likely be moving pretty fast.
Here's what I'd recommend so you don't track the user's location any more than you have to and so you get the user's location closest to the time the image was actually snapped.
Instantiate the CLLocationManager class variable in your viewDidLoad, ex:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
And make sure it's authorized:
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
[self.locationManager requestWhenInUseAuthorization];
}
(Also include the "NSLocationWhenInUseUsageDescription" key in the .plist)
Then you could wait until the UIImagePickerController is actually presented before (1) initializing the dictionary to hold the locations and (2) starting to update the location, ex:
[self presentViewController:self.imagePicker animated:YES completion:nil];
self.locationDictionary = [[NSMutableDictionary alloc] init];
[self.locationManager startUpdatingLocation];
At that point, you can start storing the user's updated locations in an NSMutableDictionary self.locationDictionary class instance variable when CLLocation values are returned from the didUpdateToLocation delegate method, ex:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// Format the current date time to match the format of
// the photo's metadata timestamp string
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"YYYY:MM:dd HH:mm:ss"];
NSString *stringFromDate = [formatter stringFromDate:[NSDate date]];
// Add the location as a value in the location NSMutableDictionary
// while using the formatted current datetime as its key
[self.locationDictionary setValue:newLocation forKey:stringFromDate];
}
And then once the image is selected, find its timestamp in the metadata and find the value in the location dictionary with a timestamp key closest to the image timestamp, ex:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self.locationManager stopUpdatingLocation];
// When a photo is selected save it as a UIImage
self.selectedPhoto = info[UIImagePickerControllerOriginalImage];
// Get the timestamp from the metadata and store it as an NSString
self.selectedPhotoDateTime = [[[info valueForKey:UIImagePickerControllerMediaMetadata] objectForKey:#"{Exif}"] objectForKey:#"DateTimeOriginal"];
// If the CLLocationManager is in fact authorized
// and locations have been found...
if (self.locationDictionary.allKeys.count > 0) {
// Sort the location dictionary timestamps in ascending order
NSArray *sortedKeys = [[self.locationDictionary allKeys] sortedArrayUsingSelector: #selector(compare:)];
// As a default, set the selected photo's CLLocation class
// variable to contain the first value in the sorted dictionary
self.selectedPhotoLocation = [self.locationDictionary objectForKey:[sortedKeys objectAtIndex:0]];
// Then go through the location dictionary and set the
// photo location to whatever value in the dictionary
// has a key containing a time most closely before
// the image timestamp. Note that the keys can be compared
// as strings since they're formatted in descending order --
// year to month to day to hour to minute to second.
for (NSString *key in sortedKeys) {
// If the photo's metadata timestamp is less than or equal to
// the current key, set the selected photo's location class
// variable to contain the CLLocation value associated with the key
if ([self.selectedPhotoDateTime compare:key] != NSOrderedAscending) {
self.selectedPhotoLocation = [self.locationDictionary objectForKey:key];
}
// Else if the time in the location dictionary is past
// the photo's timestamp, break from the loop
else {
break;
}
}
}
[self dismissViewControllerAnimated:YES completion:nil];
}
In your .h file you need to add the following to your code:
#interface TakePhotoViewController : UIViewController <CLLocationManagerDelegate>
In your .m file you need the following.
#interface TakePhotoViewController (){
//location stuff
CLLocationManager * manager;
CLGeocoder *geocoder;
CLPlacemark * placemark;
}
The above code set's up relevant references to find your location.
Next add this to your viewDidLoad method:
manager = [[CLLocationManager alloc] init];
geocoder = [[CLGeocoder alloc] init];
This initialises the Location Manager and Geocoder.
Then in your code that either initiates taking a picture or returns the picture to a view use this:
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
[manager startUpdatingLocation];
To stop the continuous updating of the location add this to your imagePickerReturn method:
[manager stopUpdatingLocation];
As soon as you stop updating the location you will be saving the very last location that was updated. The location updates once every .5 - 1 second, so even if you are moving or have the camera open for a long time it will only store the location of whatever image you pick. To save the date (which includes time down to milliseconds) use:
NSDate * yourCoreDataDateName = [NSDate date];
For good coding practice to handle any errors you will need this:
//handles the error if location unavailable
- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
NSLog(#"Error: %#", error);
NSLog(#"Failed to get location... :-(");
}
- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
NSLog(#"Location: %#", newLocation);
CLLocation * currentLocation = newLocation;
self.locationTF.text = #"Finding Location...";
if (currentLocation != nil){
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error == nil && [placemarks count] > 0){
placemark = [placemarks lastObject];
//the code below translates the coordinates into readable text of the location
self.locationLabel.text = [NSString stringWithFormat:#"%# %# , %#, %#, %# %#", placemark.subThoroughfare, placemark.thoroughfare, placemark.locality, placemark.administrativeArea, placemark.country, placemark.postalCode];
}
else{
NSLog(#"%#", error.debugDescription);
self.locationLabel.text = #"Failed to find location";
}
}];
}
}
I must warn you that iOS8 will throw NO error when it can't find a location because it needs you as the programmer to add an alert view to authorise getting locations. See this tutorial on how to overcome this issue:
http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/
I got this tutorial from the most popular asked question about the new error.
The most accurate way to do this would be through the exif metadata. Have a look at this post by Ole Begemann on how to do this.
UPDATE
It seems like Apple doesn't include the location to the metadata to images taken with the Camera from the UIImagePicker.
Another option to get the location in which the image was taken would be to use a custom overlay view for the UIImagePicker and get the location when the takePicture method is called. This would probably achieve the best result.

Wait until there is mobile internet to post data in Objective-C

I am rather new to Objective-C development so please be patient!
I am trying to take data from a user & send that information to an external database. I have managed to work out how to push the data rather easily, but the issue is; due to the nature of my application, there is a high probability that the user will have no mobile connectivity while they're using it. How can I continuously check that the user has mobile connectivity, and then send the data when it's connected? My code for the action is below:
(Just to clarify, the action takes 10 readings of signal over 5 seconds, appends them to an array, calculates the average and the updates the location. In turn, the locationManager sends the data to a cloud service including the average signal reading.
- (IBAction)buttonPressed:(id)sender {
// Make Manager Delegate & Set Accuracy
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
// Call Timer
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(arrayBuild)
userInfo:nil
repeats:YES];
// Initialise Array
resultsArray = [[NSMutableArray alloc]init];
}
#pragma mark CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Error: %#", error);
NSLog(#"Failed to get location!");
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"Location: %#",newLocation);
if (curentLocation != nil) {
// Code below uses a third party utility to submit data
PFObject *Object = [PFObject objectWithClassName:#"Issue"];
Object[#"Latitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
Object[#"Longitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
Object[#"Signal"] = [NSString stringWithFormat:#"%#",_avgNumber];
[Object saveInBackground];
// Update Text Fields
self.latitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
self.longitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
}
// Stop the Location Update
[manager stopUpdatingLocation];
}
- (void)arrayBuild {
loopCount++;
if (loopCount >= 11) {
// Find Average
NSNumber *avg = [resultsArray valueForKeyPath:#"#avg.self"];
_avgNumber = avg;
// Change Text of Label to Average & Log
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
NSLog(#"%#",_avgNumber);
// Update Location
[manager startUpdatingLocation];
// Invalidate Timer
[myTimer invalidate];
myTimer = nil;
}else{
// Declare Signal Strength
float signalstrength = CTGetSignalStrength();
// Individual Result & Convert to Integer
NSString *result = [NSString stringWithFormat:#"%f", signalstrength];
NSInteger resultInt = [result integerValue];
// Add Object
[resultsArray addObject:[NSNumber numberWithFloat:resultInt]];
// Change Text of Label Each Second
self.signal.text = [NSString stringWithFormat:#"%d",loopCount];
NSLog(#"%f",signalstrength);
}
}
What you need is called "Network Reachability Monitoring". Once you subscribe to it, your will be notified when there are changes to network connectivity state, i.e. device became online or offline, and even type of current connection (WLAN or WWAN).
There is a sample project from Apple and third-party networking libraries (such as AFNetworking) often provide a convenience class for better experience.
EDIT:: Easier solution is to is to use Parse SDK and their saveEventually method instead of saveInBackground. This will, according to documentation, take care of situations when network is not accessible.
There are many question on forum addressing this issue. Apple provides Reachability class for this purpose. You may check This or this question for further clarification.

How do I call a method when the iPhone has signal in Objective-C?

Due to the nature of my application (tracks signal) there is high probability that the application will be utilised when the device currently has no cellular signal.
How can I ensure that the method below will run once the phone recognises that it has signal?
I would still like the data to be posted but with no further interaction from the user after the initial action press that calls the method.
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"Location: %#",newLocation);
CLLocation *curentLocation = newLocation;
if (curentLocation != nil) {
// Code below uses a third party utility to submit data (Parse framework)
PFObject *Object = [PFObject objectWithClassName:#"Issue"];
Object[#"Latitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
Object[#"Longitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
Object[#"Signal"] = [NSString stringWithFormat:#"%#",_avgNumber];
[Object saveInBackground];
// Update Text Fields
self.latitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
self.longitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
}
// Stop the Location Update
[manager stopUpdatingLocation];
}

Objective C location service geocode return values

I am trying to write a method that gets geocoding and returns the place mark as the methods return. I get the location manager portion; it waits to find an location, then runs stop updating. Then I run the geocoding and that is where I have the most questions. First what does that [] block notation mean? I can't seem to return anything out of that ever. So it gets the location in the place mark, but I want to pass that location info back out.
My goal is to have to buttons on a view, one that starts finding the location and writes the starting location to a table, and another button that gets the ending location and writes that to a table. I wanted to reuse the block of code that gets the location.
So in pseudo I want the action code for
button A
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Button B
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Coming from Java I'm confused by what that block of reverseGeocodeLocation:currentLocation is and how I can modify it? I envision an object that goes and gets my location and just hands that back to whatever called it to with it what I please.
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
NSLog(#"longitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude]);
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
NSLog(#"latitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude]);
}
// Stop Location Manager
[_locationManager stopUpdatingLocation];
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
_startLocation.text = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country];
NSLog(#"%#", placemark.subThoroughfare); // address number
NSLog(#"%#", placemark.thoroughfare); // address St
NSLog(#"%#", placemark.postalCode); // zip
NSLog(#"%#", placemark.locality); // city
NSLog(#"%#", placemark.administrativeArea); // state
NSLog(#"%#", placemark.country); // country
NSLog(#" debug county == %#", placemark.country); // country
} else {
NSLog(#"%#", error.debugDescription);
//return placemark;
}
//return placemark;
} ];
NSLog(#" needs to log this to see placemerk country == %#", placemark.country);
return placemark; //placemark object?
}
The [] notation in Objective C can mean a few things, but in this case I think you are referring to the method invocation on an object. So [myObject doSomethingWithInt:i]; is the same as myObject.doSomethingWithInt(i); in Java. Apple has a guide to Objctive c here and there are a number of other introductions and tutorials available on the info pages for the iOS and Objective-C tags here on SO.
In terms of responding to button presses, it is very similar to the event model in Java. You will need to establish a link between the buttons on your storyboard or xib and an event in your UIViewController instance. Again, it is best to work through some examples and tutorials, but essentially you
create a class that subclasses UIViewController
Declare IBAction methods in the .h file for your subclass
In the view in your storyboard or XIB file you set the view's controller class to your subclass
Once you add the buttons to the view you can then drag from the "touches up inside" event on the button to your View Controller and link it to the appropriate IBAction method.
Objective-C and iOS use the delegate pattern heavily. The method in your question is one of the delegate methods of the CLLocationmanagerDelegate protocol. You can declare some #property variables in the .h file for the class that contains this method and change it to something like -
In your .h file
#property (retain,nonatomic) CLPlacemark *currentPlacemark;
in your .m file
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
self.currentPlacemark = [placemarks lastObject];
}
}
}
In your viewController subclass .h file
#property (strong,nonatomic) *MyLocationMonitor *locationMonitor;
In your ..m file
- (void) viewDidLoad
{
[super viewDidLoad];
self.locationMonitor=[[MyLocationMonitor alloc]init];
}
to expose the current location and then when the button is clicked you could do something like
-(IBAction)button1Clicked:(id)sender {
CLLocation *currentLocation=self.locationMonitor.currentPlacemark;
// TODO - Do something
}

Reverse geocoding from given coordinates (not current location, but manually provided)

I am fetching GPS information of all my images and they are stored in a Dictionary. I would pass on the lat & long values from this dictionary to the reverseGeocodeLocation function call & store the results in my database.
The problem here is that this function is an asynchronous call & I need to synchronize the whole process for my record to get inserted into the table.
Eg: My array read following coordinates: 32.77003,96.87532. It now calls the reverseGeocodeLocation function, passing on these coordinates as CLLocation object. Now before this async function returns me back the geo-coded location name, next request with new set of coordinates is sent to reverseGeocodeLocation function. This causes inconsistency to insert the record into database.
Is there any way to have this whole task turn synchronous?
i.e Make my for-loop wait until reverseGeocodeLocation returns a value and then move on to next record to be geo-coded?
A bit of my code is here:
for (int imgIdx=0; imgIdx<[imageMetaMutArray count]; imgIdx++)
{
NSDictionary *localGpsDict = [[NSDictionary alloc]initWithDictionary: [imageMetaMutArray objectAtIndex:imgIdx]];
imgLatLoc=[localGpsDict valueForKey:#"Latitude"];
imgLongLoc=[localGpsDict valueForKey:#"Longitude"];
dateStamp=[localGpsDict valueForKey:#"DateStamp"];
timeStamp=[localGpsDict valueForKey:#"TimeStamp"];
if(imgLatLoc && imgLongLoc && dateStamp && timeStamp)
{
CLGeocoder *geoCoder=[[CLGeocoder alloc]init];
CLLocation *currentLocation=[[CLLocation alloc]initWithLatitude:[imgLatLoc doubleValue] longitude:[imgLongLoc doubleValue]];
[geoCoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placeMarks, NSError *err){
if(err)
{
NSLog(#"Reverse geo-coding failed because: %#",err);
return;
}
if(placeMarks && placeMarks.count>0)
{
CLPlacemark *placeMarkers=placeMarks[0];
NSDictionary *locationDictionary=placeMarkers.addressDictionary;
NSString *country=[locationDictionary objectForKey:(NSString *)kABPersonAddressCountryKey];
NSString *city=[locationDictionary objectForKey:(NSString *)kABPersonAddressCityKey];
NSString *state=[locationDictionary objectForKey:(NSString *)kABPersonAddressStateKey];
NSLog(#"logged in from:");
if(city)
{
NSLog(#"city: %#",city);
locName = [[NSString alloc]initWithString:city];
if(state)
{
NSLog(#"state: %#",state);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:state];
}
if(country)
{
NSLog(#"country: %#",country);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:country];
}
}
else
{
if(state)
{
NSLog(#"state: %#",state);
locName = [[NSString alloc]initWithString:state];
if(country)
{
NSLog(#"country: %#",country);
locName=[locName stringByAppendingString:#","];
locName=[locName stringByAppendingString:country];
}
}
else
{
NSLog(#"country: %#",country);
locName = [[NSString alloc]initWithString:country];
}
}
}
else
{
NSLog(#"Placemark Error code:: %lu\n%#",(unsigned long)placeMarks.count,placeMarks);
}
[locName retain];
NSLog(#"location decoded is: %#",locName);
/*Call for Insert into Images table*/
[self insertDataImgTbl:locName];
});
}
}
}
The short answer is that you can't make it synchronous.
What you want to do is move the code that goes on to the next object into the completion block of the reverseGeocodeLocation because that is the soonest that you can submit another reverseGeocodeLocation request. Let me see if I can make some pseudocode here... (that is, I haven't compiled this so it might not be exactly right...)
// in place of the original for loop:
[self reverseGeocodeForIndex:0];
// Doing the reverse geocode is in a method so you can easily call it from within the completion block.
// Maybe your parameter is not the imgIdx value but is instead some object -- I'm just hacking your posted code
// The point is that your completion block has to be able to tell when
// it is done and how to go on to the next object when it is not done.
(void) reverseGeocodeForIndex:(int) imgIdx {
NSDictionary *localGpsDict = [[NSDictionary alloc]initWithDictionary: [imageMetaMutArray objectAtIndex:imgIdx]];
imgLatLoc=[localGpsDict valueForKey:#"Latitude"];
imgLongLoc=[localGpsDict valueForKey:#"Longitude"];
dateStamp=[localGpsDict valueForKey:#"DateStamp"];
timeStamp=[localGpsDict valueForKey:#"TimeStamp"];
if(imgLatLoc && imgLongLoc && dateStamp && timeStamp)
{
CLGeocoder *geoCoder=[[CLGeocoder alloc]init];
CLLocation *currentLocation=[[CLLocation alloc]initWithLatitude:[imgLatLoc doubleValue] longitude:[imgLongLoc doubleValue]];
[geoCoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placeMarks, NSError *err){
// completion block
if(err)
{
// error stuff
}
if(placeMarks && placeMarks.count>0)
{
// what happens when you get some data
}
// now see if we are done and if not do the next object
if (imgIdx<[imageMetaMutArray count])
{
[self reverseGeocodeForIndex:imgIdx+1];
} else {
// Everything is done maybe you want to start something else
}
}];
} else {
// Since you might not reverseGeocode an object you need an else case
// here to move on to the next object.
// Maybe you could be more clever and not duplicate this code.
if (imgIdx<[imageMetaMutArray count])
{
[self reverseGeocodeForIndex:imgIdx+1];
} else {
// Everything is done maybe you want to start something else
}
}
}
And, of course, you can't depend on this being done to do anything else except that you might kick something off when you have reverseGeocoded the last object.
This asynchronous programming can drive you nuts.
An alternative approach could be to place a synchronous request to the following URL, which returns reverse geo-coded results in XML format. You can later parse it, convert to JSON or whatever. The best part: 1) You're force synchronizing the whole process of reverse-geo coding
2) There's no restriction in terms of max requests you can make(I think its 50/min in case of calls to reverseGeocodeLocation handler). If exceeded, you get kCLErrorDomain code 2 error. So we avoid all that by the following approach below. Some sample code that works for me:
-(NSString *)giveMeLocName: (double)gpsLat :(double)gpsLong
{
NSString *finalLocation=[[NSString alloc]init];
//Below is URL we need to send request
NSString *reverseGeoCodeLoc = [NSString
stringWithFormat:#"http://nominatim.openstreetmap.org/reverse?format=xml&zoom=18&addressdetails=1&accept-language=en&lat=%lg&lon=%lg",gpsLat,gpsLong];
NSURL *myLocUrl = [NSURL URLWithString:reverseGeoCodeLoc];
ASIHTTPRequest *myLocRequest = [ASIHTTPRequest requestWithURL:myLocUrl];
[myLocRequest setDidFinishSelector:#selector(reverseGeoCodeImg:)];
[myLocRequest setDelegate:self];
[myLocRequest startSynchronous];
NSLog(#"waiting for location info..");
//Do stuff after receiving results here
}
//This block receives HTTP response as XML(containing reverse geo-coded info. I parse this to JSON using XMLReader class(downloadable)
-(void)reverseGeoCodeImg:(ASIHTTPRequest *)request
{
/*Allocations*/
locDict=[[NSDictionary alloc]init];
revGeoCodePart=[[NSDictionary alloc]init];
addressParts=[[NSDictionary alloc]init];
cityName=[[NSString alloc]init];
stateName=[[NSString alloc]init];
countryName=[[NSString alloc]init];
NSLog(#"starting reverse geo-code!!");
NSString *responseString = [request responseString];
NSError *parseError = nil;
locDict=[XMLReader dictionaryForXMLString:responseString error:&parseError];
[locDict retain];
revGeoCodePart=[locDict objectForKey:#"reversegeocode"];
[revGeoCodePart retain];
addressParts=[revGeoCodePart objectForKey:#"addressparts"];
[addressParts retain];
cityName=[[addressParts objectForKey:#"city"] objectForKey:#"text"];
[cityName retain];
stateName=[[addressParts objectForKey:#"state"]objectForKey:#"text"];
[stateName retain];
countryName=[[addressParts objectForKey:#"country"]objectForKey:#"text"];
[countryName retain];
NSLog(#"city: %#\nstate: %#\ncountry: %#",cityName,stateName,countryName);
}

Resources