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.
Related
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.
I have eventually succeeded with getting the index for the annotation clicked on at callOutAccecoryControlTapped-method. :)
I have made a custom annotation class called Annotation (Annnotation *ann) which I fill with values from my db at parse.com.
But now I don't know how to pass the values on to my next view LAOpeningHoursViewController?
I can see all the values when I am debugging and *ann (se code) has for example: ann.cname="Olles cafe" etc. So that is good.
I pass the whole ann-object on to my next view. But I don't know how to get for example ann.cname on to my 'self.myLabel.text'. I show you the code:
If you have the kindness to answer me, please explain very well because I am a beginner.
Best is if you could have the trouble writing a code-snippet here… ;)
This is some code of the first view:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = rightButton;
} else {
pinView.annotation = annotation;
}
return pinView;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
Annotation *ann = [[mapView selectedAnnotations] objectAtIndex:0];
NSLog(#"ann.subtitle = %#", ann.subtitle);
NSLog(#"ann.cid = %#", ann.cid);
NSLog(#"ann.cstr = %#", ann.cstr);
NSLog(#"ann.cname = %#", ann.cname);
//this works fine, I get all the values
[self performSegueWithIdentifier: #"openingHours" sender:ann];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"openingHours"]) {
LAOpeningHoursViewController *secondView = (LAOpeningHoursViewController*)segue.destinationViewController;
//PFObject *tempObject = [[myMapView selectedAnnotations] objectAtIndex:0];
Annotation *ann = (id)sender;
//Here is the problem: What to pass? I want to pass for example ann.cname that is declared in my custom annotation
//Both 'ann' and 'tempObject' has the right values for cname but I dont know how to pass it to the next view
secondView.myLabel.text = self.stringToPass;
//secondView.myLabel.text = ann.cname;
}
}
And here is the code from the second view called LAOpeningHoursViewController, that should receive the data:
- (void)viewDidLoad
{
[super viewDidLoad];
self.myLabel.text = ann.cname;
//recieving an error-code… of course
}
declared in LAOpeningHoursViewController.h as this
#property (strong, nonatomic) IBOutlet UILabel *myLabel;
#property (strong, nonatomic) NSString *textContent;
and finally, here is my Annotation.h-file where cname is the one that should go to 'myLabel'
#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;
#property(nonatomic, copy)UIButton *rightButton;
#property(nonatomic, retain)NSString *cid;
#property(nonatomic, retain)NSString *cname;
#property(nonatomic, retain)NSString *ctel;
#property(nonatomic, retain)NSString *cstr;
#property(nonatomic, retain)NSString *cmon;
#property(nonatomic, retain)NSString *ctue;
#property(nonatomic, retain)NSString *cwed;
#property(nonatomic, retain)NSString *passedId;
#property(nonatomic, retain)NSString *objectId;
#end
The sender parameter in [self performSegueWithIdentifier: #"openingHours" sender:ann]; should be set to self. This is intended to give the presented view controller reference to the presenting controller.
Add the property #property (nonatomic, strong) Annotation *selectedAnnotation to the presenting view controller. So that it can be set thus:
//this works fine, I get all the values
[self setSelectedAnnotation:ann];
[self performSegueWithIdentifier: #"openingHours" sender:ann];
Now you have the selected annotation as a property it can be used to initialize the LAOpeningHoursViewController:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"openingHours"]) {
LAOpeningHoursViewController *secondView = (LAOpeningHoursViewController*)segue.destinationViewController;
//PFObject *tempObject = [[myMapView selectedAnnotations] objectAtIndex:0];
Annotation *ann = (id)sender;
// Setup second view here:
secondView.myLabel.text = self.selectedAnnotation.cname;
// assign further properties here...
}
}
I hope this helps, good luck!
When I try to drag the pin, it does not move. When I touch it, it darkens. However, it never lifts up, and it never moves around.
Here is the header file for my annotation class:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <Parse/Parse.h>
#interface GeoPointAnnotation : NSObject <MKAnnotation>
- (id)initWithObject:(PFObject *)aObject;
#property (nonatomic, readwrite, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, readonly, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate;
#end
Here's where I create the annotation in my view controller:
- (void)updateMap
{
[self.mapView removeAnnotations:self.mapView.annotations];
PFGeoPoint *geoPoint = [self.post location];
// center the map around the geopoint
[self.mapView setRegion:MKCoordinateRegionMake(
CLLocationCoordinate2DMake(geoPoint.latitude, geoPoint.longitude),
MKCoordinateSpanMake(0.01, 0.01)
)];
GeoPointAnnotation *annotation = [[GeoPointAnnotation alloc] initWithObject:self.post.object];
[self.mapView addAnnotation:annotation];
}
// from Geolocations by Parse
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *GeoPointAnnotationIdentifier = #"RedPin";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:GeoPointAnnotationIdentifier];
if (!annotationView) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:GeoPointAnnotationIdentifier];
annotationView.pinColor = MKPinAnnotationColorRed;
annotationView.canShowCallout = YES;
annotationView.draggable = YES;
annotationView.animatesDrop = YES;
}
return annotationView;
}
Any ideas?
Found the problem: I forgot to hook up the Map View's delegate in my Storyboard xib. h/t Nitin Gohel - thanks!
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
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
}
}