This is an update to iPhone DevNote #13. This post has solved my zooming/panning problem with a CustomView on top of my MKMapView courtesy of http://spitzkoff.com/craig/?p=108 (Craig’s blog).
The trick here is instead of doing the drawing on the drawRect method of the CustomView, we will use Craig’s methodology to use the drawRect method of a custom MKAnnotationView. Note, that he also used an internal view and made clipsToBounds = NO, this way we can draw the whole geometry on top of MKMapView not just a portion of it. The end result is the shape (polygon in this example) is below the added pins.

@interface LinePolygonAnnotationInternalView : UIView { // line view which added this as a subview. LinePolygonAnnotationView* _mainView; } @property (nonatomic, retain) LinePolygonAnnotationView* mainView; @end @implementation LinePolygonAnnotationInternalView @synthesize mainView = _mainView; -(id) init { self = [super init]; self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = NO; return self; } -(void) drawRect:(CGRect) rect { GeometryAnnotation* myAnnotation = (GeometryAnnotation*)self.mainView.annotation; // only draw our lines if we're not int he moddie of a transition and we // acutally have some points to draw. if(!self.hidden && nil != myAnnotation.points && myAnnotation.points.count > 0) { CGContextRef context = UIGraphicsGetCurrentContext(); // Drawing lines with a white stroke color CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); // Draw them with a 2.0 stroke width so they are a bit more visible. CGContextSetLineWidth(context, 2.0); if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0); } // Draw them with a 2.0 stroke width so they are a bit more visible. CGContextSetLineWidth(context, 2.0); for(int idx = 0; idx < myAnnotation.points.count; idx++) { CLLocation* location = [myAnnotation.points objectAtIndex:idx]; CGPoint point = [self.mainView.mapView convertCoordinate:location.coordinate toPointToView:self]; NSLog(@"Point: %lf, %lf", point.x, point.y); if(idx == 0) { // move to the first point CGContextMoveToPoint(context, point.x, point.y); } else { CGContextAddLineToPoint(context, point.x, point.y); } } if(myAnnotation.geometryType == kGeometryTypeLine){ CGContextStrokePath(context); } else if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); } } } -(void) dealloc { self.mainView = nil; [super dealloc]; } @end @implementation LinePolygonAnnotationView @synthesize mapView = _mapView; - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor clearColor]; // do not clip the bounds. We need the LinePolygonAnnotationInternalView to be able to render the whole line/polygon, regardless of where the // actual annotation view is displayed. self.clipsToBounds = NO; // create the internal line view that does the rendering of the line. _internalView = [[LinePolygonAnnotationInternalView alloc] init]; _internalView.mainView = self; [self addSubview:_internalView]; } return self; } -(void) setMapView:(MKMapView*) mapView { [_mapView release]; _mapView = [mapView retain]; [self regionChanged]; } -(void) regionChanged { NSLog(@"Region Changed"); // move the internal line view. CGPoint origin = CGPointMake(0, 0); origin = [_mapView convertPoint:origin toView:self]; _internalView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height); [_internalView setNeedsDisplay]; } - (void)dealloc { [_mapView release]; [_internalView release]; [super dealloc]; } @end
I extended the class above to be able to draw both lines and polygons by checking a property (geometryType) of the GeometryAnnotation. If the geometryType is a line, then just stroke the path. However, if the geometryType is a polygon, then close the path and fill it.
if(myAnnotation.geometryType == kGeometryTypeLine){ CGContextStrokePath(context); } else if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); }
And here is the GeometryAnnotation class. Most of the code is from Craig, i just added the geometryType property:
// Created by Craig on 8/18/09. // Copyright Craig Spitzkoff 2009. All rights reserved. // #import "GeometryAnnotation.h" @implementation GeometryAnnotation @synthesize coordinate = _center; @synthesize points = _points; @synthesize annotationID; @synthesize geometryType; -(id) initWithPoints:(NSArray*) points withGeometry:(GeometryType)geomType { self = [super init]; geometryType = geomType; _points = [[NSMutableArray alloc] initWithArray:points]; // create a unique ID for this line so it can be added to dictionaries by this key. self.annotationID = [NSString stringWithFormat:@"%p", self]; // determine a logical center point for this line based on the middle of the lat/lon extents. double maxLat = -91; double minLat = 91; double maxLon = -181; double minLon = 181; for(CLLocation* currentLocation in _points) { CLLocationCoordinate2D coordinate = currentLocation.coordinate; if(coordinate.latitude > maxLat) maxLat = coordinate.latitude; if(coordinate.latitude < minLat) minLat = coordinate.latitude; if(coordinate.longitude > maxLon) maxLon = coordinate.longitude; if(coordinate.longitude < minLon) minLon = coordinate.longitude; } _span.latitudeDelta = (maxLat + 90) - (minLat + 90); _span.longitudeDelta = (maxLon + 180) - (minLon + 180); // the center point is the average of the max and mins _center.latitude = minLat + _span.latitudeDelta / 2; _center.longitude = minLon + _span.longitudeDelta / 2; NSLog(@"Found center of new Annotation at %lf, %ld", _center.latitude, _center.longitude); return self; } -(MKCoordinateRegion) region { MKCoordinateRegion region; region.center = _center; region.span = _span; return region; } -(void) dealloc { [_points release]; [super dealloc]; } @end
Now that we have a way to draw a line/polygon as a custom MKAnnotationView, we need a custom TouchView (GeometryTouchView) which could accept the touch events.
For example, if the user wants to draw a line geometry, the GeometryTouchView would accept touch events from the user and add a point as a PointAnnotation in the Map. Succeeding points would be added to an array. For every point added, the MKAnnotationView drawRects method connects the points to produce a line. The MKAnnotationView is now added to the map.
Once the geometry is added as an annotation, the custom TouchView is hidden. This way we have access (panning/zooming) to the mapview. If we make a pan or a zoom, the region changes, thus we need to redraw the shape of the annotation again.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated { if(currentAnnotationView != nil){ NSLog(@"regionWillChangeAnimated"); currentAnnotationView.hidden = YES; } } - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { if(currentAnnotationView != nil){ NSLog(@"regionDidChangeAnimated"); currentAnnotationView.hidden = NO; [currentAnnotationView regionChanged]; } }
-(void) regionChanged { NSLog(@"Region Changed"); // move the internal line view. CGPoint origin = CGPointMake(0, 0); origin = [_mapView convertPoint:origin toView:self]; _internalView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height); [_internalView setNeedsDisplay]; }
The resulting image is now:

Download the DrawMap.zip code.
Hey, I\’ve been trying to modify your code to allow multiple polygons to be added simultaneously. I\’m having a problem when drawRect is called however.
I keep an array of all of the custom views for each array so every time regionChanged I will loop through every custom view and let it know it must drawRect. The problem is, the fill disappears on the polygons. Only the most recently drawn polygon retains its fill, all of the previous ones disappear. Any insight or tips? Could there be a problem with multiple internal views layering on each other? (or does clearColor take care of that)
Thanks
Is there a way to keep the polygon on the screen when scrolling and have it pan and zoom with the map? I have seen some apps do that with Google maps, but I don’t see how they did it. Any ideas?
Didn’t know someone was posting comments. i have enabled comments to show on the sidebar now.. hmm maybe an email alert will be good.
@justin: i have no quick answer for you mate..
1. is it only one touchView or multiple touchViews? i would guess multiple touchViews..
2. for each touchView created. Can you implement different colors for each stroke and fill. so we can see how the polygons will be drawn on top of each other.. Make sure that the path get closed and filled for a polygon…
3. Are you still drawing the points as vertexes of the polygons on top of mapview. If yes, note that viewForAnnotation gets called.
This is interesting.. I will be happy to have a wack at it if time permits..
Regards,
Rupert
>Is there a way to keep the polygon on the screen when scrolling and have it pan and zoom with the map? I have seen some apps do that with Google maps, but I don’t see how they did it. Any ideas?
@Ross Kimes: IT IS POSSIBLE IF they are USING GoogleMaps Javascript API. You can overlay polygons, polylines on the map. However, in my tutorial, I am trying not to call any javascript and implemented using ObjC by drawing a geometry on top of the UIViews..
I’m trying to implement this method for a project I’m working. I have a question though. The GeometryAnnotation says it adopts the MKProtocol however it doesn’t use any of the methods. When I do this Xcode complains when I build, however in your sample this is not the case. Why is that?
@Daniel Wood: What MKProtocol? Are you referreing to MKAnnotation Protocol? You might be missing a MapKit frameworks reference…
Sorry for taking so long to respond. I did not realize that you had replied. Need to check back more often.
I was able to get polygons to stay on the map while panning and zooming by intercepting the touch at the UIWindow level and using a NSNotification to send out a message to update.
The problem that I am having now has to do with performance while updating. For my application, I am displaying weather warning boxes over the map, so at any given time I can have 30 or 40 of these on the map.
When drawRect: is called for each warning for every touch then it has to continuously draw each warning over and over again.
My idea to fix this is to draw out the warning box in the internal views init method and just use drawRect: to adjust the size of the box. I have tried this a few different ways, but anytime I change it I cannot get the warnings to display. Any ideas?
Thanks,
Ross