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).
Related
I have added a custom annotation and a percentage label on it.
By pressing the button in red circle, I want to change value of label from percentage to business name.
My Code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[BusinessCustomAnnotation class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *) [mapViewOffers dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
UILabel* category = [[UILabel alloc] initWithFrame:CGRectMake(annotationView.frame.size.width / 2, 15, 55, 20)];
BusinessCustomAnnotation *myAnnotationView = (BusinessCustomAnnotation *)annotation;
NSLog(#"Type One Offer! = %i", mapTypes);
[category setAdjustsFontSizeToFitWidth:YES];
if (mapTypes == 1) {
category.text = [NSString stringWithFormat:#"%#%#", myAnnotationView.offerPercentage, #"%"];
}else if (mapTypes == 2){
category.text = myAnnotationView.businessName;
}else if (mapTypes == 3){
category.text = myAnnotationView.businessName;
}
[category setMinimumScaleFactor:1.0];
category.font = [UIFont systemFontOfSize:15.0 weight:5.0];
category.textAlignment = NSTextAlignmentCenter;
[annotationView addSubview:category];
annotationView.enabled = YES;
annotationView.canShowCallout = NO;
annotationView.image = [UIImage imageNamed:#"iconMapMarker"];//here we use a nice image instead of the default pins
} else {
annotationView.annotation = annotation;
}
return annotationView; }return nil; }
Above mapview delegate is calling for one time only.
Waiting for the solution.
Thanks in advance for helping me.
There are two ways of detecting user interaction with your annotation view. The common technique is to define a callout (that standard little popover bubble that you see when you tap on a pin in a typical maps app) for your MKAnnotationView. And you create the annotation view for your annotation in the standard viewForAnnotation method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return annotationView;
}
By doing this, you get a callout, but you're adding an right accessory, which is, in my example above, a disclosure indicator. That way, they tap on your annotation view (in my example above, a pin on the map), they see the callout, and when they tap on that callout's right accessory (the little disclosure indicator in this example), your calloutAccessoryControlTapped is called.
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
//first check your view class here
// here your code for change text on view
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//first check your view class here
// here your code for change text on view
}
You need to refresh the annotations. In Action of button try this :
mapTypes = 2
for (id<MKAnnotation> annotation in mapView.annotations)
{
[mapView removeAnnotation:annotation];
[mapView addAnnotation:annotation];
}
You can't make any changes to already added annotation pin.To make any changes to the annotation pin you need to remove all the pin and add it back.
Annotations don't refresh.
You have to remove all existing annotations with
[self.mapView removeAnnotations:self.mapView.annotations];
and update your "mapTypes" variable value to "2" or "3" in order to show business name.
Then you can can add your annotations again with [MKMapView addAnnotation:].
I have a code for creating annotation view :
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id<MKAnnotation>)myannotation{
MKAnnotationView *view = nil;
view = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if([myannotation isKindOfClass:[myAnnotation class]]){
if(nil == view) {
view = [[MKPinAnnotationView alloc] initWithAnnotation:myannotation
reuseIdentifier:#"identifier"];
view.canShowCallout = YES;
}
myAnnotation *anns= (myAnnotation*)myannotation;
_annimge=anns.Img;
UIButton *btnViewVenue = [UIButton buttonWithType:UIButtonTypeContactAdd];
[btnViewVenue addTarget:self action:#selector(ButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
btnViewVenue.titleLabel.text=#"GÓWNOKURWA";
view.rightCalloutAccessoryView=btnViewVenue;
}
return view;
}
I would like to show image on button press, thats why I signing image property of annotation to _annimge, but it only shows the last image added, no matter which annotation is active, same thing was happening with ID property which I added, it only returned the highest ID (last added).I am new at iOS and trying to fix this issue for way too long. Thanks for help.
I have a map view that adds annotations more or less like this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>) annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:#"MKPinAnnotationView"];
annotationView.canShowCallout = YES;
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[detailButton addTarget:self
action:#selector(handleButtonAction)
forControlEvents:UIControlEventTouchUpInside];
annotationView.rightCalloutAccessoryView = detailButton;
return annotationView;
}
In iOS 7, this puts an “i” icon on the right-hand side of the callout. Tapping on the icon triggers mapView:annotationView:calloutAccessoryControlTapped: (on the delegate) and handleButtonAction: (on self). I recently realized, though, that you can also tap anywhere else on the callout and the same two methods are fired.
This happens with a button of type UIButtonTypeDetailDisclosure but it doesn’t seem to happen with a UIButtonTypeCustom button. The delegate method is also not fired when I tap on the callout when there’s no accessory view at all. (That behavior isn’t surprising, of course; what’s surprising is that if the accessory view is a detail-disclosure button then these two methods are fired regardless of whether you tap on the button itself or just somewhere else in the callout.)
I’d like to get rid of the button in the callout—or at least replace it with a button showing my own image instead of the stock “i” icon—while still allowing the user to tap anywhere on the callout to trigger my action. Is this possible? I don’t see an MKMapViewDelegate method that corresponds to “callout tapped”.
Try to set custom image for button without changing UIButtonTypeDetailDisclosure type.
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[detailButton setImage:[UIImage imageNamed:#"icon"] forState:UIControlStateNormal];
For iOS7 and above this image will be tinted by default. If you want to keep original icon use the following
[[UIImage imageNamed:#"icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]
Or if you want to remove icon at all
[detailButton setImage:[UIImage new] forState:UIControlStateNormal];
To tap the callout button after the user has clicked on the Annotation view, add a UITapGestureRecognizer in didSelectAnnotationView. This way you can implement tap on the callout without needing the accessory views.
You can then get the annotation object back from the sender for further action.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(calloutTapped:)];
[view addGestureRecognizer:tapGesture];
}
-(void)calloutTapped:(UITapGestureRecognizer *) sender
{
NSLog(#"Callout was tapped");
MKAnnotationView *view = (MKAnnotationView*)sender.view;
id <MKAnnotation> annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
[self performSegueWithIdentifier:#"annotationDetailSegue" sender:annotation];
}
}
Dhanu A's solution in Swift 3:
func mapView(mapView: MKMapView, didSelectAnnotationView view:MKAnnotationView) {
let tapGesture = UITapGestureRecognizer(target:self, action:#selector(calloutTapped(sender:)))
view.addGestureRecognizer(tapGesture)
}
func mapView(mapView: MKMapView, didDeselectAnnotationView view: MKAnnotationView) {
view.removeGestureRecognizer(view.gestureRecognizers!.first!)
}
func calloutTapped(sender:UITapGestureRecognizer) {
let view = sender.view as! MKAnnotationView
if let annotation = view.annotation as? MKPointAnnotation {
performSegue(withIdentifier: "annotationDetailSegue", sender: annotation)
}
}
If you don't need tap indication, I'd advise to throw a UITapGestureRecognizer into the callout upon creation and add your handling object (perhaps controller) as a target with appropriate action.
- (void)mapView:(MKMapView *)mv annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
Park *parkAnnotation = (Park *)[view annotation];
switch ([control tag]) {
case 0: // left button
{
NSURL *url = [NSURL URLWithString:parkAnnotation.link];
[[UIApplication sharedApplication] openURL:url];
}
break;
case 1: // right button
{
// build a maps url. This will launch the Maps app on the hardware, and the apple maps website in the simulator
CLLocationCoordinate2D coordinate = self.locationManager.location.coordinate;
NSString *url2 = [NSString stringWithFormat:#"http://maps.apple.com/maps?saddr=%f,%f&daddr=%f,%f",coordinate.latitude,coordinate.longitude,parkAnnotation.location.coordinate.latitude,parkAnnotation.location.coordinate.longitude];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url2]];
}
break;
default:
NSLog(#"Should not be here in calloutAccessoryControlTapped, tag=%ld!",(long)[control tag]);
break;
}
}
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 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.