Unable to get custom MKAnnotationView callout to appear - objective-c

I'm building an OSX application that uses Mapkit, and I'm trying to get a callout to appear when I click on an MKAnnotationView on my map. To do this I'm implementing the MKMapViewDelegate, and the following function :
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[LocationPin class]]) {
LocationPin *returnPin = (LocationPin *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"LocationPin"];
if (!returnPin){
returnPin = [LocationPin createLocationPinForMapView:mapView annotation:annotation];
}
returnPin.title = annotation.title;
NSButton *rightButton = [[NSButton alloc] initWithFrame:NSMakeRect(0.0, 0.0, 100.0, 80.0)];
[rightButton setTitle:#"Info"];
[rightButton setBezelStyle:NSShadowlessSquareBezelStyle];
returnPin.annotation = annotation;
returnPin.canShowCallout = YES;
returnPin.rightCalloutAccessoryView = rightButton;
return returnPin;
}
return nil;
}
The function runs fine everytime I put a new pin down, and I made sure the titles of the pins are not empty or null, but the callout still is not showing up. Anybody have any ideas?
EDIT:
After looking through Anna's response, I realized I misunderstood how to implement the MKAnnotation protocol. I've deleted my LocationPin class, which inherited from MKAnnotationView, and instead added the following class to represent my custom MKAnnotation, with a class inside of it to generate a custom MKAnnotationView:
#implementation LocationAnnotation:
-(id)initWithTitle:(NSString *)newTitle Location:(CLLocationCoordinate2D)location {
self = [super init];
if(self) {
_title = newTitle;
_coordinate = location;
}
return self;
}
- (MKAnnotationView *)annotationView {
MKAnnotationView *annotationView = [[MKAnnotationView alloc]initWithAnnotation:self reuseIdentifier:#"LocAnno"];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image = [NSImage imageNamed:#"dvd"];
NSButton *rightButton = [[NSButton alloc] initWithFrame:NSMakeRect(0.0, 0.0, 100.0, 80.0)];
[rightButton setTitle:#"Info"];
[rightButton setBezelStyle:NSShadowlessSquareBezelStyle];
annotationView.rightCalloutAccessoryView = rightButton;
return annotationView;
}
#end
I've also changed the MKMapViewDelegate function the following way now:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[LocationAnnotation class]]) {
LocationAnnotation *anno = (LocationAnnotation *)annotation;
MKAnnotationView *returnPin = [mapView dequeueReusableAnnotationViewWithIdentifier:#"LocAnno"];
if (!returnPin){
returnPin = anno.annotationView;
}
else {
returnPin.annotation = annotation;
}
return returnPin;
}
return nil;
}
But the callout still is not appearing. Any help is appreciated.
Edit 2:
As requested, LocationAnnotation.h:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface LocationAnnotation : NSObject <MKAnnotation>
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (copy, nonatomic) NSString *title;
-(id)initWithTitle:(NSString *)newTitle Location:(CLLocationCoordinate2D)location;
-(MKAnnotationView *)annotationView;
#end

I figured out what was wrong. I was subclassing MKMapView, which is apparently not recommended by Apple, as it can cause some unwanted behavior.

Related

Different MKPinAnnotation for different data

I have 3 types of locations.
I'm getting the data from my server, then I'm parsing it and displaying the locations on the mapView.
I want to display different colors for the different types of data. 3 types = 3 colors.
How can I control this?
Implement the viewForAnnotation delegate method for doing this.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[yourAnnotationLocation class]])
{
MKAnnotationView *annotationView = (MKAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
//if you need image you can set it like
//annotationView.image = [UIImage imageNamed:#"yourImage.png"];//here we use a nice image instead of the default pins
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
annotationView.annotation = annotation;
}
if ([annotation.title isEqualToString:#"Midhun"])
{
annotationView.pinColor = MKPinAnnotationColorGreen;
}
else
{
annotationView.pinColor = MKPinAnnotationColorRed;
}
return annotationView;
}
return nil;
}
For setting custom property to your annotation add a class which confirms to MKAnnotation protocol.
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyLocation : NSObject <MKAnnotation> {
NSString *_name;
NSString *_address;
int _yourValue;
CLLocationCoordinate2D _coordinate;
}
#property (copy) NSString *name;
#property (copy) NSString *address;
#property (assign) yourValue;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate;
#end
This is is a nice tutorial.
Try this,
annotation1.subtitle = #"1st annotation";
annotation2.subtitle = #"2st annotation";
annotation3.subtitle = #"3st annotation";
Check annotation
if ([annotation.subtitle isEqualToString:#"1st annotation"])
{
//change color
}
else if ([annotation.subtitle isEqualToString:#"2st annotation"])
{
//change color
}
else if ([annotation.subtitle isEqualToString:#"3st annotation"])
{
//change color
}
You can do something with latitude and longitude comparison
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { static NSString *identifier = #"yourIdentifier";
MKPinAnnotationView *pin = (MKPinAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if([annotation coordinate].latitude==YourLocationLatitude)
{
pin.image=[UIImage imageNamed:#"Flag-red.png"];
}
else
{
pin.image=[UIImage imageNamed:#"Flag-green.png"];
}}

Using MKMapViewDelegate

I can't understand why it's not working.
I have a map with a marker, I would like to change the icon
map.h:
#define METERS_PER_MILE 1609.344
#interface Map : UIViewController<CLLocationManagerDelegate,MKMapViewDelegate>{
IBOutlet MKMapView *map;
CLLocationManager *locationManager;
}
#property(nonatomic,retain) IBOutlet MKMapView *map;
#property(nonatomic,retain)IBOutlet UIWindow *window;
#property (nonatomic, readwrite) CLLocationCoordinate2D location;
- (void)setMarkers:(MKMapView *)mv;
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;
#end
And in map.m this:
#implementation Map
#synthesize map, window, location;
- (void)setMarkers:(MKMapView *)mv
{
//no necesary here
}
- (void)viewWillAppear:(BOOL)animated {
// 1
location.latitude = 38.989567;
location.longitude= -1.856283;
// 2
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(location, 0.8*METERS_PER_MILE, 0.8*METERS_PER_MILE);
// 3
MKCoordinateRegion adjustedRegion = [map regionThatFits:viewRegion];
// 4
[map setRegion:adjustedRegion animated:YES];
//[self setMarkers: map];
CLLocationCoordinate2D pointCoord = location;
NSString *theTitle = #"title";
NSString *theSubtitle = #"subtitle";
MapPoint *mp = [[MapPoint alloc] initWithCoordinate:pointCoord title:theTitle subTitle:theSubtitle];
[map addAnnotation:mp];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
NSLog(#"here!!");
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MapPoint class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [map dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image=[UIImage imageNamed:#"Icon.png"];//here we use a nice image instead of the default pins
return annotationView;
}
return nil;
}
#end
The viewForAnnotation is never executed (I ckeck this with NSLog)
What should I do?
Thank you in advance
Looks like there's nothing wrong with your code. As phix23 said, you may missed to link the MKMapView with the delegate:

Push another view when click on rightCalloutAccessoryView

When I click on the right callout on the PIN, i want to move to a new view controller named DetailsThemes, however, when i do so:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self.navigationController pushViewController:self.detailsThemes animated:YES];
}
I got this error:
Property detailsThemes not found on object of type 'ViewController *';
did you mean to access ivar 'detailsThemes'?
Although, detailsThemes is an instance of DetailsThemes which is a UIViewController class, also, in my class I did invoke the DetailsThemes class with #class DetailsThemes; on the top of the file like this:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#class DetailsThemes;
#interface ViewController : UIViewController<MKMapViewDelegate>
{
DetailsThemes * detailsThemes;
}
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#end
EDIT:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"calloutAccessoryControlTapped: %#",view.annotation);
[self.navigationController pushViewController:self.detailsThemes animated:YES];
}
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"ManageAnnotations";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [map dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
[annotationView setAnimatesDrop:YES];
annotationView.canShowCallout = YES;
//annotationView.pinColor = ((ManageAnnotations *)annotation).pinColor;
annotationView.rightCalloutAccessoryView=[UIButton buttonWithType:UIButtonTypeInfoLight];
return annotationView;
}
In your ViewDidLoad method you have to allocate first your DetailsThemes and then use it in your call out. if you still not success then please post code of your callOutAccessoryView

MKMapview place pin at location (long/lat)

I have latitude and long values and I need to be able to drop a pin at this location.
Can anybody provide some advice on how to go about this?
Find the below very simple solution to drop the pin at given location define by CLLocationCoordinate2D
Drop Pin on MKMapView
Edited:
CLLocationCoordinate2D ctrpoint;
ctrpoint.latitude = 53.58448;
ctrpoint.longitude =-8.93772;
AddressAnnotation *addAnnotation = [[AddressAnnotation alloc] initWithCoordinate:ctrpoint];
[mapview addAnnotation:addAnnotation];
[addAnnotation release];
You should:
1. add the MapKit framework to your project.
2. create a class which implements the MKAnnotation protocol.
Sample:
Annotation.h
#interface Annotation : NSObject <MKAnnotation> {
NSString *_title;
NSString *_subtitle;
CLLocationCoordinate2D _coordinate;
}
// Getters and setters
- (void)setTitle:(NSString *)title;
- (void)setSubtitle:(NSString *)subtitle;
#end
Annotation.m
#implementation Annotation
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
[self setTitle:nil];
[self setSubtitle:nil];
[super dealloc];
}
#pragma mark -
#pragma mark Getters and setters
- (NSString *)title {
return _title;
}
- (NSString *)subtitle {
return _subtitle;
}
- (void)setTitle:(NSString *)title {
if (_title != title) {
[_title release];
_title = [title retain];
}
}
- (void)setSubtitle:(NSString *)subtitle {
if (_subtitle != subtitle) {
[_subtitle release];
_subtitle = [subtitle retain];
}
}
- (CLLocationCoordinate2D)coordinate {
return _coordinate;
}
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate {
_coordinate = newCoordinate;
}
#end
2. create an instance of this class and set the lat/lon property
3. add the instance to the MKMapView object with this method:
- (void)addAnnotation:(id<MKAnnotation>)annotation
4. You should set the delegate of the map and implement the following method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
static NSString* ShopAnnotationIdentifier = #"shopAnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ShopAnnotationIdentifier];
if (!pinView) {
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ShopAnnotationIdentifier] autorelease];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.animatesDrop = YES;
}
return pinView;
}
This assumes that you have ARC enabled and that you have included the MapKit framework.
First create a class that implements the MKAnnotation protocol. We'll call it MapPinAnnotation.
MapPinAnnotation.h
#interface MapPinAnnotation : NSObject <MKAnnotation>
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, readonly) NSString* title;
#property (nonatomic, readonly) NSString* subtitle;
- (id)initWithCoordinates:(CLLocationCoordinate2D)location
placeName:(NSString *)placeName
description:(NSString *)description;
#end
MapPinAnnotation.m
#implementation MapPinAnnotation
#synthesize coordinate;
#synthesize title;
#synthesize subtitle;
- (id)initWithCoordinates:(CLLocationCoordinate2D)location
placeName:(NSString *)placeName
description:(NSString *)description;
{
self = [super init];
if (self)
{
coordinate = location;
title = placeName;
subtitle = description;
}
return self;
}
#end
Then add the annotation to the map using the following:
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(34.421496,
-119.70182);
MapPinAnnotation* pinAnnotation =
[[MapPinAnnotation alloc] initWithCoordinates:coordinate
placeName:nil
description:nil];
[mMapView addAnnotation:pinAnnotation];
The containing class will have to implement the MKMapViewDelegate protocol. In particular you will have to define the following function to draw the pin:
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
return nil;
}
static NSString* myIdentifier = #"myIndentifier";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:myIdentifier];
if (!pinView)
{
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:myIdentifier];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.animatesDrop = NO;
}
return pinView;
}
In this example the MKAnnotation title and subtitle member variables are not used, but they can be displayed in the delegate function.
-(MKPointAnnotation *)showClusterPoint:(CLLocationCoordinate2D)coords withPos:(NSString *)place
{
float zoomLevel = 0.5;
region = MKCoordinateRegionMake (coords, MKCoordinateSpanMake (zoomLevel, zoomLevel));
[mapView setRegion: [mapView regionThatFits: region] animated: YES];
point = [[MKPointAnnotation alloc]init];
point.coordinate = coords;
point.title=place;
[mapView addAnnotation:point];
return point;
}
Swift Version
let location = CLLocationCoordinate2DMake(13.724362, 100.515342);
let region = MKCoordinateRegionMakeWithDistance(location, 500.0, 700.0)
self.mkMapView.setRegion(region, animated: true)
// Drop a pin
let dropPin = MKPointAnnotation();
dropPin.coordinate = location;
dropPin.title = "Le Normandie Restaurant";
self.mkMapView.addAnnotation(dropPin);
Please use this code. its working fine.
-(void)addAllPinsOnMapView
{
MKCoordinateRegion region = mapViewOffer.region;
region.center = CLLocationCoordinate2DMake(23.0225, 72.5714);
region.span.longitudeDelta= 0.1f;
region.span.latitudeDelta= 0.1f;
[mapViewOffer setRegion:region animated:YES];
mapViewOffer.delegate=self;
MKPointAnnotation *mapPin = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.0225, 72.5714);
mapPin.title = #"Title";
mapPin.coordinate = coordinate;
[mapViewOffer addAnnotation:mapPin];
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:
(MKAnnotationView *)view
{
NSLog(#"%#",view.annotation.title);
NSLog(#"%f",view.annotation.coordinate.latitude);
NSLog(#"%f",view.annotation.coordinate.longitude);
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView
viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)annotation).title = #"Current Location";
return nil;
}
else
{
MKAnnotationView *pinView = nil;
static NSString *defaultPinID = #"annotationViewID";
pinView = (MKAnnotationView *)[mapViewOffer dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil ){
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID];
}
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"placeholder"];
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// [infoButton addTarget:self action:#selector(infoButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = infoButton;
pinView.rightCalloutAccessoryView.tag=1;
return pinView;
}
}
- (MKPinAnnotationView*)myMap:(MKMapView*)myMap viewForAnnotation:
(id<MKAnnotation>)annotation{
MKPinAnnotationView *pin = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"CustomPin"];
UIImage *icon = [UIImage imageNamed:#"bustour.png"];
UIImageView *iconView = [[UIImageView alloc] initWithFrame:CGRectMake(8,0,32,37)];
if(icon == nil)
NSLog(#"image: ");
else
NSLog(#"image: %#", (NSString*)icon.description);
[iconView setImage:icon];
[pin addSubview:iconView];
pin.canShowCallout = YES;
pin.pinColor = MKPinAnnotationColorPurple;
return pin;
}
- (IBAction)btnLocateMe:(UIButton *)sender
{
[mapViewOffer setCenterCoordinate:mapViewOffer.userLocation.location.coordinate animated:YES];
}

Clean solution to know which MKAnnotation has been tapped?

Ok, so you typically have some object X you want to be annotated inside a MKMapView. You do this way:
DDAnnotation *annotation = [[DDAnnotation alloc] initWithCoordinate: poi.geoLocation.coordinate title: #"My Annotation"];
[_mapView addAnnotation: annotation];
Then you create the annotation view inside
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;
And when some callout gets tapped, you handle the event inside:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control;
What's the cleanest solution to pass X down to the latest tap event?
If I'm understanding your question, you should add a reference or property to your DDAnnotation class so that in your calloutAccessoryControlTapped method you can access the object.
#interface DDAnnotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
id objectX;
}
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, retain) id objectX;
When you create the annotation:
DDAnnotation *annotation = [[DDAnnotation alloc] initWithCoordinate:poi.geoLocation.coordinate title: #"My Annotation"];
annotation.objectX = objectX;
[_mapView addAnnotation: annotation];
Then:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
DDAnnotation *anno = view.annotation;
//access object via
[anno.objectX callSomeMethod];
}
I did this and it worked alright!
It's exactly what I need because I needed to do something when the map was tapped but letting the tap into the annotation flow normally.
- (void)viewDidLoad
{
[super viewDidLoad];
UIGestureRecognizer *g = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)] autorelease];
g.cancelsTouchesInView = NO;
[self.mapView addGestureRecognizer:g];
}
- (void) handleGesture:(UIGestureRecognizer*)g{
if( g.state == UIGestureRecognizerStateEnded ){
NSSet *visibleAnnotations = [self.mapView annotationsInMapRect:self.mapView.visibleMapRect];
for ( id<MKAnnotation> annotation in visibleAnnotations.allObjects ){
UIView *av = [self.mapView viewForAnnotation:annotation];
CGPoint point = [g locationInView:av];
if( [av pointInside:point withEvent:nil] ){
// do what you wanna do when Annotation View has been tapped!
return;
}
}
//do what you wanna do when map is tapped
}
}