Introduction to Cocoa Drawing

This introduction to Cocoa Drawing consists of two parts. The first part explains the basics, while the second parts shows you how to use the achieved knowledge to realise a "real life" drawing project.

We will use nearly all common Cocoa drawing-related objects and structures:

Table of Contents

Part 1

Chapter 1: Getting prepared.

Of course we have to start by creating our custom view class. In Xcode
go to 'File > New File...', choose 'Objective-C class' and give it a name. I'll use 'ResistorView'.

In the header file, change @interface ResistorView : NSObject to @interface ResistorView : NSView as we want to create a subclass of NSView. In your nib-file, drag a 'Custom View' object from the Library onto your form. In the inspector window, choose the 'Identity' tab and change Class to 'ResistorView'.

Now we're set to begin.

Chapter 2: Where to put our drawing code.

In ResistorView.m, add a new method:

- (void)drawRect:(NSRect)rect {

}

This method is invoked when the view is drawn. So it's the right place to do our own drawing.

Chapter 3: How positioning works

To understand positioning better let's have a look at the coordinates system you know from maths:

Cs

As you know, the origin is located in the lower left hand corner. This is completely similar to our Cocoa drawing area.

Chapter 4: Rectangles

Rectangles are represented by a NSRect object. It is created by using the NSMakeRect function which is used this way:

NSMakeRect(x, y, w, h)

X, y, w and h are CGFloat values. Now, what do x, y, w and h stand for?

Knowing how to use NSMakeRect is nearly everything, you need to draw your first rectangle. NSRect only contains information about where the rectangle is placed and how big it is. It's somehow imaginary and virtual yet. But how do we make it visible?

To do so, I have to introduce a new object first. It is called NSColor. A NSColor object can be created in many different ways. I'll use the easiest one at this point and explain the other ones in the second part of this tutorial.

NSColor *myFavoriteColor = [NSColor orangeColor];

Here we just used a color preset (you could use blueColor, greenColor, etc. as well)


Now we got everything to do our first drawing. Add the following code piece (which should now be self-explanatory) to the drawRect method.

NSRect aRect = NSMakeRect (20, 40, 70, 50); // Create the rectangle.
NSColor *myFavoriteColor = [NSColor orangeColor]; // Create a NSColor object.

[myFavoriteColor set]; // Use the color we just defined.

NSRectFill (aRect); // Fill the rectangle with the color.

In this case our rectangle is placed 20px to the right of the origin and 40px to the top. It is 70px high and 50px wide.

When running your app, you should get something like this:

ch3.png

Not too bad for the beginning, huh.

Chapter 5: Bezier Paths

So far, we've only drawn rectangles. Probably you're missing lines. Here's how to draw them:
Lines are created using Bezier paths. They are represented by the NSBezierPath object.

To continue, we first have to look at a new structure called NSPoint. It defines (suprise, surprise) a (again virtual) point in the drawing area and is created this way:

NSPoint aPoint = NSMakePoint(0, 0) // Point at (0|0) which is the origin.

The next steps are quite easy to understand, so I'll explain the necessary steps in the code comments.

NSBezierPath* myPath = [NSBezierPath bezierPath]; // Create new NSBezierPath object.
[myPath moveToPoint:NSMakePoint(0, 0)]; // Begin in the lower left corner of the view. (coordinates system, again :-)
[myPath lineToPoint:NSMakePoint(20, 40]; // Make a line to the a point at (20|40). Remember, this is where our rectangle begins.
[myPath setLineWidth:1]; // Our line width is going to be 1px.
[myPath stroke]; // Draw the line

Add the code to our existing drawRect method. You'll get something like this:

ch4.png

Pretty cool.

Of course you can do lineToPoint as many times to as many points you like.


Exercise

To get more familiar with Bezier paths, try to get something like the figure below on your own.

ex1.png

You don't really know where to place the first point for the right line? Maybe this will help you:

ex1_help.png

Chapter 6: Gradients

Solid colors are just boring, aren't they. So why not use a gradient instead.
The NSGradient object makes it easy to do so.

There are several ways to create it. Here's the easiest one:

NSColor *myFirstColor = [NSColor redColor]; // Define a NSColor object for the first gradient color
NSColor *mySecondColor = [NSColor yellowColor]; // Same for the second one.
NSGradient *aGradient = [[NSGradient alloc] initWithStartingColor:myFirstColor endingColor:mySecondColor];

[aGradient drawInRect:aRect angle:90]; // Draw it into our rectangle with an angle of 90 degrees (which means vertical)


In drawRect, change

NSColor *myFavoriteColor = [NSColor orangeColor]; // Create a NSColor object.
[myFavoriteColor set]; // Use the color we just defined.
NSRectFill (aRect); // Fill the rectangle with the color.


into the code above and try it out:

ch5.png

Even cooler. But... hold on a second. Our two Bezier lines are now black. Mysterious?
No. We don't set any color anymore (remember: we just replaced the code snippet containing [myFavoriteColor set]),
so it will use the default color which is black).

Chapter 7: NSMaxX and NSMaxY: Introduction and Exercise

So far, we've only used hardcoded values to position and size our drawing objects.
But what to do if our view will be resizable (e.g. it shrinks and grows when resizing the window)?

Maybe you've noticed that the drawRect method has a parameter we've totally ignored yet. It's called rect and represents the out custom view we're drawing into.

Now it would be great to know the width and height of that view so we can use them as a base to calculate sizes and positions of our objects we're drawing.
To do so, there are two functions called NSMaxX and NSMaxY which can of course be applied
on any NSRect structure (but in this case we just use it with the rect parameter).

Exercise

Let's try to get something like this (without using fixed values):

ch7_ex.png

First, try to do it on your own, then read on:


As you can see, the drawing consists of two bezier paths.
The first one starts in the lower left hand corner and ends in the upper right hand corner,
the second one starts in the upper left hand corner and ends in the lower right hand corner.

First path: We move to point (0, 0) which is in the lower left hand corner, then we line to a point at (NSMaxX(rect), NSMaxY(rect)).

Second path: We move to point (0, NSMaxY(rect)) which is in the upper left hand corner, then we line to a point at (NSMaxX(rect), 0).

ch7_ex_help.png

(all colored inserts made by me)

Here's the complete code:

NSBezierPath* myPath = [NSBezierPath bezierPath];
[myPath moveToPoint:NSMakePoint(0, 0)];
[myPath lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
[myPath setLineWidth:1];
[myPath stroke];

NSBezierPath* myPath2 = [NSBezierPath bezierPath];
[myPath2 moveToPoint:NSMakePoint(0, NSMaxY(rect))];
[myPath2 lineToPoint:NSMakePoint(NSMaxX(rect), 0)];
[myPath2 setLineWidth:1];
[myPath2 stroke];


Part 2

Chapter 1

Making a plan of what we're going to do.

Let's start by thinking about what our drawing will consist of:

Parts of the drawing

- (void)drawRect:(NSRect)rect {

// Draw the resistor background.

int resistorWidth = 0;
if (hasFiveBands) {
resistorWidth = (5 * BAND_WIDTH) + (5 * BANDSPACING_NORMAL) + (BANDSPACING_TBAND);
} else {
resistorWidth = (4 * BAND_WIDTH) + (4 * BANDSPACING_NORMAL) + (BANDSPACING_TBAND);
}

NSRect resistorRect = NSMakeRect(RESISTOR_WIRE_LENGTH, 0, resistorWidth, NSMaxY(rect));

NSColor *colorOne = [NSColor colorWithCalibratedWhite:0.7 alpha:1];
NSColor *colorTwo = [NSColor colorWithCalibratedWhite:0.8 alpha:1];

NSGradient *theGradient = [[NSGradient alloc] initWithStartingColor:colorOne endingColor:colorTwo];
[theGradient drawInRect:resistorRect angle:90];
[theGradient release];


// Draw wires.

[[NSColor grayColor] set];

// Left wire.
NSBezierPath* leftPath = [NSBezierPath bezierPath];
[leftPath moveToPoint:NSMakePoint(0, 0)];
[leftPath lineToPoint:NSMakePoint(RESISTOR_WIRE_LENGTH, NSMaxY(rect) / 2)];
[leftPath closePath];
[leftPath setLineWidth:0.5];
[leftPath stroke];

// Right wire.
NSBezierPath* rightPath = [NSBezierPath bezierPath];
int mP = RESISTOR_WIRE_LENGTH + resistorWidth;
int lP = (2 * RESISTOR_WIRE_LENGTH) + resistorWidth;
[rightPath moveToPoint:NSMakePoint(mP, NSMaxY(rect) / 2)];
[rightPath lineToPoint:NSMakePoint(lP, 0)];
[rightPath closePath];
[rightPath setLineWidth:0.5];
[rightPath stroke];



// Draw bands.
int height = NSMaxY(rect) ;

// First band.
NSRect firstBand = NSMakeRect (RESISTOR_WIRE_LENGTH + BANDSPACING_NORMAL,
0, BAND_WIDTH, height);

[[self gradientForColor:firstBandColor] drawInRect:firstBand angle:90];

// Second band.
NSRect secondBand = NSMakeRect (RESISTOR_WIRE_LENGTH + 2 * BANDSPACING_NORMAL
+ BAND_WIDTH, 0, BAND_WIDTH, height);

[[self gradientForColor:secondBandColor] drawInRect:secondBand angle:90];

// Third band.
NSRect thirdBand = NSMakeRect (RESISTOR_WIRE_LENGTH + 3 * BANDSPACING_NORMAL
+ 2 * BAND_WIDTH, 0, BAND_WIDTH, height);

[[self gradientForColor:thirdBandColor] drawInRect:thirdBand angle:90];

// Fourth band.
NSRect fourthBand;
if (hasFiveBands)
{
fourthBand = NSMakeRect (RESISTOR_WIRE_LENGTH + 4 * BANDSPACING_NORMAL

+ 3 * BAND_WIDTH, 0, BAND_WIDTH, height);
} else {
fourthBand = NSMakeRect (RESISTOR_WIRE_LENGTH + 3 * BANDSPACING_NORMAL
+ BANDSPACING_TBAND + 3 * BAND_WIDTH, 0,
BAND_WIDTH, height);
}

if (fourthBandColor != [NSColor cyanColor]) { // Cyan means transparent.
[[self gradientForColor:fourthBandColor] drawInRect:fourthBand angle:90];
}

// Fifth band.
if (hasFiveBands) {
NSRect fifthBand = NSMakeRect (RESISTOR_WIRE_LENGTH + 4 * BANDSPACING_NORMAL
+ BANDSPACING_TBAND + 4 * BAND_WIDTH, 0,
BAND_WIDTH, height );
[[self gradientForColor:fifthBandColor] drawInRect:fifthBand angle:90];
}
}