Everything is working fine in my app except for one thing: after zooming in and zooming back out, to see the whole map, some callouts open the wrong detailview. I don't know if I'm missing some code or else. Using Xcode 5.1.1 for iOS7. This is what I've got at the moment:
Annotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface Annotation: NSObject <MKAnnotation>
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@end
Annotation.m
#import "Annotation.h"
@implementation Annotation
@synthesize coordinate,title,subtitle;
@end
MapView.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface Nameofthemap : UIViewController <MKMapViewDelegate>
@property (strong, nonatomic) IBOutlet MKMapView *Nameofthemap;
@end
MapView.m
#import "MapView.h"
#import "Annotation.h"
#import "InfoViewController.h"
#import "InfoTwoViewController.h"
@interface MapView ()
@property (nonatomic, strong) IBOutlet InfoViewController *InfoViewController;
@property (nonatomic, strong) IBOutlet InfoTwoViewController *InfoTwoViewController;
@end
#define PLACE1_LATITUDE 43.777130;
#define PLACE2_LONGITUDE 10.790018;
#define PLACE2_LATITUDE 43.81471237;
#define PLACE2_LONGITUDE 10.67472765;
@implementation MapView
- (IBAction)changeMapType:(id)sender {
if (_MapView.mapType == MKMapTypeHybrid)
_MapView.mapType = MKMapTypeStandard;
else
_MapView.mapType = MKMapTypeHybrid;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self gotoLocation];
_MapView.showsUserLocation = YES;
}
- (void)gotoLocation
{
MKCoordinateRegion newRegion;
newRegion.center.latitude = PLACE1_LATITUDE;
newRegion.center.longitude = PLACE2_LONGITUDE;
newRegion.span.latitudeDelta = 0.25f;
newRegion.span.longitudeDelta = 0.25f;
[self.MapView setRegion:newRegion animated:YES];
NSMutableArray * locations = [[NSMutableArray alloc] init];
CLLocationCoordinate2D location;
Annotation *myAnn;
Annotation *myAnn2;
//Place1 annotation
myAnn = [[Annotation alloc] init];
location.latitude = PLACE1_LATITUDE;
location.longitude = PLACE1_LONGITUDE;
myAnn.coordinate = location;
myAnn.title = @"Name of the place";
myAnn.subtitle = @"Details";
[locations addObject:myAnn];
//Place2 annotation
myAnn2 = [[Annotation alloc] init];
location.latitude = PLACE2_LATITUDE;
location.longitude = PLACE2_LONGITUDE;
myAnn2.coordinate = location;
myAnn2.title = @"Name of place two";
myAnn2.subtitle = @"Details";
[locations addObject:myAnn2];
[self->_MapView addAnnotations:locations];
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = @"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:@"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[myAnn title] isEqualToString:@"Name of the place"]){
[rightButton addTarget:self action:@selector(myAnnClicked:)forControlEvents:UIControlEventTouchUpInside];
}
if ([[myAnn title] isEqualToString:@"Name of place two"]){
[rightButton addTarget:self action:@selector(myAnn2Clicked:)forControlEvents:UIControlEventTouchUpInside];
}
pinView.rightCalloutAccessoryView = rightButton;
}
return pinView;
}
-(IBAction)myAnnClicked:(id)sender
{
InfoViewController *info = [[InfoViewController alloc]init];
[self.navigationController pushViewController:info animated:YES];
}
-(IBAction)myAnn2Clicked:(id)sender
{
InfoTwoController *info2 = [[InfoTwoController alloc]init];
[self.navigationController pushViewController:info2 animated:YES];
}
@end
It's an annotation view re-use issue.
In
viewForAnnotation
, the button targets are only being set when creating a view (ifdequeueReusableAnnotationViewWithIdentifier
returnsnil
).But if
dequeueReusableAnnotationViewWithIdentifier
returns a previously-used view, the button target is still whatever was set for the annotation that used the view before.That previous annotation may not be the same as the current annotation.
So it's possible for annotation "two" to re-use a view that was originally created for annotation "one" and tapping on the already-created button shows the info for "one" instead of "two".
To fix this, two things should be done:
dequeueReusableAnnotationViewWithIdentifier
returns a view (ifpinView
is notnil
), the code must update the view'sannotation
property to the current annotation.if
and just before thereturn
.The updated
viewForAnnotation
would look like this:By the way, instead of creating separate methods for each annotation (which can get tedious), use the map view's
calloutAccessoryControlTapped
delegate method instead.In fact, right now, the map view is calling both your custom methods and the
calloutAccessoryControlTapped
delegate method (in which there's no code currently).In the delegate method, the annotation tapped is accessible via
view.annotation
.So in
viewForAnnotation
, you would just do this:Then in the
calloutAccessoryControlTapped
delegate method, you can do something like this:Then remove the
myAnnClicked
andmyAnn2Clicked
methods.You would also be much better off creating a generic "Info" view controller instead of a separate one for each annotation.
Some other unrelated things:
#define
linesPLACE2_LONGITUDE
twicenewRegion.center
is usingPLACE2_LONGITUDE
instead ofPLACE1_LONGITUDE