I need to show on my MkMapView about 10 locations (and respective annotations) and after pressing a button I need to add new different annotations according to different JSON parsing results (for example a locationIDValue < 100 means a red pin, otherwise green). This is the simplified code:
- (void)viewDidLoad
{
[super viewDidLoad];
map.showsUserLocation = true;
map.mapType = MKMapTypeStandard;
arrayID = [[NSMutableArray alloc] initWithObjects: #"id1", #"id2", #"id3", #"id4", #"id5", #"id6", #"id7", #"id8", #"id9", #"id10", nil];
#define MakeLocation(lat,lon) [[CLLocation alloc] initWithLatitude:lat longitude:lon]
locations= #[ MakeLocation(lat1,lon1), MakeLocation(lat2,lon2), MakeLocation(lat3,lon3), MakeLocation(lat4,lon4), MakeLocation(lat5,lon5), MakeLocation(lat6,lon6), MakeLocation(lat7,lon7), MakeLocation(lat8,lon8), MakeLocation(lat9,lon9), MakeLocation(lat10,lon10) ];
for (int l=0; l<[locations count]; l++) { // HERE ITS PERFECT! I CAN SEE ALL 10 ANNOTATIONS!
MKPointAnnotation* annotation= [MKPointAnnotation new];
annotation.coordinate = [locations[l] coordinate];
[map addAnnotation: annotation];
}
}
and
- (IBAction)parseMethod {
[map removeAnnotations:map.annotations];
for (int i=0; i < [arrayID count]; i++) { // arrayID contains ID values to parse for each location
NSURL *url = [NSURL URLWithString:
[NSString stringWithFormat:
#"http://JSONPARSINGURL/%#",[arrayID objectAtIndex:i]]];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:5.0];
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// PARSING CODE . . .
NSMutableArray *value = [JSON objectForKey:#"value"];
NSMutableDictionary *value0 = [value objectAtIndex:0];
[valueID replaceObjectAtIndex:i withObject:[value0 objectForKey:#"valueID"]];
locationIDValue = [[valueID objectAtIndex:i] intValue]; // locationIDValue contains the values that i must use to put different annotations on the map
NSLog(#"locationIDValue: %d", locationIDValue); // here I control IF app parses all the values of locationIDValue
[table reloadData]; // here I put another control to see all values parsed
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"FAILURE");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // I have used also simple [operation start] but I have same issue!
[queue addOperation:operation];
[queue waitUntilAllOperationsAreFinished];
}
NSLog(#"END PARSING"); // here I control the end of parsing, so now I can add new annotations to MKMapView according to locationIDValue array
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
for (int l=0; l<[locations count]; l++) { // HERE I CAN SEE ONLY A SINGLE LOCATION!
annotation.coordinate = [locations[l] coordinate];
NSLog(#"%f - %f", annotation.coordinate.latitude, annotation.coordinate.longitude); // here I control IF app parses all the values of coordinates
[map addAnnotation: annotation];
}
}
and
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pinView"];
pinView.animatesDrop=YES;
if (annotation != map.userLocation)
{
if ( locationIDValue <= 100) {
pinView.pinColor = MKPinAnnotationColorRed;
return pinView;
}
pinView.pinColor = MKPinAnnotationColorGreen;
return pinView;
}
else
map.userLocation.title = #"My position";
return nil;
}
The situation:
When I open app for the first time its all right, I can see ALL the annotations dropping on the map, according with the #define MakeLocation latitude and longitude; but if I press the button and start the parseMethod, I wait a few seconds (according to NSOperationQueue, but I can test it also without queue, with simple [operation start] code), then I can see ONLY ONE SINGLE annotation on the map, always the same, strangely on the FIFTH location, with lat5-lon5 coordinates (otherwise, if I change a few code I can see ALL THE ANNOTATIONS dropping on the SAME location). As u can see I write some NSLog code and add UITableView to control the activity, and this is the result:
END PARSING
coordinates: lat1 - lon1
coordinates: lat2 - lon2
coordinates: lat3 - lon3
coordinates: lat4 - lon4
coordinates: lat5 - lon5 <--- THE STRANGE POSITION
coordinates: lat6 - lon6
coordinates: lat7 - lon7
coordinates: lat8 - lon8
coordinates: lat9 - lon9
coordinates: lat10 - lon10
locationIDValue: 100
locationIDValue: 90
locationIDValue: 50
locationIDValue: 120
locationIDValue: 20
locationIDValue: 40
locationIDValue: 80
locationIDValue: 180
locationIDValue: 140
locationIDValue: 10
Well, it seems PERFECT: app parse all the data (also a UITableView populate rows with ALL the new values parsed), so AFTER the end of parsing operation I think we have EVERY value necessary to populate map with new annotations. Well, what is wrong here? Why after parsing I can only see a single annotation? Or, ALL annotations dropping ON THE SAME LOCATION? Please give me an help!
In parseMethod, change this:
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
for (int l=0; l<[locations count]; l++) { // HERE I CAN SEE ONLY A SINGLE LOCATION!
annotation.coordinate = [locations[l] coordinate];
NSLog(#"%f - %f", annotation.coordinate.latitude, annotation.coordinate.longitude); // here I control IF app parses all the values of coordinates
[map addAnnotation: annotation];
}
to this:
for (int l=0; l<[locations count]; l++) { // HERE I CAN SEE ONLY A SINGLE LOCATION!
//create a NEW annotation for each location...
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = [locations[l] coordinate];
NSLog(#"%f - %f", annotation.coordinate.latitude, annotation.coordinate.longitude); // here I control IF app parses all the values of coordinates
[map addAnnotation: annotation];
}
Move the MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init] to inside the for-loop so that a new annotation object is created for each location.
The existing code there creates a single annotation object and just keeps modifying its coordinate.
What is also really important to note (because it was very hard to find that error and I sat the whole last night in front of this...) is to check if your object that conforms to MKAnnotation has some master base class that imlements the methods isEqual and / or hash on only the data of the base class and you do not overwrite that in your base class. This was my problem and thus isEqual returned YES for all annotations and THUS it showed always only one annotation on the map. I couldn't find any hint on the internet about this so I will leave at least this comment here for poor people in the same situation.
Related
I am trying to annotate some locations in map using MapBox. and its done.
But the problem is some other annotations are seen in map without any reference, i.e we cant click on it and when i zoom in/zoom out it disappears.
Am only try to create two annotations including user location.
How this happen?
is it because any reusing of annotation?
Some code snippets used:
1) creating annotation in loop
MGLPointAnnotation *point = [[MGLPointAnnotation alloc] init];
point.coordinate = coordinate;
point.title = location;
NSString *altString =[NSString stringWithFormat:#"%#",mIsNSDictionary(response)?[response objectForKey:kKeyAlt]:response];
NSString *str = [NSString stringWithFormat:kKeyLocationLatLonNAltInBaidu,coordinate.latitude,coordinate.longitude,[altString floatValue]];
point.subtitle = str;
[self.arrayAnnotations addObject:point];
[self.mapView addAnnotation:point];
Note: self.arrayAnnotations contains only 2 points
2) Delegate method for annotation
-(MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation {
if([annotation isKindOfClass:[MGLPointAnnotation class]] && [self.arrayAnnotations containsObject:annotation]) {
NSString *reuseIdentifier = [self makeIdentifierString:annotation];
MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:reuseIdentifier];
if (!annotationImage) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:kImageNameMarker] reuseIdentifier:reuseIdentifier];
}
return annotationImage;
}
return nil;
}
I got 2 ViewControllers:
In the first I got TableView with name of countries.
When I click on one of the countries's cell it moves to the second ViewControl with performSegueWithIdentifier & saves the cell's name (for example: "France") in a variable outside the ViewController. in the seconds ViewController I got a MapKit View. I don't know how to make the map to go to "France".
Any suggestions?
- (void)performSearchWithLocationName:(NSString*)CountryName{
//#define METERS_PER_MILE 1.344
MKLocalSearchRequest *request =
[[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = CountryName;
request.region = _mapView.region;
MKLocalSearch *search =
[[MKLocalSearch alloc]initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse
*response, NSError *error) {
if (response.mapItems.count == 0){
NSLog(#"No Matches");
}
else{
for (MKMapItem *item in response.mapItems)
{
MKPointAnnotation *annotation =
[[MKPointAnnotation alloc]init];
annotation.coordinate = item.placemark.coordinate;
annotation.title = item.name;
NSLog(#"%#", annotation.title);
[_mapView addAnnotation:annotation];
MKCoordinateRegion region;
region = MKCoordinateRegionMakeWithDistance(annotation.coordinate,0.3*METERS_PER_MILE, 0.3*METERS_PER_MILE);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
}
}
}];
}
//You can implement map delegate
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
NSLog(#"selected annotation and it's coordinates =>%f & %f",[view.annotation coordinate].latitude,[view.annotation coordinate].longitude);
}
Setp 1 : Getting latitude & longitude using location name
Setp 2 : Create the CLLocationCoordinate2D Using that latitude and longitude
Setp 3 : Create the MKCoordinateRegion
Setp 2 : Change the Mapview Region
The route is clearly being made but the polyline is not drawn. I am able to find the total distance, and have verified that the coordinates we are using are not (0,0). Is there something wrong with the delegate, since it seems that both the addOverlay and addAnnotation (called in a custom method shown below called createAndAddAnnotationForCoordinate) methods are not working?
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//generates map view
mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,
self.view.bounds.size.height)];
//setting delegate to self is not fixing the error
mapView.delegate = self;
mapView.mapType = MKMapTypeStandard;
//converting CLLocationCoordinate2D to MKPlacemark
MKPlacemark *startPlacemark = [[MKPlacemark alloc]
initWithCoordinate: addressOneCoords addressDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:nil]];
MKPlacemark *endPlacemark = [[MKPlacemark alloc]initWithCoordinate: addressTwoCoords addressDictionary:[NSDictionary dictionaryWithObjectsAndKeys:nil]];
//converting MKPlacemark to MKMapItem
MKMapItem *start = [[MKMapItem alloc ]initWithPlacemark:startPlacemark];
MKMapItem *end = [[MKMapItem alloc]initWithPlacemark:endPlacemark];
MKDirectionsRequest *request = [MKDirectionsRequest new];
[request setSource:start];
[request setDestination:end];
[request setTransportType:MKDirectionsTransportTypeAutomobile];
request.requestsAlternateRoutes = YES;
//Just to check if the coordinates were transferred successfully between view controllers and they did transfer successfully
NSLog(#"address one lat is %f",addressOneCoords.latitude);
NSLog(#"address one lon is %f",addressOneCoords.longitude);
NSLog(#"address two lat is %f",addressTwoCoords.latitude);
NSLog(#"address two lon is %f",addressTwoCoords.longitude);
MKDirections *directions = [[MKDirections alloc]initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse response, NSError error){
//if the route can't be created
if(error){
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Unable to create route" message:#"Go back to check if addresses are valid" delegate:nil cancelButtonTitle:#"Ok"otherButtonTitles:nil];
[alert show];
}
else{
[mapView removeOverlays:self.mapView.overlays];
MKRoute *mainRoute = [response.routes firstObject];
routeLine = mainRoute.polyline;
if(routeLine){
[self.mapView removeOverlay:routeLine];
}
//the addOverlay method is not drawing the polyline
[self.mapView addOverlay: routeLine level:MKOverlayLevelAboveRoads];
//proof that route is being created successfully
NSLog(#"Total distance is %f", mainRoute.distance);
MKMapPoint middlePoint = mainRoute.polyline.points[mainRoute.polyline.pointCount/2];
//also, the addannotation method is not being called either it seems like
[self createAndAddAnnotationForCoordinate:MKCoordinateForMapPoint(middlePoint)];
}
}];
}
Our createAndAddAnnotationForCoordinate method,
-(void) createAndAddAnnotationForCoordinate : (CLLocationCoordinate2D)coordinate{
MKPointAnnotation* annotation = [[MKPointAnnotation alloc]init];
annotation.coordinate = coordinate;
annotation.title = #"Point";
annotation.subtitle = #"subtitle";
[mapView addAnnotation:annotation];
}
Our overridden mapviewdelegate method,
-(MKOverlayRenderer )mapView:(MKMapView )mapView rendererForOverlay:(id<MKOverlay>)overlay{
if([overlay isKindOfClass:[MKPolyline class]]){
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc]initWithOverlay:overlay];
renderer.strokeColor = [UIColor redColor];
renderer.lineWidth = 10.0f;
return renderer;
}
else
return nil;
}
The output if address one was somewhere in NJ and address two was somewhere in CA:
address one lat is 40.902599
address one lon is -74.407097
address two lat is 34.054435
address two lon is -118.253393
Total distance is 4459771.000000
Ron, I'm suspecting your polyline (mainRoute.polyline) - your rendererForOverlay looks almost exactly like one I am using successfully. Barring the very basic mistakes like not setting the MKMapView delegate, or setting it to some other object, I would be almost sure the polyline you add to the map does not contain valid CLLocationCoordinate2D structs.
In Swift, creation goes like
var coordinatePtr = UnsafeMutablePointer<CLLocationCoordinate2D>(track2DCoordinates)
let trackPolygon = MKPolyline(coordinates: coordinatePtr, count: track2DCoordinates.count)
mapView.removeOverlays(mapView.overlays)
mapView.addOverlay(trackPolygon)
Start by verifying that you really have a valid MKPolyline.
I'm also not sure about your middlePoint calculation.
MKMapPoint middlePoint = mainRoute.polyline.points[mainRoute.polyline.pointCount/2];
This kind of thing probably works right now but in Swift you need to be a lot more careful of the data types used as index. What if you have an odd number of points, or zero?
Annotation pin title shows same title for all pins. I have set label to show the title of each pin but I am getting same title for all labels. I did NSLog
of lbl.text and in NSLog it shows different title.
Why am I getting same title for all map pins.
-(void)maprequests
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// getting an NSString
NSString *emailid = [prefs stringForKey:#"email"];
NSString *deviceid = [Request UDID];
//NSString * walkGUID=[prefs stringForKey:#"walkguid"];
//NSLog(#"walkGUID:%#",walkGUID);
NSString * walkguid=[prefs stringForKey:#"walkguid"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://10.0.0.28/web/ws/get_poilist_walks.php?strEmailID=%#&strDeviceID=%#&strWalkGuid=%#",emailid,deviceid,walkguid]];
NSLog(#"%#",url);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//AFNetworking asynchronous url request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
self.pointofintrests = [responseObject objectForKey:#"PointOfIntrests"];
NSIndexPath *indexpath;
NSDictionary *tempDictionary= [self.pointofintrests objectAtIndex:indexpath.row];
for (NSDictionary *dictionary in _pointofintrests)
{
MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
NSString * latitude= [dictionary objectForKey:#"Latitude"];
NSString * longitude =[dictionary objectForKey:#"Longitude"];
double strlatitude = [latitude doubleValue];
double strlongitude = [longitude doubleValue];
region.center.latitude =strlatitude;
region.center.longitude = strlongitude;
region.span.longitudeDelta = 0.01f;
region.span.latitudeDelta = 0.01f;
[_mapview setRegion:region animated:YES];
[_mapview setDelegate:self];
DisplayMap *ann = [[DisplayMap alloc] init];
ann.coordinate = region.center;
[_mapview addAnnotation:ann];
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Request Failed: %#, %#", error, error.userInfo);
}];
[operation start];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
MKPinAnnotationView *pinView = nil;
UILabel *label;
if(annotation != mapView.userLocation)
{
static NSString *defaultPinID = #"com.invasivecode.pin";
pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil ) pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:defaultPinID];
if(![annotation isKindOfClass:[DisplayMap class]])
return nil;
DisplayMap *a = (DisplayMap *)annotation;
pinView.image=[UIImage imageNamed:#"push_pin#2x"];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(10, 30, 250, 30)];
lbl.backgroundColor = [UIColor clearColor];
lbl.textColor = [UIColor whiteColor];
lbl.alpha = 0.5;
lbl.tag = 42;
for (int i=0; i<_pointofintrests.count; i++)
{
lbl.text = [[_pointofintrests valueForKey:#"title"] objectAtIndex:i];
}
[pinView addSubview:lbl];
[_mapview selectAnnotation:pinView animated:YES];
pinView.canShowCallout = YES;
pinView.animatesDrop = NO;
}
else
{
}
return pinView;
}
Your problem is you are looping over _pointofinterests
for (int i=0; i<_pointofintrests.count; i++)
{
lbl.text = [[_pointofintrests valueForKey:#"title"] objectAtIndex:i];
}
This isn't doing what you think. Its assigning the last title in the array to every pin.
Because the viewForAnnotation delegate method gives you the annotation that will display, I tend to keep an array of annotations to save the index, and therefore you can access your data correctly.
When you call [_mapview addAnnotation:ann]; also save ann into an array.
You should then be able todo something like:
[[_pointofintrests valueForKey:#"title"] objectAtIndex:[annArray indexOfObject:annotation]]
As a side note, if _pointofinterests is an NSDictionary I would use objectForKey instead.
The main reason you are getting the same label text on all pins is, as already pointed out in another answer, that for every pin, lbl.text is always set to the title of the last object in _pointofintrests.
The solution I would prefer however is to set the annotation's title and use that to set the label's text.
When creating the annotation and before calling addAnnotation, set its title property:
DisplayMap *ann = [[DisplayMap alloc] init];
ann.coordinate = region.center;
ann.title = [dictionary objectForKey:#"title"]; //<-- add this line
[_mapview addAnnotation:ann];
In viewForAnnotation, instead of the for loop, you can simply set the label's text to the annotation's title (no searching of arrays or looping):
//for (int i=0; i<_pointofintrests.count; i++)
//{
// lbl.text = [[_pointofintrests valueForKey:#"title"] objectAtIndex:i];
//}
lbl.text = annotation.title;
That technically fixes the problem of the same text appearing on all pins.
However, there are some other issues with the code in viewForAnnotation which will become apparent after the above fix:
The UILabel is being added to the annotation view even if the annotation view was dequeued (meaning a previously created view is being re-used). That dequeued view will already have a UILabel in it and the existing code will add another one on top. After panning and zooming the map for a while, you will notice overlapping labels for each pin.
The UILabel should only be added when a new view is created (when the dequeue returns nil and you alloc+init a new one).
Another problem is that since you are using a custom image for your annotations, you should create a plain MKAnnotationView instead of an MKPinAnnotationView. The MKPinAnnotationView class is designed for showing the standard, built-in pin image and although it still has an image property, it tends to sometimes ignore that and revert to showing its standard pin image.
Another problem is this line:
[_mapview selectAnnotation:pinView animated:YES];
The map view can only have one annotation "selected" at a time so if you were thinking that calling this in viewForAnnotation would show the callout for all the annotations at once, then that is mistaken.
The selectAnnotation method requires the annotation as the first parameter. The pinView is not the annotation -- it is the annotation's view (they are not the same thing). The compiler must be complaining about this line and at run-time, the system is probably showing an error in the console saying something like "Trying to select an annotation which has not been added". So technically, the line should be [_mapview selectAnnotation:annotation animated:YES];
Regardless, do not call selectAnnotation in the viewForAnnotation delegate method. Doing so may lead to recursion and a stack overflow (and an EXC_BAD_ACCESS) because the select causes the annotation's view to be updated which causes viewForAnnotation to be called, etc. Instead, call it in the didAddAnnotationViews delegate method for the one annotation you want to show the callout for.
Your revised viewForAnnotation delegate method might look like this:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if(! [annotation isKindOfClass:[DisplayMap class]])
{
return nil;
}
static NSString *defaultPinID = #"MyPin";
int lblTag = 42;
MKAnnotationView *pinView = [mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if (pinView == nil)
{
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID];
pinView.image = [UIImage imageNamed:#"push_pin#2x"];
pinView.canShowCallout = YES;
//Create and add the label to the view ONCE when creating the view...
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(10, 30, 250, 30)];
lbl.backgroundColor = [UIColor clearColor];
lbl.textColor = [UIColor whiteColor];
lbl.alpha = 0.5;
lbl.tag = lblTag;
[pinView addSubview:lbl];
}
else
{
//If we are re-using a dequeued view
//update its annotation reference...
//(otherwise view will still be pointing to its previous annotation)
pinView.annotation = annotation;
}
//At this point, we have a new or dequeued view
//pointing to the current annotation.
//Now update the label that will already be there
//with the current annotation's title...
UILabel *lbl = (UILabel *)[pinView viewWithTag:lblTag];
lbl.text = annotation.title;
return pinView;
}
Remember you'll need to set the annotation's title as shown at the top of the answer as well.
There are also some things in the for loop in the maprequests method that I want to point out:
It's not necessary to create and set a region just to set the annotation's coordinate. Calling setRegion in the loop is pointless and inefficient since the user will only end up seeing the map positioned at the last annotation anyway. To set an annotation coordinate, just do:
ann.coordinate = CLLocationCoordinate2DMake(strlatitude, strlongitude);
You can call setRegion (once) after the for loop (or just call [_mapview showAnnotations:_mapview.annotations animated:YES]; so you don't have to yourself calculate the region that shows all the annotations).
You don't need to set the map view's delegate inside the for loop repeatedly. Set the delegate (once) before the for loop (though it should already have been set long before this method is called).
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.