I have the following method in my MapViewController:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"MapVC"];
if (!annotationView) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"MapVC"];
annotationView.canShowCallout = YES;
annotationView.leftCalloutAccessoryView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// could put a rightCalloutAccessoryView here
} else {
annotationView.annotation = annotation;
[(UIImageView *)annotationView.leftCalloutAccessoryView setImage:nil];
}
return annotationView;
}
I believe it's properly set up, but when my map shows my annotations with title and subtitle properly, but they don't show the detail disclosure button, am I missing something?
Another thing is that when debugging this method is never called, yet the annotation view shows up with title and subtitle.
Most likely the map view's delegate is not set.
If the delegate is not set or if you don't implement the viewForAnnotation method, the map view will create a default annotation view which is a red pin with a callout containing only the title and subtitle (unless the title is blank in which case you will get a pin but no callout).
Either connect the map view's delegate outlet to File's Owner or in code add this (eg. in viewDidLoad before the annotation is added):
mapView.delegate = self;
Also, if you're not using ARC, add autorelease to the alloc lines to avoid a memory leak.
Related
I am trying to change my pin colour to purple, when I do it I lose the title though. Code is:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBarHidden=YES;
//init the location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager requestWhenInUseAuthorization];
self.mapView.showsUserLocation=YES;
self.userGeoPoint=self.message[#"userLocation"];
self.pinView = [[MKPointAnnotation alloc] init];
self.pinView.title=self.message[#"fromUser"];
self.coord = CLLocationCoordinate2DMake(self.userGeoPoint.latitude, self.userGeoPoint.longitude);
self.pinView.coordinate=self.coord;
//use a slight delay for more dramtic zooming
[self performSelector:#selector(addPin) withObject:nil afterDelay:0.5];
}
-(void)addPin{
[self.mapView addAnnotation:self.pinView];
[self.mapView selectAnnotation:self.pinView animated:YES];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.coord, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotationPoint
{
if ([annotationPoint isKindOfClass:[MKUserLocation class]])//keep the user as default
return nil;
static NSString *annotationIdentifier = #"annotationIdentifier";
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc]initWithAnnotation:annotationPoint reuseIdentifier:annotationIdentifier];
pinView.pinColor = MKPinAnnotationColorPurple;
//now we can throw an image in there
return pinView;
}
I tried setting the title property for MKPinAnnotation but there isn't one. Is there anyway I can get around this?
In viewForAnnotation, you need to set canShowCallout to YES (it's NO by default):
pinView.pinColor = MKPinAnnotationColorPurple;
pinView.canShowCallout = YES;
A couple of unrelated points:
It looks like you have a property named pinView of type MKPointAnnotation that you are using to create the annotation object in viewDidLoad. Then in viewForAnnotation, you have a local variable also named pinView of type MKPinAnnotationView. Although there is no naming conflict here, it causes and implies some conceptual confusion. MKPointAnnotation is an annotation model class while MKPinAnnotationView is an annotation view class -- they are completely different things. It would be better to name the annotation property pin or userAnnotation for example.
This comment in viewForAnnotation:
//now we can throw an image in there
seems to imply that you could set a custom image in the view at this point. Setting a custom image in a MKPinAnnotationView is not recommended since that class is designed to display default pin images in one of three colors. To use a custom image, create a plain MKAnnotationView instead.
The pinColor property was deprecated in iOS 9. From iOS 9 onwards you should use pinTintColor which takes a UIColor rather than a MKPinAnnotationColor.
Old:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.pinColor = MKPinAnnotationColorPurple;
New:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.pinTintColor = [UI Color purpleColor];
There is more information available in the Apple docs.
I just upgrade to ios 10, apple changed the api.
pinColor no longer in use, instead, use tintColor
New:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.tintColor = [UIColor purpleColor];
Note that the pin title will be displayed when the user taps it:
If the value of this property is true, a standard callout bubble is shown when the user taps a selected annotation view. The callout uses the title and subtitle text from the associated annotation object.
( https://developer.apple.com/documentation/mapkit/mkannotationview/1452451-canshowcallout )
I am trying to use a custom image on my MKAnnotationView when I use the following code I get no image on my annotation. I have checked in debug to ensure the image is being properly loaded into the UIImage.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"String"];
if(!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"String"];
UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
[directionButton setImage:directionIcon forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = directionButton;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
return annotationView;
}
There are two main issues:
The frame of the custom callout button is not set making it essentially invisible.
An MKAnnotationView is being created but its image property (the image of the annotation itself -- not the callout button's) is not set. This makes the whole annotation invisible.
For issue 1, set the button's frame to some appropriate value. For example:
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
directionButton.frame =
CGRectMake(0, 0, directionIcon.size.width, directionIcon.size.height);
For issue 2, set the annotation view's image (or create an MKPinAnnotationView instead):
annotationView.image = [UIImage imageNamed:#"SomeIcon"];
Additionally, you should handle view re-use correctly by updating the annotation property.
Complete example:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"String"];
if(!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"String"];
annotationView.image = [UIImage imageNamed:#"SomeIcon"];
UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
directionButton.frame =
CGRectMake(0, 0, directionIcon.size.width, directionIcon.size.height);
[directionButton setImage:directionIcon forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = directionButton;
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
}
else {
//update annotation to current if re-using a view
annotationView.annotation = annotation;
}
return annotationView;
}
In order for the callout to be shown, the annotation must be selected. To do this programmatically, call:
[mapView selectAnnotation:annotation animated:YES];
where annotation is the specific MKAnnotation for which you want a callout displayed.
You'll almost certainly want to put this in - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views.
There are a few caveats to consider, so here are two other posts that have some great answers and relevant discussions:
How to trigger MKAnnotationView's callout view without touching the pin?
Wanted: How to reliably, consistently select an MKMapView annotation
I am trying to show annotations on a mapView. All annotations come from JSON objects. They are divided into three groups. The user can select which annotations should be shown selecting an option on an segmentedIndex control.
As for now, the app is working as expected, the user selects an option from the segmentedIndex control, and the annotations are shown on the mapView.
My current issue is that I need the user to click on the callout view to open another viewController.
I think my code is right, but I guess it isn't then the showed callout view is the default calloutview, with title and subtitle. No action is fired when clicked on it.
Any help is welcome.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[PlaceMark class]]) {
MKPinAnnotationView *annotationView =
(MKPinAnnotationView *)[myMapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
// Create a UIButton object to add on the
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setRightCalloutAccessoryView:rightButton];
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[leftButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setLeftCalloutAccessoryView:leftButton];
return annotationView;
}
return nil;
}
- (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if ([(UIButton*)control buttonType] == UIButtonTypeDetailDisclosure){
// Do your thing when the detailDisclosureButton is touched
UIViewController *mapDetailViewController = [[UIViewController alloc] init];
[[self navigationController] pushViewController:mapDetailViewController animated:YES];
} else if([(UIButton*)control buttonType] == UIButtonTypeInfoDark) {
// Do your thing when the infoDarkButton is touched
NSLog(#"infoDarkButton for longitude: %f and latitude: %f",
[(PlaceMark*)[view annotation] coordinate].longitude,
[(PlaceMark*)[view annotation] coordinate].latitude);
}
}
Most likely the map view's delegate is not set in which case it won't call viewForAnnotation and will instead create a default view (red pin with a callout showing only the title and subtitle -- no buttons).
The declaration in the header file does not set the map view's delegate. That just tells the compiler that this class intends to implement certain delegate methods.
In the xib/storyboard, right-click on the map view and connect the delegate outlet to the view controller or, in viewDidLoad, put mapView.delegate = self;.
Unrelated, but I want to point out that in calloutAccessoryControlTapped, rather than checking the buttonType, you probably want to just know whether it's the right or left button so just do:
if (control == view.rightCalloutAccessoryView) ...
See https://stackoverflow.com/a/9113611/467105 for a complete example.
There are at least two problems with checking the buttonType:
What if you want to use the same type for both buttons (eg. Custom)?
In iOS 7, setting a button to UIButtonTypeDetailDisclosure ends up actually creating a button of type Info (see MKAnnotationView always shows infoButton instead of detailDisclosure btn for details). So the check for buttonType UIButtonTypeDetailDisclosure would fail (on iOS 7).
I have a map that has multiple annotations that are plotted on the map sourced from JSON data that are broken up by category within a UITableViewController that contains information like their latitude and longitude as well as other information like: address, phone number and so on...
I know how to add a detail disclosure button to the annotation view for each one, but how to do I get that information into a detail viewController?
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annoPin"];
MKAnnotationView *view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"annoView"];
if(!view) {
view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annoView"];
}
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:nil action:#selector(showDetails :) forControlEvents:UIControlEventTouchUpInside];
pinView.animatesDrop = YES;
view.rightCalloutAccessoryView = rightButton;
view.image = [UIImage imageNamed:#"check.png"];
view.enabled = YES;
view.canShowCallout = YES;
return view;
}
As you can see I've created a button, but how do I get the JSON info for each category over to a detail ViewController for each category inside of the UITableViewController?
When you create the annotation that you add to your mapview you need to give it all the information that will eventually get to the detail view controller, or at least a reference to the data that you can get later. Then in mapView:annotationView:calloutAccessoryControlTapped:, which gets called on the map delegate when a disclosure button is tapped, you get the annotationview's annotation and get the data from there.
I've tried both this (MKMapView Delegate):
-(void) mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *annotationView in views)
{
annotationView.image = [UIImage imageNamed:#"itemType2.png"];
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.canShowCallout = YES;
}
}
and this (on the VC that has the MKMapView):
[self.theMapView addAnnotations:annotationsArray];
for (id <MKAnnotation> itemAnnotation in self.theMapView.annotations)
{
MKAnnotationView *itemAnnotationView = [self.theMapView viewForAnnotation:itemAnnotation];
itemAnnotationView.image = [UIImage imageNamed:#"itemType2.png"];
itemAnnotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
itemAnnotationView.canShowCallout = YES;
//[self.theMapView setNeedsDisplay];
}
whithout any success on changing the MKAnnotationView's appearance, they appear as simple red pins without the disclosure buttons or anything....
Is the only way of changing them through creating a subclassed MKMapView and using - (MKAnnotationView *)viewForAnnotation:(id < MKAnnotation >)annotation ??? I feel it's desnecessary to create an extra subclass just for changing the annotations, why don't the above methods work?
You don't need to subclass anything to set those properties on an MKAnnotationView.
You need to implement the viewForAnnotation delegate method and set the properties there.
See this answer for a code example.
The only change from that example is to use MKAnnotationView instead of MKPinAnnotationView.