The MKPointAnnotation is the basic version of an annotation that can be used to display a pin along with a title and subtitle. These can come in handy for displaying basic information on a map. If you want to do more than show a point on a map with a title, then you will need to use MKAnnotation and MKAnnotationView. We’ll look at both of those later in this tutorial. Note that you can change the pin as well and provide a custom image. I’ll show you how in the tutorial below.
The screenshot to the left shows what an MKPointAnnotation looks like in its basic form. This is what this short tutorial will cover today. To get started, download the tutorial from yesterday or create a new Single View Application and drag a map to the view, connect it up as an IBOutlet, import MapKit.framework and the CoreLocation.framework. Include both of these in your header file of the associated class with the view.
When done, we can begin.
Creating the MKPointAnnotation
We first start by creating an MKPointAnnotation pointer as follows:
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc]init];
Alloc and init is essential for this to work. If you skip this line, you will not see an annotation on your map.
Next, we need to configure the annotation. It needs three things to work. First we need to provide a coordinate of where to drop the pin. We then need to supply a title (if needed) and finally, we need to specify a subtitle.
Setting the MKPointAnnotation Coordinate
There are a number of ways we can specify the coordinate for the annotation. I’ll start with the long way so that you see a breakdown of how it works. I’ll then cut out a few lines of code to give you a more “compact” version.
CLLocationCoordinate2D pinCoordinate;
pinCoordinate.latitude = 51.49795;
pinCoordinate.longitude = -0.174056;
myAnnotation.coordinate = pinCoordinate;
On line 1 we create a CLLocationCoordinate2D called pinCoordinate. A CLLocationCoordinate2D is a struct which contains both a latitude and a longitude.
On lines 2 and 3 we specify the pinCoordinate.latitude and then the pinCoordinate.longitude.
On line 4, we access the coordinate property of our MKPointAnnotation and assign it the pinCoordinate.
A shorter way of accomplishing this can be done by using functions. We can cut all of this down to just a single line of code as follows:
myAnnotation.coordinate = CLLocationCoordinate2DMake(51.49795, -0.174056);
The CLLocationCoordinate2DMake is a function that creates a CLLocationCoordinate2D structure. Rather than defining a CLLocationCoordinate2D structure, using the CLLocationCoordinate2DMake function to assign coordinates to it and then use that variable to assign to the myAnnotation.coordinate, we can simply assign the result of the CLLocationCoordinate2DMake directly in to the myAnnotation.coordinate.
We next need to go ahead and add a title and subtitle. The code so far looks like this:
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
myAnnotation.coordinate = CLLocationCoordinate2DMake(51.49795, -0.174056);
myAnnotation.title = @"Matthews Pizza";
myAnnotation.subtitle = @"Best Pizza in Town";
Finally, we need to add the annotation to the map. We do that with the following line of code:
[self.mapView addAnnotation:myAnnotation];
We are simply calling the addAnnotation method from MKMapView on self.mapView.
Running the program will now show a pin on Exhibition Road in London. This is not really my Pizza shop and if I remember the area correctly, I don’t even think a Pizza Shop is on that part of the street… anyways.
If you have been using the sample app that was available for download earlier in this tutorial, you might want to comment out this method:
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
[self.mapView setCenterCoordinate:userLocation.coordinate animated:YES];
}
This will prevent the map from moving to the current location automatically. You can also go ahead and delete the Search button from the storyboard as it isn’t required for this tutorial.
Changing the Pin Colour or Pin Image
Although MKPointAnnotation is basic and can only provide a coordinate, name and subtitle, you can still use this class when customising the pin. Lets look at the code to do this:
To do this, we create the MKPointAnnotation as above and also add it to the map just like above. What we do next is override the – (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
// Try to dequeue an existing pin view first.
MKAnnotationView *pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"];
if (!pinView)
{
// If an existing pin view was not available, create one.
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomPinAnnotationView"];
//pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:@"pizza_slice_32.png"];
pinView.calloutOffset = CGPointMake(0, 32);
} else {
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Line 1 is the delegate method called mapView:viewForAnnotation:. Each time an annotation appears in the visible area of the map, this method is called. It is run for each annotation on the map… so if you show your current location and another annotation then the method runs twice if both come in to the view.
Lines 4 and 5 are used to check the current location annotation (the blue dot). If this is the annotation it is being called on, it returns nil which essentially means, leave the annotation as it is as standard.
Line 8 checks if the annotation coming in to the view is of the MKPointAnnotation class. Because our annotation is an MKPointAnnotation this is true and we move to line 11.
Line 11 creates an MKAnnotationView called pinView. This line simply checks for any pins that can be dequeued and then moves to the next line. This saves on memory because if a pin is not on the visible area of the map then why have it still there? By dequeuing it you free it up for another pin to take its place. This prevents the need to create another one and reuse it instead. This also helps increase performance when scrolling around the map. Pins off the map are put in a reuse queue.
Line 12 checks to see if there was an existing pin available. As this is our “first” pin on the map of this particular type, this line returns false and it creates a pin to use.
Line 15 we alloc/init the pin and specify the annotation from the argument in the method name. This means that the MKPointAnnotation we created earlier in viewDidLoad is passed in to the init method and provided with a reuse identified.
On lines 16 – 19 we can customise the pinview by setting properties of MKAnnotationView. Although we could use MKPinAnnotationView for this to add the following two properties, pinColor and animatesDrop, there is a bug that changes the image back to a pin when using it. For that reason it is best to currently use the MKAnnotationView class which has a bunch of methods we can use. In this instance we used the .image property to specify the pizza slice image. We also use canShowCallout which lets you tap on the pin to display text. We also offset the image with calloutOffset so that the bottom left corner of the pizza was the actual location.
To change the pin colour, modify the code as follows:
MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"];
if (!pinView)
{
// If an existing pin view was not available, create one.
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomPinAnnotationView"];
//pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
//pinView.image = [UIImage imageNamed:@"pizza_slice_32.png"];
pinView.pinColor = MKPinAnnotationColorPurple;
On line 11, change MKAnnotationView to MKPinAnnotationView (note that you need to change that in two locations.
On line 15 change MKAnnotationView to MKPinAnnotationView.
Comment out the pinView.image line (line 18).
Add the pinView.pinColor line as above.
The reason we switch to the MKPinAnnotationView class is it contains the pinColor property. The class inherits from MKAnnotationView. However, if we want to use an image then there’s a bug which causes the image to default back to a red pin if you tap and hold on it. This is the reason why we would use MKAnnotationView if using images even though MKPinAnnotationView inherits from MKAnnotationView. It just doesn’t work correctly for some reason.
Adding Left and Right Callouts to Further Enhance the Annotation Details
With the MKPointAnnotation we can also enhance use the MKAnnotationView to add a left and right callout. In this example we’ll put the disclosure button on the right and a thumbnail on the left. We do this as follows:
// Add a detail disclosure button to the callout.
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = rightButton;
// Add an image to the left callout.
UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pizza_slice_32.png"]];
pinView.leftCalloutAccessoryView = iconView;
When looking at the last sample code, we would move the else statement down that is on line 20 and would put this code just after line 19 (or anywhere in those curly braces would be sufficient).
Line 21 we create a UIButton and and create it as a UIButtonTypeDetailDisclosure which is a small circle with an arrow in it.
Line 22 we set the rightCalloutAccessoryView property with that UIButton we just created.
Line 25 we are creating a UIImageView and we alloc/init with the imageNamed as an NSString which is the filename you have dragged in to your project.
On line 26 we simply set the leftCalloutAccessoryView property with the UIImageView we just created.
This will then show as the image to the side with an icon, title, subtitle and disclosure button. To get the disclosure button working we need to override the delegate method called mapView:annotationView:calloutAccessoryControlTapped:
Here is some code I took from a sample project that Apple created but I dropped their segue and just created a UIAlertView just so you could see something happening without adding another view controller to the project.
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
id annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(@"Clicked Pizza Shop");
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Disclosure Pressed" message:@"Click Cancel to Go Back" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alertView show];
}
Lines 3 and 4 we are checking if the annotation isKindOfClass. We used the MKPointAnnotation class to create out annotation, so this is what we are checking for. Should you have 2 or more different custom classes then this is how you separate out what to do when a pin is tapped.
Lines 7 and 8 we create a UIAlertView and init with a few details. We then show that alert. What you would more likely do is segue to another View Controller to present information about the particular pin.
The full code for this project is listed below.
Closing Thoughts
As you can see, using the MKPointAnnotation is just for simple annotation pins. Note that in the methods of the MKMapView class you also have addAnnotations: which is similar to addAnnotation: but lets you add several at the same time. To do this, you need to encompass the annotations in to an NSArray and then provide that NSArray as the argument. When doing this, several pins will be added at the same time.
Tomorrow, we’ll take a look at customising annotations to show more information.
Header Code
#import
#import
#import
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@end
Implementation Code
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
self.mapView.mapType = MKMapTypeStandard;
self.mapView.showsUserLocation = YES;
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
myAnnotation.coordinate = CLLocationCoordinate2DMake(51.49795, -0.174056);
myAnnotation.title = @"Matthews Pizza";
myAnnotation.subtitle = @"Best Pizza in Town";
[self.mapView addAnnotation:myAnnotation];
}
#pragma mark Delegate Methods
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
id annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(@"Clicked Pizza Shop");
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Disclosure Pressed" message:@"Click Cancel to Go Back" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alertView show];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
// Try to dequeue an existing pin view first.
MKAnnotationView *pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"];
if (!pinView)
{
// If an existing pin view was not available, create one.
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomPinAnnotationView"];
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:@"pizza_slice_32.png"];
pinView.calloutOffset = CGPointMake(0, 32);
// Add a detail disclosure button to the callout.
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = rightButton;
// Add an image to the left callout.
UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pizza_slice_32.png"]];
pinView.leftCalloutAccessoryView = iconView;
} else {
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
#pragma mark Zoom
- (IBAction)zoomToCurrentLocation:(UIBarButtonItem *)sender {
float spanX = 0.00725;
float spanY = 0.00725;
MKCoordinateRegion region;
region.center.latitude = self.mapView.userLocation.coordinate.latitude;
region.center.longitude = self.mapView.userLocation.coordinate.longitude;
region.span.latitudeDelta = spanX;
region.span.longitudeDelta = spanY;
[self.mapView setRegion:region animated:YES];
}
#pragma mark Memory Warning
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
Alex says
How would you edit this to make it so each annotation object has a different leftCalloutAccessoryView?
Matthew says
If depends on what you are wanting to change. If just an image within a box then you just specify imageNamed: as something else. If you want to change the actual accessory view to look different for various types of annotation then you would subclass MKPointAnnotation… (a subclass for each type of annotation you want) and then in the MKAnnotationView method (viewForAnnotation) you would have this line:
if ([annotation isKindOfClass:[MKPointAnnotation class]])
And add more else if statements:
else if ([annotation isKindOfClass:[MyCustomAnnotation1 class]])
…
…
…
else if ([annotation isKindOfClass:[MyCustomAnnotation2 class]])
You would then set the call out different for each annotation.
How you go about it depends on what you want to achieve though. Let me know how it goes, or ask away if you still need help.
E. Newton says
Thank you. Thank you so very much. I have spent 8 hours trying to drop a pin at a specific location and every example I looked at was complicated with arrays or some ridiculous nested stuff. Maybe I’m not very bright, but your example I understand. Great work for us beginners.
akshay says
good explanation….
Shaun says
How to add customized button on top of the annotation?
Matthew says
You could investigate putting a custom UIView in to this property: rightCalloutAccessoryView. This accepts a UIView which would allow you to instantiate a button in there.
Eugene says
Thanks mate, exellent tutorial!
Trond says
Thanks a lot!
paul says
Hi there! thanks a lot for your tutorial :)
Here is my question: instead of having a UIAlertView, I’d like to have another view controller. I know apple released a sample “map callouts”, but I still cannot understand how to do it (they’re doing it with .xib files while I prefer working with storyboards…). I’ve been searching the internet all day but I’m quite lost now (I could not find any tutorials or complete explanation about it!) Could you help me with it?
Thanks,
Best
Craig says
It would be great if you updated this tutorial for Swift?
zühal calayoÄlu says
Hi ! I get an error in MKPointAnnotation Tutorial .It’s thread 1 signal sigabrt error.Can you help me?
Matthew says
Can you provide the code you are using?
Juan says
Thank you very much matthew. Very well explained info for annotation views
jayant rawat says
on click of annotation i want to create a view with image ,buttons ,text at the bottom of map view…how can i do it? i don’t want to show title,subtitle or any callout buttons..i want to show all description of annotation location in this custom view on the bottom of map view…
Shaklee Ahmed says
A very Simple and Great Tutorial. Very Easy to understand. Awesome Bro.
Naushad Qamar says
Thanks
Adnan says
can you change this tutorial to swift please ??
Matthew says
Working on the Swift version now. Should be ready in the next day or so. I’ll post a link.
Andy says
Hi Matthew
Any progress on the Swift version of this tutorial?
Leigh says
What would one add in order to programmatically move our annotation from point A to point B at the touch of a button?
What would one add in order to programmatically move our annotation through an array of points using a timer?
What would one add in order to tap and drag our annotation around the map?
Leigh says
A Swift version should be a separate post. My questions are for Objective-C.
Bajrang Sinha says
but after scrolling the map custom view say pizza icon converted into red pin. Help me out to solve this problem. Thank You.
Rafiullah says
how we add custom annotation for multiple annotation.plz explain it sir.
Hemant says
This tutorial really helped me to learn map. thanks