Programming Mac OS X with Cocoa for Beginners/A more ambitious application

Previous Page: Containers - arrays, and dictionarys | Next Page: Graphics - Drawing with Quartz

Well, "Hello World" will only take us so far. In order to get into more advanced aspects of Cocoa, we'll need to be a little more ambitious. The rest of this book will develop a new application, which will be a simple vector drawing program, a little like MacDraw. While we won't attempt to make this full featured, we will develop enough of the foundation so that you could go on to make it full featured.

The features we will develop are:

  • Multiple drawings open at once (document interface)
  • Saving and reading drawings from a file
  • A palette of tools, such as rectangle, oval, line
  • A drawing area allowing selection of objects for editing
  • Objects can be resized and moved by dragging
  • Objects can be cut and pasted
  • An inspector for setting the properties of selected objects
  • Changes are undoable
  • Drawings can be printed

Writing a full-featured drawing program from scratch is not a small undertaking, though Cocoa is probably a faster route than most. Because we'd like to get to understand as much of Cocoa as possible, those parts of this code that don't contribute to this understanding will be glossed over a little. The full source code can be downloaded however.

Application design

edit

Before we code, we must have a clear design in mind. Cocoa provides a lot of the basic infrastructure we'll need, such as a comprehensive document-based interface. However, we'll still have to develop a number of our own objects, since Cocoa is general purpose, and a drawing program is very specific.

We will need a class to embody the actual shapes that we will draw. Each new shape we draw creates a new instance of the object and adds it to a list of objects in the drawing. In fact the drawing is nothing more than a list of the objects it consists of. An array will make an ideal container for the contents of the drawing. By flattening the array to a file, we can easily save drawings. Reading a drawing file is a simple matter of reconstructing the array from the flattened file copy.

Because different shapes can be drawn, we will need different kinds of shape objects. Therefore a generic shape class which contains everything that is common to all shapes is needed, with subclasses of it to represent each different type of shape. The common class can deal with things such as the position and colours of the shape, handle mouse clicks, and so forth. The subclasses only need to provide the actual shapes. We will be using Cocoa's built-in graphics objects, mainly NSBezierPath, to implement the actual shape outlines.

The palette of tools is a user interface for selecting which type of shape will be drawn. One way to implement a tool palette is to use the selected tool to indicate which factory method should be used to make the shape being added. After that, the shape itself handles its own drawing. In fact the case of drawing the shape initially and resizing it later are almost the same, so we can leverage the same code for the two situations.

When we select objects, we will maintain a list of the selected objects. Another Array makes an ideal container for this. When the drawing is displayed, objects that are in the selection array can have the drag handles added. Changes to the selection need to be signalled to the Inspector so that it can update itself to reflect the object state, and so that it knows which object to target when changes are made. We will see how Cocoa makes this straightforward using notifications.

Every change to the list of objects, or to the object itself, needs to be recorded so it can be undone. As we will see, Cocoa has some very powerful features that make Undo quite easy to implement. As long as we design with Undo in mind, making it work is very straightforward.

The ability to print is also straightforward, because the Quartz graphics we will use for rendering is very close to the PDF (Portable Document Format) that is used for printing.

Getting started

edit

To get started, return to Xcode and close the HelloWorld project. Choose File->New Project... In the assistant, find 'Cocoa Document-based application', select it and click 'Next'. Choose a name for the project - I have called it Wikidraw. Choose a path for the project - a good place is the same projects folder you made earlier, for the HelloWorld project. Click 'Finish'. A new project window is opened.

Click 'Build and Go' to compile the project source code, and run the resulting application. This time, an untitled window will be displayed, including the text 'your document contents here'. If you choose File->New, you'll get a second window, and so on. Unlike HelloWorld, you will see that automatically we have a multi-window application already, and we haven't written any code yet. Quit Wikidraw and return to Xcode.

In the left hand panel, drill down to Wikidraw->Resources->MyDocument.nib (in Xcode 4, these document-related files will be named "Document" rather than "MyDocument"). You'll see that we have a 'MainMenu.nib' as we did for HelloWorld, but also MyDocument.nib, which we didn't have before. This file contains the user interface resources for the document windows. MainMenu.nib, as before, contains the menubar and all the menus, but this time it doesn't include a Window. You can open these files in Interface Builder by double-clicking them. Have a look and see how they are different from the earlier case.

In developing Wikidraw, we will stick to these two resource files. Interface elements that pertain to the whole application, like the tool palette and the Inspector, will go in MainMenu.nib. Things that pertain to each document window, such as the view that displays the drawing, will go in MyDocument.nib. In all real applications, you will often create more .nib files as your interfaces develop, but we are trying to keep it fairly simple here. Once you get the general idea, it should be clear how and when to create new .nib files.

The first thing we'll do is customise the menu bar.

Open MainMenu.nib in IB, and double-click the 'MainMenu' icon. A small window containing a menu bar will open. To edit a menu, click its title to show the menu, then click the item to select it. Double-clicking an item selects it so you can edit its text. First go through and change every instance of 'NewApplication' to 'Wikidraw'.

We'll add more here later. Save the file, return to Xcode, build and run. Verify that the changes to the menubar show up.

Setting up the document view

edit

Close MainMenu.nib, and open MyDocument.nib. Double-click Window to make it visible and frontmost. Position and size the window where you'd like it to show up in the application. Select and delete the 'Your document content here' text item. As with HelloWorld, drag a CustomView into the window. Position the view at the top left of the window . Choose Layout->Embed Objects In->Scroll View. This places the customView inside a scrollable view so that we will be able to scroll the drawing. Drag the scrollview so that it fits the edges of the window. Use the Size inspector to set the scroll view to follow the size of the window by making both interior "springs" flexible (click the straight line--it should become a coiled "spring"), and all the exterior ones rigid (straight). Double-click the CustomView to select it and use the Size inspector to make its width and height both 1500 pixels.

Create a subclass of NSView using the 'Classes' tab of the main window, selecting NSView, then choosing Classes->Subclass NSView. Type the class name as 'WKDDrawView'. Remember that if you are using XCode 3.0 or greater, Apple changed the way to do this step. In XCode, right click in the folder "Other Sources", then mouse over "Add", then "New File...". Under the Cocoa heading, choose "Objective-C NSView subclass". Type in your name in place of untitled, and leave the other stuff alone.

Use the Custom Class panel in the inspector to change the class of CustomView to WKDDrawView. (If you need to select the view, double-click it within the scroll view).

Finally, generate files for WKDDrawView (Classes->Create Files for WKDDrawView, having selected the class in the 'Classes' pane of the main window). Accept the default settings when asked if you want it added to the project. if you are using XCode 3.0 or greater, you can skip this step.

Save the nib file, and build and run it in Xcode. Verify that the window contains working scrollbars.

Assuming that works OK, let's add two simple methods to WKDDrawView.m. We will work in a flipped view, so we need to implement isFlipped, returning YES, and we can optimise drawing a little, by implementing a method called isOpaque, returning YES:

 - (BOOL)	isFlipped
 {
	return YES;
 }


 - (BOOL)	isOpaque
 {
	return YES;
 }

Build and Go again to confirm that the scrollbars now start at the top rather than the bottom, and that the view is filled with white rather than the Aqua background stripes.

Previous Page: Containers - arrays, and dictionarys | Next Page: Graphics - Drawing with Quartz