CLGeocoder only working 50% of the time - ios

so I'm trying to search a location using CLGeocoder,
It works the first time I do it, but it's very unreliable. Sometimes it will work, when I go back, try it again. crashes. Sometimes when I load it I get an error PBRequester failed with Error Error Domain=NSURLErrorDomain, then something with the span.
This is my code:
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:placeName completionHandler:^(NSArray *placemarks, NSError *error) {
//Error checking
CLPlacemark *placemark = [placemarks objectAtIndex:0];
MKCoordinateRegion region;
region.center = [(CLCircularRegion *)placemark.region center];
region.center.latitude = placemark.region.center.latitude;
region.center.longitude = placemark.region.center.longitude;
MKCoordinateSpan span;
double radius = placemark.region.radius / 1000; // convert to km
NSLog(#"Radius is %f", radius);
span.latitudeDelta = radius / 112.0;
region.span = span;
[self.mapView setRegion:region animated:YES];
}];

During geocoding a lot of things can happen. Since it is an internet service its functionality is based on internet reachability and available band.
Fortunately the CLGeocoder returns and error in the completion block, it is supposed that if an error happen you must handle correctly. If your app crashes is not a geocoded fault, because probably you are not handling well the error or the situation.
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:placeName completionHandler:^(NSArray *placemarks, NSError *error) {
/* Error checking */
if (error)
{
NSLog(#"The error is %#",error.localizedDescription);
return;
}
if (placemarks.count >1)
{
/* Handle multiple placemarks */
}
else {
/* This is probably the source of your crashes if there is an error placemarks it would be an empty array, but if you ask for an object it will raise an exception for out-of-bounds index */
CLPlacemark *placemark = [placemarks objectAtIndex:0];
MKCoordinateRegion region;
region.center = [(CLCircularRegion *)placemark.region center];
region.center.latitude = placemark.region.center.latitude;
region.center.longitude = placemark.region.center.longitude;
MKCoordinateSpan span;
double radius = placemark.region.radius / 1000; /* convert to km */
NSLog(#"Radius is %f", radius);
span.latitudeDelta = radius / 112.0;
region.span = span;
[self.mapView setRegion:region animated:YES];
}
}];
Some suggestion:
Handle the error
Check how many placemarks the geocoded have be returned, they can be more than one
In your code if the geocoded will return an empty array, asking for an object at index 0 will raise an out-of-bounds exception, always check if the count is different from 0 or use -firstObject method (iOS7 only)

Related

geocodeAddressString is not running for the following address

If i give any address for this method it is not at all calling.But if if give "1800 Ellis St,San Francisco, CA 94102,United States" it is being called. tell me what is wrong.
[geocoder geocodeAddressString:#"#83 2nd Floor Diagonal Road V.V.Puram Bangalore 560004 India"
completionHandler:^(NSArray* placemarks1, NSError* error){
for (CLPlacemark* aPlacemark in placemarks1)
{
annotationCoord= aPlacemark.location.coordinate;
[locationManager stopUpdatingLocation];
MKCoordinateRegion region;
region.center.latitude=annotationCoord.latitude;
region.center.longitude=annotationCoord.longitude;
region.span.longitudeDelta=0.;
region.span.latitudeDelta=0.;
self.mapView.showsUserLocation = YES;
[self.mapView setRegion:region];
_annotation=[[LDAnnotation alloc]initWithCoordinate:annotationCoord andTitle:#""subTitle:#""];
[self.mapView addAnnotation:(id<MKAnnotation>)_annotation];
[_searchAddressTF resignFirstResponder];
}
}];
your placemarks1 array is probably empty, as you're not checking if it is or not, the block just isn't executing. This is the common format
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
if ([placemarks count] > 0) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
you can try printing the size of the place marks. Check out this page if you haven't seen it already. http://code.tutsplus.com/tutorials/forward-geocoding-with-clgeocoder--mobile-11011

Geocoding method looks right, but I always get 0 0 as my coordinates

This shouldnt be this hard... I'm just trying to geocode an address string so I can zoom to that address on the map on viewDidLoad, but every time I run the geocoding method I get 0 0 as my lat and long.
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:addressOfSelected completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"Geocode failed with error: %#", error);
return;
}
if (placemarks && placemarks.count > 0)
{
CLPlacemark *placemark = placemarks[0];
CLLocation *location = placemark.location;
self.shackCoords = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude);
}
}];
MKCoordinateRegion region;
region.center.latitude = self.shackCoords.latitude;
region.center.longitude = self.shackCoords.longitude;
region.span.latitudeDelta = .05;
region.span.longitudeDelta = .05;
[self.mapView setRegion:region];
shackCoords is just a CLLocationCoordinate2D.

iOS 7 - region.center deprecated

I have this code for my iOS app:
NSString *location = [[NSString alloc] initWithFormat:#"%#, %#", [self.campus campusStreetAddress], [self.campus campusCityStateZip]];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.campusMap.region;
region.center = placemark.region.center; //DEPRECATED iOS 7
region.span.longitudeDelta /= 1500;
region.span.latitudeDelta /= 1500;
[self.campusMap setRegion:region animated:NO];
[self.campusMap addAnnotation:placemark];
}
}
];
But, when I upgraded my app to iOS 7, placemark.region.center is deprecated. Is there a replacement I should use? Is this even a proper method to creating a map in a view?
Thanks!!
Try this:
region.center = [(CLCircularRegion *)placemark.region center];
if you just want the center of the region you can use :
region.center = placemark.location.coordinate
Combination of Heesien's and other answers and a bit of experimenting.
- (void)centerMapAroundPlacemark:(MKPlacemark *)placemark
{
CLRegion *region = placemark.region;
if ([region isKindOfClass:[CLCircularRegion class]])
{
[self centerMapAroundCircularRegion:(CLCircularRegion *)region
centerCoodinate:placemark.location.coordinate];
}
else
{
[self centerMapAroundCoorinate:placemark.location.coordinate];
}
}
- (void)centerMapAroundCircularRegion:(CLCircularRegion *)circularRegion
{
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMakeWithDistance(circularRegion.center,
circularRegion.radius,
circularRegion.radius);
[self.mapView setRegion:coordinateRegion animated:YES];
}
- (void)centerMapAroundCircularRegion:(CLCircularRegion *)circularRegion
centerCoodinate:(CLLocationCoordinate2D)centerCoodinate
{
// Only user the radius of region for an appropriate zoom level.
// The center of the region is not accurate.
// To see this search for 'Bath, UK'
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMakeWithDistance(centerCoodinate,
circularRegion.radius,
circularRegion.radius);
[self.mapView setRegion:coordinateRegion animated:YES];
}

Add Pin using Coordinates

I am creating a map app, and I am using the built-in forward geocoder. So far, everything is working great. When I enter an address, the geocoder converts the results beautifully into coordinates, displayed in the console with NSLog. How do I now convert these coordinates into a pin that is displayed on the map? Here is my code.
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
if ([placemarks count] > 0)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
NSLog (#"%f %f", coordinate.latitude, coordinate.longitude);
}
}];
The simplest way is to use an MKPointAnnotation
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:myCoordinate];
[annotation setTitle:#"My Place"];
[[self mapView] addAnnotation:annotation];

In iPhone how do I know when all of my map's annotations have loaded while using forward geocode?

In my app I have a table view that toggles the annotations on a map view. I can go back and forth between the table view and map view through the tab bar controller. My map will reload the annotations (from the selected items that are in the table view) on view did appear. I need to know when those annotations are done loading so that I can run my method that zooms to the generated region determined by the annotation cluster.
The problem was when I ran the zoom method directly after the addAnnotations method in the view did appear, it would start the zoom process before my annotations could get the correct coordinates. Thus resulting in an incorrect zoom, and my annotations moving to the correct location.
I would also like to note that I am using forward geocoding to get my annotations coordinates.
Here is my view did appear:
[super viewDidAppear:animated];
[businessMap removeAnnotations:businessPoints];
[businessPoints removeAllObjects];
UINavigationController *navVC = (UINavigationController *) [self.tabBarController.viewControllers objectAtIndex:0];
FirstViewController *VC = [navVC.viewControllers objectAtIndex:0];
for (businessInfo *business_info in VC.selectedBusinessesArray) {
businessInfoAnnotation *businessAnnotation = [[businessInfoAnnotation alloc] init];
businessAnnotation.businessInfoClass = business_info;
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:business_info.location
completionHandler:^(NSArray* geocoded, NSError* error){
if (geocoded && geocoded.count > 0) {
CLPlacemark *placemark = [geocoded objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D business_cords = location.coordinate;
businessAnnotation.coordinate = business_cords;
}
}];
businessAnnotation.title = business_info.name;
[businessPoints addObject:businessAnnotation];
}
[businessMap addAnnotations:businessPoints];
[businessMap setZoomEnabled:YES];
[self zoomToFitMapAnnotations:businessMap withArray:businessPoints];
Here is my zoom method:
-(void)zoomToFitMapAnnotations:(MKMapView*)mapViews withArray:(NSArray*)anAnnotationArray
{
if([mapViews.annotations count] == 0) return;
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
NSLog(#"zooming");
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for(MKPointAnnotation* annotation in anAnnotationArray)
{
topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);
bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
}
MKCoordinateRegion region;
region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1;
region = [mapViews regionThatFits:region];
[businessMap setRegion:region animated:YES];
}
Your help is appreciated, thanks. Sorry if this is sloppy, this is my first post.
Edit:
Here is my edited geocoder method according to nevan king's answer.
[geocoder geocodeAddressString:business_info.location
completionHandler:^(NSArray* geocoded, NSError* error){
if (geocoded && geocoded.count > 0) {
CLPlacemark *placemark = [geocoded objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D business_cords = location.coordinate;
businessAnnotation.coordinate = business_cords;
businessAnnotation.title = business_info.name;
[businessPoints addObject:businessAnnotation];
[businessMap addAnnotations:businessPoints];
[self zoomToFitMapAnnotations:businessMap withArray:businessPoints];
}
}
];
Also not that I tried using the count of the annotation array to determine the last annotation's geocode completionHandler to change the region only on that specific one. But this produced an inconsistent result for my region. This is the only way it was consistently keeping all annotations within view.
MKMapViewDelegate has a mapView:didAddAnnotationViews: method which is called after a group of annotation views gets placed on the map. Note that it's not the same as annotations (some annotations might not be visible depending on the zoom).
Edit: I just noticed that you're geocoding the locations first. In that case, you'll have to add the annotations to the map in the geocode completion handler. Do it on the main thread. In your code, at the time you call addAnnotations: the completion block hasn't finished yet.
You could use a dispatch group, but I believe you'd have to manually use dispatch_group_enter and dispatch_group_leave, rather than dispatch_group_async. That way you can enter the group before each call to geocodeAddressString, and leave the group when its completion block finished. When all the completion blocks have completed, dispatch_notify would call your code for the resizing.
It seems the best way to run a method after all annotations have been geocoded and loaded on the map is to run a simple count and an if statement checking the count number with an array count.
Here is what I came up with:
int pointsArrayCount = contactArray.count;
NSLog(#"Count : %d", pointsArrayCount);
int numberOfPoints = pointsArrayCount;
NSLog(#"Count Number : %d", numberOfPoints);
i = 0;
for (contactInfo *contact_info in contactArray) {
contatcInfoAnnotation *annotation = [[contatcInfoAnnotation alloc] init];
annotation.contactInfoClass = contact_info;
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:contact_info.address
completionHandler:^(NSArray* geocoded, NSError* error){
if (geocoded && geocoded.count > 0) {
CLPlacemark *placemark = [geocoded objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D contact_cords = location.coordinate;
annotation.coordinate = contact_cords;
annotation.title = contact_info.name;
[mapPoints addObject:annotation];
[mapView addAnnotations:mapPoints];
i++;
NSLog(#"Count Number of Geocoded: %d", i);
if(i == numberOfPoints) {
[self zoomToFitMapAnnotations:mapView withArray:mapPoints];
}
}
}//end completionHandler
];
However, the completion handler is only able to handle up to 40 some geocodes. So this only works well when you are loading small amounts to your map while geocoding. If you are doing more than that, then you should be storing all the coordinates somewhere and then loading them separately when the map loads.

Resources