Creating Maps in Cocoa using NSBezierPath

 Creating Maps in a Cocoa App using NSBezierPath

In this tutorial we will create the USA Map using NSBezierPath.

The final output of the tutorial is as follows


NSBezierPath Map View

Please note that the map coordinates used in this tutorial is not 100% accurate.

Following are the steps to create the Map View application.

  1. Create a new Cocoa project and name it as USAMap
  2. Modify the NSWindow of the MainMenu.xib and add a view and specify the CustomClass as MapView (We will write the MapView class in a short while)
  3. Add a slider in the window that will be used to zoom the Map.
  4. Add a Custom View, this will be used as the information popover (The information is purely upto the implementer, here I am using a NSPopOver to display the custom view showing State Names and some descriptive text for each State.
  5. Include/Add the attached state border coordinate path file to your project.
  6. (Click on the link to download the json file having the USA State map border coordinates)
  7. Note: In this sample we have not used a ViewController or WindowController, (not a good design practice), The entire load is put of the NSView instance to parse and load the Map Coordinates from a JSON file. If you intend to use this in your app, move the file reading into a NSWindowController or NSViewController subclass and use the NSView instance only for rendering and drawing purpose.
  8. We have loaded the path coordinates JSON into a path array of actual NSBezierPath and are iterating through the path array inside the View drawRect method to do the actual drawing



//  MapView.h
//  KSWorldMap
//  Created by Debasis Das on 5/6/14.
//  Copyright (c) 2014 Debasis Das. All rights reserved.
#import <Cocoa/Cocoa.h>
@interface MapView : NSView

@property (assign) IBOutlet NSView *tooltipView; //This is the view that will contain the text fields/labels to be displayed in the Popover
@property (assign) IBOutlet NSTextField *primaryLabelTextField;
@property (assign) IBOutlet NSTextField *secondLabelTextField;
@property (assign) IBOutlet NSTextView  *detailsTextView;
@property (nonatomic,retain) NSArray *dataArray; //The container for holding the data loaded from the JSON File.
@property (nonatomic,retain) NSMutableArray *pathArray; //The actual NSBezierPath placeholder. drawRect method will iterate through this array and do the drawing in the MapView
@property (nonatomic,retain) NSPopover *infoPopOver; //Popover to display information of the clicked state/area
//  MapView.m
//  KSWorldMap
//  Created by Debasis Das on 5/6/14.
//  Copyright (c) 2014 Debasis Das. All rights reserved.

#import "MapView.h"

@implementation MapView
static float scale=1.0;
@synthesize dataArray;
@synthesize pathArray;
@synthesize tooltipView;
@synthesize primaryLabelTextField;
@synthesize secondLabelTextField;
@synthesize detailsTextView;
@synthesize infoPopOver;

- (id)initWithFrame:(NSRect)frame
    self = [super initWithFrame:frame];
    if (self) {
        [self recreatePaths]; 
    return self;

//Method to load the dataArray from the JSON Path File and then convert the same into NSBezierPath and store in the pathArray
    NSError *anError = nil;
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"usmap" ofType:@"json"]];
    dataArray =  [[NSArray alloc] initWithArray:[NSJSONSerialization JSONObjectWithData:data /*self.responseData*/ options:NSJSONReadingMutableContainers error:&anError]];
    NSArray *borderPathArray;
    [self setPathArray:nil];
    pathArray = [[NSMutableArray alloc] init];
    for (NSDictionary *dict in dataArray)
        NSBezierPath *hPath = [NSBezierPath bezierPath];
        [hPath setLineWidth: 1.5];
        borderPathArray = [dict objectForKey:@"borders"];
        [hPath moveToPoint:NSMakePoint([[[borderPathArray objectAtIndex:0] objectAtIndex:0] floatValue]*scale, [[[borderPathArray objectAtIndex:0] objectAtIndex:1] floatValue]*scale)];
        for (int i=1; i<[borderPathArray count];i++)
            [hPath lineToPoint:NSMakePoint([[[borderPathArray objectAtIndex:i] objectAtIndex:0] floatValue]*scale, [[[borderPathArray objectAtIndex:i] objectAtIndex:1] floatValue]*scale)];
        [pathArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:hPath,@"path",[dict objectForKey:@"name"],@"name",[dict objectForKey:@"color"],@"color", nil]];


//The popover is initialized in the awakeFromNib method
    if(self.infoPopOver == nil)
        NSPopover *popover = [[NSPopover alloc] init];
        [self setInfoPopOver:popover];
        [self.infoPopOver setBehavior:NSPopoverBehaviorTransient];
        NSViewController *vC = [[NSViewController alloc] init];
        vC.view = tooltipView;
        [[vC view] setFrame:[tooltipView frame]];
        [self.infoPopOver setContentViewController:vC];
        [self.detailsTextView setTextColor:[NSColor whiteColor]];
- (BOOL)isFlipped
    return YES; 

//The resizeScale method is mapped to the slider on the screen to zoom the MapView. it simply modifies the path coordinates by multiplying the scale value from the slider
    scale = [sender floatValue];
    [self recreatePaths];
    [self setNeedsDisplay:YES];

//Display the Information Popover on clicking inside an area. This method iterates through the path array and finds the path that contains the Clicked Point. 
- (void)mouseDown:(NSEvent *)theEvent
    NSPoint p = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    for (int i=0; i<pathArray.count; i++)
        if ([[[pathArray objectAtIndex:i] objectForKey:@"path"] containsPoint:p])
            [self.primaryLabelTextField setStringValue:[[pathArray objectAtIndex:i] objectForKey:@"name"]];
            [self.secondLabelTextField setStringValue:[NSString stringWithFormat:@"%@ Secondary Info",[[pathArray objectAtIndex:i] objectForKey:@"name"]]];
            [self.detailsTextView setString:[NSString stringWithFormat:@"%@ Detailed Information.. \r This is a long text about %@",[[pathArray objectAtIndex:i] objectForKey:@"name"],[[pathArray objectAtIndex:i] objectForKey:@"name"]]];
            [self.infoPopOver showRelativeToRect:NSMakeRect(p.x, p.y, 10, 10) ofView:self preferredEdge:CGRectMaxXEdge];

//The actual method that takes the load of doing the drawing/plotting of the map.
- (void)drawRect:(NSRect)dirtyRect
    [super drawRect:dirtyRect];
    [NSGraphicsContext saveGraphicsState];
    [[NSColor blackColor] setFill];
    [[NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:1.0 yRadius:1.0] fill];
    [NSGraphicsContext restoreGraphicsState];
    for (int i =0; i<pathArray.count; i++)
        [[NSColor blackColor] set];
        [[[pathArray objectAtIndex:i] objectForKey:@"path"] stroke];
        if([[[pathArray objectAtIndex:i] objectForKey:@"color"] isEqualToString:@"red"])
            [[NSColor redColor] set];
        else if([[[pathArray objectAtIndex:i] objectForKey:@"color"] isEqualToString:@"blue"])
            [[NSColor blueColor] set];
            [[NSColor lightGrayColor] set];
        [[[pathArray objectAtIndex:i] objectForKey:@"path"] fill];


Build and Run


MapView using NSBezierPath


Additional Information as a Popover on Click inside an Area/State or a path


Zoomed Map View

Created By: Debasis Das

Posted in Cocoa, Objective C Tagged with: , ,
2 comments on “Creating Maps in Cocoa using NSBezierPath
  1. Thank you for this code, I have adapted an iOS version. However, the JSON file seems to be missing the states of New York and Maine, is there a source where you got the json file from that might be missing the necessary points for these states? And also, I need Hawaii and Alaska for what I am doing.

    • Debasis Das says:

      Hi Robert,
      Glad that you found this useful.
      What I remember is that I created the coordinates myself by parsing an online SVG of USA map from wikipedia.
      The coordinates might not be perfect.
      Another thing to note, Using bezier path might not be the best option. Check if you can use CAShapeLayer for achieving the same.

      Best Regards

Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Recent Posts

Hit Counter provided by technology news