Thursday, December 17, 2015

Drawing a flag as overlay on top of MKMapView

The source code of the following can be found at

I have been working on a travel related app where I want to highlight the countries I have visited. Now MKMapView allows you to create a polygon and stroke it with a color fill. It can be achieved with few lines of code in rendererForOverlay.

MKPolygonRenderer *renderer=[[MKPolygonRenderer alloc] initWithPolygon:overlay];renderer.fillColor = [UIColor colorWithRed:0.93 green:0.35 blue:0.26 alpha:0.7];
renderer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:1.0];
renderer.lineWidth   = 2;
return renderer;

This will create an image like below. It looked good but not great. I wanted to get something more interesting like the image in right.

How do we do that ? It is a combination of following steps.
  • Construct a polygon from the map points and add as an overlay to MKMapView.
  • Subclass the MKPolygonRenderer and override the drawMapRect
  • Create a UIBezierPath from the path property of polygon. Close the path.
  • Add clip to path for intersect.
  • Draw the overlay image.

Step 1 : Construct the polygon 

You need points which will construct the boundary of the country. I loaded the JSON file and constructed the map overlay using

  if(overLayDict == Nil)
    overLayDict = [[NSMutableDictionary alloc] init];
  NSString *fileName = [[NSBundle mainBundle] pathForResource:@"gz_2010_us_040_00_500k" ofType:@"json"];
  NSData *overlayData = [NSData dataWithContentsOfFile:fileName];
  NSArray *countries = [[NSJSONSerialization JSONObjectWithData:overlayData options:NSJSONReadingAllowFragments error:nil] objectForKey:@"features"];
  for (NSDictionary *country in countries) {
    NSMutableArray *overlays = [[NSMutableArray alloc] init];
    NSDictionary *geometry = country[@"geometry"];
    if ([geometry[@"type"] isEqualToString:@"Polygon"]) {
      MKPolygon *polygon = [ViewController overlaysFromPolygons:geometry[@"coordinates"] id:country[@"properties"][@"name"]];
      if (polygon) {
        [overlays addObject:polygon];
    } else if ([geometry[@"type"] isEqualToString:@"MultiPolygon"]){
      for (NSArray *polygonData in geometry[@"coordinates"]) {
        MKPolygon *polygon = [ViewController overlaysFromPolygons:polygonData id:country[@"properties"][@"name"]];
        if (polygon) {
          [overlays addObject:polygon];
    } else {
      NSLog(@"Unsupported type: %@", geometry[@"type"]);
    [overLayDict setObject:overlays forKey:country[@"properties"][@"name"]];

Step 2 : Override the drawRect in MKPolygonRenderer

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
  [super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
  MKMapRect theMapRect = [self.overlay boundingMapRect];
  CGRect theRect = [self rectForMapRect:theMapRect];
  @try {
    UIBezierPath *bpath = [UIBezierPath bezierPath];
    MKPolygon *polyGon = self.polygon;
    MKMapPoint *points = polyGon.points;
    NSUInteger pointCount = polyGon.pointCount;
    CGPoint point = [self pointForMapPoint:points[0]];
    [bpath moveToPoint:point];
    for (int i = 1; i < pointCount; i++) {
      point = [self pointForMapPoint:points[i]];
      [bpath addLineToPoint:point];
    [bpath closePath];
    [bpath addClip];
    [_overlayImage drawInRect:theRect blendMode:kCGBlendModeMultiply alpha:0.4];
  @catch (NSException *exception) {
    NSLog(@"Caught an exception while drawing radar on map - %@",[exception description]);
  @finally {

No comments:

Post a Comment