Clean solution to know which MKAnnotation has been tapped? - cocoa-touch

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
}
}

Related

Unable to get custom MKAnnotationView callout to appear

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.

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

Custom image for MKAnnotation

I have created an annotation which I'm adding to an MKMapView. How would I go about having a custom image instead of the standard red pin?
#interface AddressAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
MKPinAnnotationColor pinColor;
}
#property (nonatomic,retain) NSString *title;
#property (nonatomic,retain) NSString *subtitle;
#property (nonatomic, assign) MKPinAnnotationColor pinColor;
#end
MKMapView gets its pin views from its delegate method mapView:viewForAnnotation: So you have to:
Set your view controller as the map's delegate.
Implement mapView:viewForAnnotation: in your controller.
Set your controller as delegate
#interface MapViewController : UIViewController <MKMapViewDelegate>
Mark the interface with the delegate protocol. This let's you set the controller as MKMapView's delegate in Interface Builder (IB). Open the .xib file containing your map, right click the MKMapView, and drag the delegate outlet to your controller.
If you prefer to use code instead IB, add self.yourMapView.delegate=self; in the controller's viewDidLoad method. The result will be the same.
Implement mapView:viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// this part is boilerplate code used to create or reuse a pin annotation
static NSString *viewId = #"MKPinAnnotationView";
MKPinAnnotationView *annotationView = (MKPinAnnotationView*)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:viewId];
if (annotationView == nil) {
annotationView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:viewId] autorelease];
}
// set your custom image
annotationView.image = [UIImage imageNamed:#"emoji-ghost.png"];
return annotationView;
}
Override the mapView:viewForAnnotation: delegate method. If the annotation param points to one of your custom annotations, return a custom view that looks to your liking.
To set custom image instead of standart MKPinAnnotationView the only way to do that is to use MKAnnotationView with function - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation. Here's the example:
- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
static NSString *identifier = #"Annotation";
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!aView) {
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
aView.image = [UIImage imageNamed:#"Untitled1.png"];
aView.canShowCallout = YES;
aView.draggable = YES;
} else {
aView.annotation = annotation;
}
return pin;
}
For the aView.image value You can set Your own image. And also look into MKAnnotationView class reference to handle better with it.