oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Working With Bitmap Images; Document-Based Application Redux
Pages: 1, 2, 3, 4, 5

Implementing the Class

Let's start out by implementing IAWindowController with our action -changeScale:. This action method will work alongside another method of IAWindowController called -scaleImageTo:. The purpose of -scaleImageTo: is to handle higher-level interface actions related to scaling, while letting IAImageView execute the actual scaling machinery. -scaleImageTo: also performs error checking to make sure that the scale factor is positive and non-zero, neither of which make much sense in terms of zooming.

As part of this implementation we need to declare another instance variable in IAWindowController.h that will store the current scale factor:

float scale;

This variable will serve to keep a consistent zoom between various image operations (which will be added in later columns). Let's take a look at the implementation of -scaleImageTo: before getting to -changeScale:.

- (void)scaleImageTo:(float)_scale
    if ( _scale > 0 )
         scale = _scale;

    [view scaleFrameBy:scale];
    [zoomControl setFloatValue:(scale * 100)];

The first thing we do is check to see if the scale factor _scale is greater than 0; if so then the scale factor is valid and we set the value of the instance variable equal to the argument. If it is not greater than zero then the scale remains unchanged and the remainder of the method is carried out using the previous value of scale. Next we send a -scaleFrameBy: message to view, with scale as the argument. Note that -scaleFrameBy: is a method of our design, not one provided by NSImageView.

Finally, we set the float value of the zoomControl text field to scale * 100. We multiply by 100 because scale is a factor, not a percentage, that the zoom field displays. The reason we set the value of the zoom text field -- despite the fact that the scale we're setting it to was most likely taken from that very text field, and nothing will change -- is that there may be situations when -scaleImageTo: is invoked by something other than the user changing the zoom value in the interface. In these situations we want to keep the information displayed in the interface (the zoom percentage) consistent with the state of the application (the value of the scale instance variable).

Now, getting back to the -changeScale: action, we have the following implementation:

- (IBAction)changeScale:(id)sender
    [self scaleImageTo:([sender floatValue] / 100.0)];

Again, we divide by 100 for the same reason we multiplied by 100 above -- the control provides a percentage, and -scaleImageBy: wants a factor.

The last thing we want to do in IAWindowController is implement some form of initialization code. In NSWindowController subclasses, we can use the method -windowDidLoad for initialization in much the same way we use -awakeFromNib for initialization purposes.

Window controllers are created when documents are opened and a document's data has been loaded. So one thing we want to do in this method is get the document's image and display it. We do this using MyDocument's -activeImage method, and pass the returned image along to view for display. The other thing we want to do is set the initial zoom factor for images when they are first displayed. This is what -windowDidLoad looks like:

- (void)windowDidLoad
    NSImage *image = [[self document] activeImage];

    [view setImage:image];
    [self scaleImageTo:1.0];

Note in the first line how we can access the document object that owns the window controller object by sending a -document message to self. Next we send the newly obtained image object along to the image view using the method -setImage:. Finally, we do an initial scale operation to set the document zoom at 100%.

For the curious reader, just like there are -windowControllerDidLoadNib: and -windowControllerWillLoadNib: in NSDocument, NSWindowController has a -windowWillLoad to complement the -windowDidLoad method.

NSImageView -- Quick Tangent

With our discussion of IAWindowController finished, let's move on to talk about IAImageView, with a quick stop to look over its parent class, NSImageView. NSImageView is a direct subclass of NSControl, which is a subclass of NSView. Therefore, an image view is both a view, which we know something about, and a control, which we also know something about. With that we're already on familiar grounds regarding the capabilities of NSImageView since we know a bit about NSControl and NSView.

NSImageView only adds 10 methods beyond NSControl. The job of NSImageView is to display NSImage inside of an NSView frame (NSImage + NSView = NSImageView). As part of this job, NSImageView lets the developer define how the image should scale to fill the view's frame, as well as how the image should align within the view's frame. The default behavior is for images to scale proportionally to fill the view, and to align in the center of the view.

Another thing we gain with NSImageView is the ability to drag images into the view and have them display. This behavior won't be too useful for our purposes, but I have no doubt that it is to many people for many different applications. NSImageView allows us to define the style of decorative border around the image view. This is referred to as the frame style, but I'm reluctant to call it a frame since we will be talking quite a bit about frame rectangles for views. Finally, there are methods that allow us to set the image programmatically to be displayed in the view, as well as to retrieve that image object. We will use both of these methods later on.

It's not often that we get a complete synopsis of a class, but NSImageView is pretty straightforward as to what functionality it adds to its parent classes. So there you have NSImageView.

Implementing IAImageView

As we move along in our coding we come to the point where we implement IAImageView -- ever closer to the end! We already know from our work in IAWindowController that IAImageView must define a method called -scaleFrameBy:. As has been stated elsewhere, this method contains the machinery of the scaling operation (it is, however, fairly simple machinery).

To implement scaling we exploit the scaling behavior of the image view that we set in Interface Builder to automatically resize the image to fit the frame of the view. So if the image view's frame were larger than the native image size, then the image view would resize the image to make it fit in the larger frame. The same goes for a frame that is smaller than the image. Thus, by controlling the size of the image view's frame, we can control the size of the image displayed. This leads to an easy implementation of some scaling behavior. All we need to do in the scaling method is resize the view's frame relative to the image's size, according to the supplied scale factor.

This will be done by first obtaining the NSSize of the image view's image, and then applying a scaling affine transformation to that size. The scaled size is then used to set the size of the image view's frame, and we wrap things up by telling the view to redraw itself. In other words:

- (void)scaleFrameBy:(float)scale
    NSSize imageSize = [[self image] size];
    NSAffineTransform *at = [NSAffineTransform transform];
    [at scaleBy:scale];
    [self setFrameSize:[at transformSize:imageSize]];
    [self setNeedsDisplay:YES];

In the first line we create an NSSize variable and set its value to the size returned by a -size message to the view's image, which is accessed by sending an -image method to self.

Moving on we create an instance of NSAffineTransform and send it a -scaleBy: message with scale as the argument to this method. The -scaleFrameBy: method ends with a nested message in which we transform the NSSize imageSize according to our affine transform, using -transformSize:, and then use the return value of that method -- the scaled size -- as the argument of a -setFrameSize: message to self. Now the next time this view is redrawn the image will be drawn to fit the resized frame of the view. To finish things off we tell the view to redraw its contents so the image is drawn to fill the new frame.

This method too should be declared in IAImageView's header file.

Pages: 1, 2, 3, 4, 5

Next Pagearrow