How to Customize Map Annotation and Map Annotation View
Many developers might have seen iOS app like yelp, foursquare, they have their own very customized Annotation View providing very specific information.
While, in this demo, it will show you how to make your own map annotation and annotation view.
1st, create a new project, name it "Tracker 286s".
2nd, Add MapKit framework to your project. (It's under Build Phases of your project target).
3rd, Go to your storyboard, drag a toolbar and MKMapView to the your controller's view. The clear button will clear all the annotation from the map.
4th, Ctrl + drag your MapView outlet to the ViewController.m, also ctrl + drag the clear(Bar Button Item) to add the IBAction to the ViewController.m
5th, Add
@import MapKit;
in the ViewController.m file.
6th, Add <MKMapViewDelegate> right after
@interface ViewController ()
To create an Annotation to be added to the MapView, we need to create a class conforms MKAnnotation Protocol.
7th, Create a new file names MAnnotation subclass of NSObject, conforms <MKAnnotation>.
// // MAnnotation.h // Tracke 286s // // Created by Antonio081014 on 4/7/14. // Copyright (c) 2014 Antonio081014.com. All rights reserved. // #import <Foundation/Foundation.h> @import MapKit; @interface MAnnotation : NSObject <MKAnnotation> @property (nonatomic, strong) NSNumber *angle; - (id)initWithLocation:(CLLocationCoordinate2D)coord andAngle:(CGFloat) angle; @end
In this code, we have a public property angle, which will be used and explained later. Also a public designate initializer is declared, which contains a coordinate tells where in the map the annotation will be, and the angle.
8th, Implement the function in MAnnotation File.
// // MAnnotation.m // Tracke 286s // // Created by Antonio081014 on 4/7/14. // Copyright (c) 2014 Antonio081014.com. All rights reserved. // #import "MAnnotation.h" @implementation MAnnotation @synthesize coordinate = _coordinate; @synthesize title = _title; @synthesize subtitle = _subtitle; - (id)initWithLocation:(CLLocationCoordinate2D)coord andAngle:(CGFloat)angle { self = [super init]; if (self) { self.coordinate = coord; self.angle = [NSNumber numberWithDouble:angle]; } return self; } #define epsilon 0.005f - (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate { if ((fabs(_coordinate.latitude - newCoordinate.latitude) > epsilon || fabs(_coordinate.longitude - newCoordinate.longitude) > epsilon)) { [self willChangeValueForKey:@"coordinate"]; _coordinate = newCoordinate; [self didChangeValueForKey:@"coordinate"]; } } - (CLLocationCoordinate2D)coordinate { return _coordinate; } - (void)setTitle:(NSString *)title { if (_title != title) { [self willChangeValueForKey:@"title"]; _title = title; [self didChangeValueForKey:@"title"]; } } - (NSString *)title { if (_title) { return _title; } return _title = [NSString stringWithFormat:@"%.6f", [self.angle doubleValue]]; } - (NSString *)subtitle { if (_subtitle) { return _subtitle; } return _subtitle = [NSString stringWithFormat:@"(%.5f, %.5f)", self.coordinate.longitude, self.coordinate.latitude]; } - (void)setSubtitle:(NSString *)subtitle { if (_subtitle != subtitle) { [self willChangeValueForKey:@"subtitle"]; _subtitle = subtitle; [self didChangeValueForKey:@"subtitle"]; } } @end
Here, implement all the properties of the MKAnnotation, since the Map-Kit uses KVO(Key-Value-Observation) to detect any change of property in MKAnnotation, so when we custom implemented these setters, we need to notify the observers(the one who is interested in these changes) in these setters. Any manual change in your change will require you implementing the corresponding setter. For more reference, check out this link. The title and subtitle are used to display callout, which will be displayed when you click on the annotation on the map.
9th, Display annotation on the Map.
// // ViewController.m // Tracke 286s // // Created by Antonio081014 on 4/7/14. // Copyright (c) 2014 Antonio081014.com. All rights reserved. // #import "ViewController.h" @import MapKit; #import "MAnnotation.h" @interface ViewController () <MKMapViewDelegate> @property (weak, nonatomic) IBOutlet MKMapView *mapView; @end @implementation ViewController #define METERS_PER_MILE 1609.344 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Set the center of the map. // This is the Park near Los Angeles City Hall. CLLocationCoordinate2D centerLocation; centerLocation.latitude = 34.0528873; centerLocation.longitude = -118.2434255; // Create a Map Region based on the Center Location created above and the size defined below. MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(centerLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE); // Set the Map Region to display. [self.mapView setRegion:viewRegion animated:YES]; // Create the coordinate for the Annotation. // This is the location of Los Angeles City Hall, which could be fetched from Google Map. CLLocationCoordinate2D coordinateOfLACityHall; coordinateOfLACityHall.latitude = 34.053714; coordinateOfLACityHall.longitude = -118.242653; // Create the Annotation with our designate initializer. MAnnotation *annotation = [[MAnnotation alloc] initWithLocation:coordinateOfLACityHall andAngle:90.f]; // Add the annotation to the Map. [self.mapView addAnnotation:annotation]; } - (IBAction)clearAllAnnotations:(UIBarButtonItem *)sender { } @end
Most of the code are already explained in the comment around the code. You should get something like this:
When click on the red pin point, you should see:
Which displays the angle as the title, coordinate as the subtitle as we defined in our MAnnotation class.
10th, Custom Annotation View. Adding this snippet to your ViewController.m file.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { return nil; }
This function will be like this even you don't write it here. It's a default delegate function.
11th, Create a new file MAnnotationView subclass MKAnnotationView. So the MAnnotationView.h file will look like:
// // MAnnotationView.h // Tracke 286s // // Created by Antonio081014 on 4/7/14. // Copyright (c) 2014 Antonio081014.com. All rights reserved. // #import <MapKit/MapKit.h> @interface MAnnotationView : MKAnnotationView @end
12th, Implement the designate initializer which will be called in the
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation;
So with other function.
// // MAnnotationView.m // Tracke 286s // // Created by Antonio081014 on 4/7/14. // Copyright (c) 2014 Antonio081014.com. All rights reserved. // #import "MAnnotationView.h" #import "MAnnotation.h" @implementation MAnnotationView - (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; if (self) { // Set the frame size to the appropriate values. CGRect cFrame = self.frame; cFrame.size.width = 40; cFrame.size.height = 40; self.frame = cFrame; self.contentMode = UIViewContentModeRedraw; // The opaque property is YES by default. Setting it to // NO allows map content to show through any unrendered parts of your view. self.opaque = NO; } return self; } // This function is critical, the comment is very important. // Whenever you assign an annotation to this view, the view will be redraw. - (void)setAnnotation:(id <MKAnnotation>)annotation { [super setAnnotation:annotation]; // this annotation view has custom drawing code. So when we reuse an annotation view // (through MapView's delegate "dequeueReusableAnnoationViewWithIdentifier" which returns non-nil) // we need to have it redraw the new annotation data. // // for any other custom annotation view which has just contains a simple image, this won't be needed // [self setNeedsDisplay]; } // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. // This function simply draw a triangle, then rotate in proper angle. - (void)drawRect:(CGRect)rect { CGFloat angle = 0.f; if ([self.annotation isKindOfClass:[MAnnotation class]]) { MAnnotation *annotation = (MAnnotation *)self.annotation; angle = [annotation.angle doubleValue]; } CGFloat size = MIN(self.bounds.size.width, self.bounds.size.height) / 2.f; CGPoint center = CGPointMake(self.bounds.size.width / 2.f, self.bounds.size.height / 2.f); // Draw the Path. UIBezierPath *path = [[UIBezierPath alloc] init]; [path moveToPoint:CGPointMake(center.x + 0.f, center.y - size)]; [path addLineToPoint:CGPointMake(center.x - size / 2.f, center.y + size / 2.f * sqrt(3.f))]; [path addLineToPoint:CGPointMake(center.x + size / 2.f, center.y + size / 2.f * sqrt(3.f))]; [path closePath]; // Rotate to the right angle. CGAffineTransform transform = CGAffineTransformIdentity; [path applyTransform:transform]; transform = CGAffineTransformTranslate(transform, center.x, center.y); transform = CGAffineTransformRotate(transform, angle / 180.f * M_PI); transform = CGAffineTransformTranslate(transform, -center.x, -center.y); [path applyTransform:transform]; // Fill and Stroke with right color. [[UIColor redColor] setFill]; [[UIColor blackColor] setStroke]; [path fill]; [path stroke]; } @end
Most of the code are explained with the comment.
13, Back to the ViewController.m file, Add the following code right after [super viewWillAppear:animated];
self.mapView.delegate = self;
14, Add the following code to the ViewController.m file.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { // If this annotation is our custom define annotation, we'll display it in our custom annotation view. if ([annotation isKindOfClass:[MAnnotation class]]) { static NSString *annotationViewIdentifier = @"Custom Annotation View"; // We retrieve this annotation view. MKAnnotationView *annotationView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:annotationViewIdentifier]; // If it has been used before, we update the annotation information, so the view could be redraw. if (annotationView) { annotationView.annotation = (MAnnotation *)annotation; } // Otherwise, let's create it. else { annotationView = [[MAnnotationView alloc] initWithAnnotation:(MAnnotation *)annotation reuseIdentifier:annotationViewIdentifier]; annotationView.opaque = NO; } return annotationView; } // Otherwise, will use annotation's default annotation view to display it. // For user current location, it will be a blue twinkle ball. // For other location, it will be a red point pin. return nil; }
Build and Run this code, you will see:
15th, For last, we'll remove the annotations from the Map by implement
- (IBAction)clearAllAnnotations:(UIBarButtonItem *)sender
Adding this snippet in this function:
[self.mapView removeAnnotations:self.mapView.annotations];
If you have more question about removing annotations from MapView, check out another post.
For the full code, check out the github.















