问题
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
回答1:
It's an annotation view re-use issue.
In viewForAnnotation
, the button targets are only being set when creating a view (if dequeueReusableAnnotationViewWithIdentifier
returns nil
).
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:
- If
dequeueReusableAnnotationViewWithIdentifier
returns a view (ifpinView
is notnil
), the code must update the view'sannotation
property to the current annotation. - The button target must be set whether a new view is being created or a dequeued view is being re-used. The easiest way to do this is to move the button creation/setting after the main
if
and just before thereturn
.
The updated viewForAnnotation
would look like this:
- (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;
}
else
{
//1. Re-using a view, update which annotation it's being used for now
pinView.annotation = myAnn;
}
//2. Now pinView is either a new view or re-used view.
//Set its button target based on current annotation...
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;
}
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:
- (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;
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
pinView.annotation = myAnn;
}
return pinView;
}
Then in the calloutAccessoryControlTapped
delegate method, you can do something like this:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
if ([view.annotation isKindOfClass:[Annotation class]])
{
Annotation *myAnn = (Annotation *)view.annotation;
id vcToPush = nil;
if ([[myAnn title] isEqualToString:@"Name of the place"]) {
vcToPush = [[InfoViewController alloc]init];
}
else if ([[myAnn title] isEqualToString:@"Name of place two"]) {
vcToPush = [[InfoTwoController alloc]init];
}
[self.navigationController pushViewController:vcToPush animated:YES];
}
}
Then remove the myAnnClicked
and myAnn2Clicked
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:
- Don't put a semi-colon at the end of the
#define
lines - You've defined
PLACE2_LONGITUDE
twice newRegion.center
is usingPLACE2_LONGITUDE
instead ofPLACE1_LONGITUDE
来源:https://stackoverflow.com/questions/24702230/callout-opening-wrong-view-after-zoom-in-ios7