oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

All About the Little Green Glob
Pages: 1, 2

Another thing to notice is that when the image is zoomed, the window dumbly does nothing to accommodate the changes in the information being displayed. Both of these things can be fixed with one simple line of code that takes advantage of window zooming.

Quit the application and open IAWindowController.m in Project Builder. At the very end of the method -scaleImageTo: append the single line of code:

[[self window] zoom:nil];

NSWindow's -zoom: method is the same method invoked when the user clicks on the window zoom button. We're invoking it directly in response to any zoom activity, and thus ensuring that the image is displayed as best as it can be. Effectively, the application pushes the green button for you whenever the scale of the image changes. The reason this takes care of the problem of the incorrectly sized initial window is the line of code executed in –windowDidLoad:

[self scaleImageTo:1.0];

By sending this message whenever the application opens a new document, we always zoom the window to fit the current image.

Now, compile and run the application again, and let's look at one other bit of behavior. Open an image and enter some large-scale value, such as 1000 percent, that will make the image much larger than the screen. Did you notice what happened? Cocoa's window-zooming mechanism knows when the window will be larger than the screen. If we return a standard frame that is larger than the useable area of the screen, which excludes the menu bar and the dock, the window will know and simply make itself as large as the useable area.

So, given this built-in behavior, why should we bother testing the size of stdFrame against the default frame ourselves? Compare the two images below. The image on the left uses the built-in size restriction mechanism; in the image on the right I compare stdFrame against defaultFrame. Do you see the difference?

Size restriction using the built-in behavior is shown on the top. Size restriction implemented by ourselves using the default frame is shown on the bottom.

Also in Programming with Cocoa

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Build an eDoc Reader for Your iPod, Part 3

The difference is subtle, but one that is important to me as a user. The difference is the presence of a thin (approximately 10 pixels) empty strip below the window. The default frame treats this area as off-limits, just like the dock and the menu bar. The utility of this is that we have a convenient spot to click through the windows or the desktop in the background. If you think this is important like I do, then continue reading as we implement our own version of size constraint using the default frame.

When taking the default frame into account there are two things to consider. First is the obvious case of stdFrame being larger than the default frame. We handle this case in the x and y directions independently. So if stdFrame is wider than the default frame, we set the width of stdFrame to the width of defaultFrame, and set the x origin of stdFrame to the origin of defaultFrame. The same goes for the vertical direction. If stdFrame is higher than defaultFrame, then we set the height of stdFrame and the y origin of stdFrame to those values of defaultFrame.

The second thing we must consider are situations when the size of stdFrame isn't larger than the default frame, but part of the window may be off the screen. We handle this by moving stdFrame back onto the window. Let's take a look at the modifications to our method to see how this all fits together:

- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender 
defaultFrame:(NSRect)defaultFrame { float stdX, stdY, stdW, stdH, defX, defY, defW, defH; // get the y-origin of the scroll view for use in computing newHeight int svOffset = [[[view superview] superview] frame].origin.y; NSSize viewSize = [view frame].size; float newHeight = viewSize.height + svOffset; float newWidth = viewSize.width; NSRect stdFrame = [NSWindow contentRectForFrameRect:[sender frame]
styleMask:[sender styleMask]]; stdFrame.origin.y += stdFrame.size.height; stdFrame.origin.y -= newHeight; stdFrame.size.height = newHeight; stdFrame.size.width = newWidth; stdFrame = [NSWindow frameRectForContentRect:stdFrame
styleMask:[sender styleMask]]; stdX = stdFrame.origin.x; stdY = stdFrame.origin.y; stdW = stdFrame.size.width; stdH = stdFrame.size.height; defX = defaultFrame.origin.x; defY = defaultFrame.origin.y; defW = defaultFrame.size.width; defH = defaultFrame.size.height; if ( stdH > defH ) { stdFrame.size.height = defH; stdFrame.origin.y = defY; } else if ( stdY < defY ) { stdFrame.origin.y = defY; } if ( stdW > defW ) { stdFrame.size.width = defW; stdFrame.origin.x = defX; } else if ( stdX < defX ) { stdFrame.origin.x = defX; } return stdFrame; }

The first thing added in this implementation over the previous one was the string of float variable declarations. These are used just after the previous method ended as a convenience in the following if statements. As you can see, we assign to each of these eight variables the values of the different parts of the two frames we wish to compare.

Following that are two if statements, conceptually alike, but operating on the two dimensions of the window. First we look at the vertical dimensions of the two frames. In the first if statement we check to see if the height of the standard frame is greater than the height of the default frame. If this is true, then we set the height of stdFrame to defH (which is the height of the default frame), and the vertical origin of stdFrame to that of the default frame (defY).

If this first conditional is false we move on to check whether the bottom of the standard frame is below the bottom of the screen. If it is, we reset the standard frame's origin to that of the default origin (again, only in the y direction), and move on. We don't have to worry about translating the origin of stdFrame up to defFrame's origin (making the top of the window higher than the screen) since in the first conditional we checked to make sure the height of stdFrame was less than defaultFrame.

So that's the logic behind using the default frame. What we did for the y direction is exactly how we do it for the x direction, except Ys are replaced by Xs and Hs by Ws. Go ahead and compile ImageApp and give it a run to try out this newest change.

Now that we know how window zooming works in Cocoa, we'll all have happier users of the software we create. If you're interested you can download the project containing the changes we made to ImageApp today. In the next column we're going to learn how we can further polish ImageApp.