>binaervarianz >projects >CocoaRay
Chapter 2: Bothersome OS-nonsense

Whatever you write, you first have to deal with the OS dependent librarys or farmeworks. It's a hard fight until you see your first output in an OS-window. Especially if your are not witing some simple text-based program, but a graphic oriented.  

To our luck it's at least Mac OS X with the Cocoa Framework we have to deal with here.

So let's start with firing up Xcode with a fresh ObjC Cocoa project. The MainMenu.nib has then to be opened with InterfaceBuilder. There we create some new subclasses. A NSView subclass (I called mine BIRenderView) is used for diplaying our rendering output. Derived from NSObject we create a BIRenderer and a BIControls class. The first one will be holding the rendering algorithm, the second is for translating inputs from the GUI.

InterfaceBuilder

We then resize the window of our program and place a CustomView out of the standard palette into it. I set it to a fixed size of 360x240 pixels. ( Why this size? I of course meant 320x240, but misstyped it the first time. But the render output fitted so nicely on the widescreen display that I continued using this size.) After that we have to change the class to our own subclass. We do that in the Custom Class  pane in the Inspector window.(I didn't use more interface elements at this point. But you may peek into further chapters if you don't want to redisign the GUI later on.)

You now can generate the source code for all new classes. We are then done with the InterfaceBuilder, at least for now.

In Xcode, you should find 6 new files for the 3 new classes, which you can move to the Classes folder for  tidyness. The BIControls class can be ignored for now, we won't do anything with it until later on. We first take a look at  BIRenderView. It inherits from (among others) two methods from NSViewwhich we need to overwrite:

- (id)initWithFrame:(NSRect)frameRect
initializes the object after its creation. Here we have to initialize all variables we want to use.
- (void)drawRect:(NSRect)rect
will be called regularly by the system to redraw the view. Here we have to put the call to our rendering algorithm. It is not enough to simply give the contents to be drawn here. It actually has to draw itself, or better: we have to activly make it draw itself.

To find out what this means and how it is achieved took me the first week of development. There are many functions which actually do drawing of some sort, but all I wanted was to display a finished image. To achieve that we need to wrap it into  a Cocoa object which then knows how to draw itself. The object we use for this purpose is of the NSImage class. We declare it in the header file as follows:

@interface BIRenderView : NSView
{
BIRenderer* renderer;
NSImage* image;
}

While we do this we also declare an instance of our renderer. Both of them need to be initialized in the aforementioned method. (Overriden methods need not to be deklared in the header):


- (id)initWithFrame:(NSRect)frameRect{
if ((self = [super initWithFrame:frameRect]) != nil) {
renderer = [[[BIRenderer alloc] init] initWithRect:frameRect];
image= [[NSImage alloc] init]; }
return self;
}

 alloc and init are standard methods which are understood by every object. initWithRect needs to be implemented in the renderer. It's used to transmit the rectangle to the renderer, in which the drawing should occure.

The drawing function of the view class is simple. Because we just ask the renderer to produce a new image. We then put that image inside a NSImage class and tell it to draw itself. The last two actions are just one line of code. But nevertheless it's not all that easy. 'Cause we can't just put raw image date inside the NSImage class. Another Cocoa class need to be put in between, a NSBitmapImageRef. This will be created by the renderer and then given to the NSImage for drawing.


- (void)drawRect:(NSRect)rect{ [renderer drawImage]; [image drawRepresentation:[renderer imgRep] inRect:rect];
}

With this we are done in BIRenderView. Which methods of the renderer did we use by now? Of course -(void) drawImage, to render the image. We need to declare this in the header, but we don't have to supply an implementation right now. The -(NSBitmapImageRep*) imgRep methode to return the rendered data and the -(id) initWithFrame:(NSRect)frameRect mothod for initialization. All three need to be put in the header, in addition to the NSBitmapImageRep.


@interface BIRenderer : NSObject
{
NSBitmapImageRep* image;
}
- (id)initWithRect:(NSRect)rect;
- (NSBitmapImageRep*)imgRep;

How to implement this methods? Well, imgRep just returns the declared image object. We don't want that reference to point to nothing, so we have to initialize it. The generated image is of course empty, but this will compile and run.


-(id)initWithRect:(NSRect)rect{
if(self=[self init]){ image = [NSBitmapImageRep alloc];
}
return self;
}
- (NSBitmapImageRep*) imgRep { return image;
}

Well, we are done for now. This was the most boring part. From now on we will get graphical feedback and can compile prototypes. Bravo!

>binaervarianz >projects >CocoaRay