TweetFollow Us on Twitter

Dec 01 Mac OS X

Volume Number: 17 (2001)
Issue Number: 12
Column Tag: Mac OS X

by Andrew Stone

Cocoamotion

Getting more for less with Cocoa.

Ever since ‘89 I’ve been developing with the precursor to Cocoa, NeXTStep & OpenStep. In each release, new features and new concepts have been added to the Application and related frameworks. making life even easier for the third party developer. The more reusable software components that Apple provides, the less code I have to write and maintain. Imagine being able to remove more and more code from your applications! Today I removed hundreds of lines of code in my Image drawing class by using the new NSImage drawing methods which respect the current transformation matrix.

This article goes over a few ways to eradicate code that you may have accrued by using new objects and new features in the old ones. We’ll look at a simplified Image drawing object, the NSStepper, and the fairly new drag methods in NSOutlineView and NSTableView.

NSImage Gets An Upgrade

Transformations

Before Mac OS X 10.0, NSImage would draw ignoring the current transformation matrix. Now this is fine if you have a simple list of graphics - but if you have nested layers of scaled, rotated, skewed groups, it’s a royal pain in the patooti to micromanage image drawing based on depth of a hierarchy!

For compatibility with older applications, the existing NSImage drawing methods such as compositeToPoint: alway draw with only the origin of the image transformed. The image itself is drawn ignoring scale and rotation transforms with the origin at the lower left. While it has been possible to draw with the current transform by getting one of the image’s representation and calling it’s draw method, two new methods have been added to NSImage that do this for you.

- (void)drawAtPoint:(NSPoint)point fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op 
  fraction:(float)delta;
  
- (void)drawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op 
  fraction:(float)delta;

If you pass in NSZeroRect for the fromRect: parameter, then the whole image is used. For drawInRect:, the image will be scaled to fit in the destination rectangle as well as transformed with the context’s current transform. Please note that if you use these routines in a flipped view, you will need to ‘undo’ the negative y scale factor and adjust the origin before calling the new methods:

      // make a new transform:
                NSAffineTransform *t = [NSAffineTransform transform];
      
      // by scaling Y negatively, we effectively flip the image:
                [t scaleXBy:1.0 yBy:-1.0];
      
      // but we also have to translate it back by its height:
                [t translateXBy:0.0 yBy:-_bounds.size.height];
      
      // apply the transform:
                [t concat];

Now you can have groups of images that are scaled and skewed, and these transformations will automatically be correctly applied to the image when it is rendered. Total lines of code: 5. Number of lines of code replaced: 250. Improvement of rendering speed: 2x. Reliability and correctness were improved greatly.

Transparency:

Using these new methods, you can add a slider to your interface which controls the transparency of the image, because the new methods take a fractional dissolve, from 0.0 fully transparent to 1.0 fully opaque. Let’s say you store the amount of transparency as a float in _dissolve - then your call to render the image would be:

   // we have translated already to the origin of the image in its current group
   // _dissolve varies from 0 for no dissolve to 1 for fully transparent:
            [image drawInRect:NSMakeRect(0.0,0.0,_bounds.size.width,_bounds.size.height) 
            fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 - _dissolve];

PDF Optimization:

In 10.1 and earlier, there is an “over-optimization” bug in Apple’s implementation of the NSPDFImageRep whereby it draws from its bitmap cache in certain circumstances which are incorrect. For example, when you print, you want the full resolution PDF, not a standin bitmap image which will produce jaggies. Also, when you zoom in, you want the PDF to render beautifully at that scale. You might want to test if you are printing or saving or at a high level zoom, and render the image directly from the PDF data:

      // if image is a pdf, and we are printing or saving or need the actual data:

            if (needToUseRealPDFData) {
          // grab the image’s best representation, the actual NSPDFImageRep:
                NSPDFImageRep *rep = [_image bestRepresentationForDevice:nil];
         // ask it to draw within our bounds:
                [rep drawInRect:NSMakeRect(0.0,0.0,_bounds.size.width,_bounds.size.height)];
            } else 
                  [image drawInRect:NSMakeRect(0.0,0.0,_bounds.size.width,_bounds.size.height) 
                  fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 - _dissolve];

The trick to get zoomed PDF to render onscreen at full quality is to multiply the size of the PDF image to the zoom:

   // helper method to return an integral size of an image, based on the zoom:
   
    - (NSSize)sizeForZoom:(float)zoom {
       NSSize s = [_image size];
       if (zoom == 1.0 || zoom == 0.0) return s;
       // images cannot be fractional sizes, so we round down:
       return NSMakeSize(floor(s.width * zoom), floor(s.height * zoom));
    }
    
          // Before drawing the image, we check if we are zoomed:
         // if our image is a PDF, and we’re drawing to screen, get a bigger image:
         
            if (_isPDF &&  [[NSGraphicsContext currentContext] isDrawingToScreen]  &&  zoom != 1.0) {
                NSSize s = [self sizeForZoom:zoom];
                if (_zoomedPDFImage == nil) {
                    _zoomedPDFImage = [_image copyWithZone:[self zone]];
             }
             if ( !NSEqualSizes([_zoomedPDFImage size], s))
                    [_zoomedPDFImage setSize:s];
                image = _zoomedPDFImage;
            } else {
                image = _image;
            }

NSStepper

An NSStepper provides what is known in Carbon as ‘little arrows’. These are common controls for date and time entries, but are also useful for any UI component that has values which can be “tweaked” a little bit. For example, we use them in Create to allow small adjustments to scale:


The ‘little arrows’ are a new addition to Cocoa, but have been around Carbon for awhile.

Like most control subclasses, NSStepper relies on the behavior of its custom NSStepperCell for its functionality. You don’t even need to know to this to use the stepper because the control has cover methods for the standard cell methods. Besides methods familiar from slider controls (setMaxValue:, setMinValue:, setAutorepeat:), the NSStepper allows you to set the granularity of an increment (or a decrement if the lower arrow is clicked) with setIncrement:.

You can also set whether the values loop around (as would be true in the case of rotation stepper) or not with setValueWraps:. In the example of a scale slider, for example, it makes no sense to go from the maximum value to the minimum value with one click!

Using the NSStepper in your interface consists of two parts: initializing the stepper for your requirements and keeping the value of the stepper in synch with data model, since it needs to know the original value in order to increment or decrement the value. You can initialize the stepper when loading the UI components for the first itme or directly in Interface Builder. This example is for a rotational slider: When loading the UI components the first time, you might initialize the values of the stepper, in this example, for a rotational slider. You can also set these values directly in Interface Builder, or programmatically in the File Owner’s class in awakeFromNib, which is called by the nib loading mechanism if this method exists:

- (void)awakeFromNib {
   [stepper setMinValue:0.0];
   [stepper setMaxValue:360.0];
   [stepper setIncrement:1.0];
   [stepper setValueWraps:YES];
   [stepper setAutorepeat:YES];
       // target/action set in IB
}

When inspecting a graphic that can be rotated, you reload the stepper with the current value of the graphic’s rotation:

   double currentRotation = [graphic rotation];
    [circularSlider setDoubleValue: currentRotation];
      [rotationStepper setDoubleValue: currentRotation];
   ...

The stepper handles all mousedowns and it knows whether it needs to increment or decrement the value. Let’s say you have connected the stepper to an action “incrementDecrementAction:”: The stepper has set its internal value to its incremented or decremented value, depending where the user clicked.

- (void)incrementDecrementAction:(id)aStepper {
    [self setSomeValue:[aStepper doubleValue]];  
      // take the value from the stepper
    [self reloadInspectingUI];   
         // update textfields, sliders, etc with new values 
}

The NSStepper is an excellent addition to the Cocoa developer’s toolkit, and both saves some lines of code as well as provide a standard mechanism for tweaking UI values.

Drag & Drop Support in NSTableView and NSOutlineView

The refinement of the Application framework is an ongoing process. For example, a few years ago we had to write our own code for drag and drop row reordering of table views and outlineviews (http://www.stone.com/GIFfun/GIFfunSource/Row_Moving_Protocol.html). Now, that code can be thrown away, and you can use the exposed API to accomplish this:

typedef enum { NSTableViewDropOn, NSTableViewDropAbove } NSTableViewDropOperation;

// In drag and drop, used to specify a dropOperation. For example, given a table with N rows 
(numbered with row 0 at the top visually), a row of N-1 and operation of NSTableViewDropOn would 
specify a drop on the last row. To specify a drop below the last row, one would use a row of N and 
NSTableViewDropAbove for the operation.

- (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard;
    // This method is called after it has been determined that a drag should begin, but before the drag 
    has been started.  To refuse the drag, return NO.  To start a drag, return YES and place the drag 
    data onto the pasteboard (data, owner, etc...).  The drag image and other drag related information 
    will be set up and provided by the table view once this call returns with YES.  The rows array is 
    the list of row numbers that will be participating in the drag.

- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info 
   proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op;
   
    // This method is used by NSTableView to determine a valid drop target.  Based on the mouse 
    position, the table view will suggest a proposed drop location.  This method must return a value 
    that indicates which dragging operation the data source will perform.  The data source may 
    “re-target” a drop if desired by calling setDropRow:dropOperation: and returning something other 
    than NSDragOperationNone.  One may choose to re-target for various reasons (eg. for better visual 
    feedback when inserting into a sorted position).

- (BOOL)tableView:(NSTableView*)tv acceptDrop:(id )info row:(int)row 
   dropOperation:(NSTableViewDropOperation)op;
    // This method is called when the mouse is released over an outline view that previously decided to 
    allow a drop via the validateDrop method.  The data source should incorporate the data from the 
    dragging pasteboard at this time.

Our free application, GIFfun, allows users to reorder the individual images that make up the final animated GIF. Here’ s a sample implementation of the Drag and Drop support in an NSTableView:

static int _moveRow = 0;

- (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard 
{
    int count = [gifFileArray count];
    int rowCount = [rows count];
    if (count < 2) return NO; 

    [pboard declareTypes:[NSArray arrayWithObject:GifInfoPasteBoard] owner:self];
    [pboard setPropertyList:rows forType:GifInfoPasteBoard];
    _moveRow = [[rows objectAtIndex:0]intValue];
    return YES;
}

- (unsigned int)tableView:(NSTableView*)tv validateDrop:(id )info 
   proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op 
{
    if (row != _moveRow) {
        if (op==NSTableViewDropAbove) {
            return NSDragOperationAll;
        }
        return NSDragOperationNone;
    }
    return NSDragOperationNone;
}

- (BOOL)tableView:(NSTableView*)tv acceptDrop:(id )info row:(int)row 
   dropOperation:(NSTableViewDropOperation)op 
{
   BOOL result = [self tableView:tableView didDepositRow:_moveRow at:(int)row];
   [tableView reloadData];
   return result;
}

// here we actually do the management of the data model:

-  (BOOL)tableView:(NSTableView *)tv didDepositRow:(int)rowToMove at:(int)newPosition
{
   if (rowToMove != -1 && newPosition != -1) {
      id object = [gifFileArray objectAtIndex:rowToMove];
      if (newPosition < [gifFileArray count] - 1) {
         [gifFileArray removeObjectAtIndex:rowToMove];
         [gifFileArray insertObject:object atIndex:newPosition];
      } else {
         [gifFileArray removeObjectAtIndex:rowToMove];
         [gifFileArray addObject:object];
      }
      return YES;   // ie reload
   }
   return NO;
}

These few lines listed replace the 650 lines of code from SDTableView.h, SDTableView.m and SDMovingRowsProtocol.h. The complete source code to GIFfun is available at http://www.stone.com/GIFfun/.

Adding Features By Doing Nothing

And, if my Zen slacker coding model isn’t convincing enough, consider how you can add new features by doing nothing!

If you are using the Model-View-Controller pattern and taking advantage of the NSDocument architecture, then the introduction of Mac OS X 10.1 brought you new features, without you doing anything. Because your application links at runtime with the current version of the Application and Foundation frameworks, new features available in the kits become available to your users.

10.1 adds Hidden file extensions (which is way more Mac-like than the UNIX file extensions), ability to track documents, folders, and volumes which are renamed, and ability to save documents in a way which preserves aliases to the documents and additional document info such as maintaining file permissions, creation date, and icon settings.

Some of these features do require that you recompile your application, because the Apple engineers wanted to provide full backwards binary compatibility in programs that were linked against the Mac OS 10.0 version of the Cocoa framework.

Conclusion

Software is a process not a product, and therefore it is never finished! The frameworks that Cocoa developers rely on are continually being refined and improved, so it’s very important to carefully read the Release Notes found in /Developer/Documentation/ReleaseNotes, especially AppKit.html and Foundation.html. Always check for new functionality which can replace your home-rolled versions, thus making your application more compact and reducing the code base that you need to support.


Andrew Stone andrew@stone.com is the chief executive haquer at Stone Design Corp http://www.stone.com and divides his time between raising children, llamas & cane and writing applications for Mac OS X.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Dispatch is bringing its superhero shena...
If you were anywhere online last year, then you would have no doubt heard of Dispatch, and been flooded with videos of it. With its star-studded cast and unrelenting humour, it fast became a hit, racking up over four million downloads. And it is a... | Read more »
5 reasons to sink your teeth into VAMPIR...
On October 28, Netmarble will globally launch VAMPIR, its latest and most macabre MMORPG, plunging players into a Gothic world of vampires, sorcerers and cursed creatures of the night. Built around blood, fear and large scale battles, the MMORPG... | Read more »
Gundam Blitz Order opens registration fo...
It would appear that video game developers are delving into my childhood and making idle games from my favourite TV shows. First, Bandai completely whiffed with Digimon Up, perhaps the single biggest miss of a beloved franchise in recent memory. Now... | Read more »
The Traitors Interactive Game lets you l...
With the second season of The Celebrity Traitors in full swing, the world is once more enthralled by just how good a liar our heroes are. I don’t watch it, but I hope James Blunt wins. The premise does intrigue me, however, and thanks to Fusebox... | Read more »
Dissidia Duellum Final Fantasy brings in...
For all his successful manipulations and successes through Final Fantasy 9, I never really liked Kuja. And it's not like I dislike him; more I nothing him. But that's just me; the vast majority of the fandom adore him, and they are in luck, for Kuja... | Read more »
Combo Quest (Games)
Combo Quest 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Combo Quest is an epic, time tap role-playing adventure. In this unique masterpiece, you are a knight on a heroic quest to retrieve... | Read more »
Hero Emblems (Games)
Hero Emblems 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: ** 25% OFF for a limited time to celebrate the release ** ** Note for iPhone 6 user: If it doesn't run fullscreen on your device... | Read more »
Puzzle Blitz (Games)
Puzzle Blitz 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Puzzle Blitz is a frantic puzzle solving race against the clock! Solve as many puzzles as you can, before time runs out! You have... | Read more »
Sky Patrol (Games)
Sky Patrol 1.0.1 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.1 (iTunes) Description: 'Strategic Twist On The Classic Shooter Genre' - Indie Game Mag... | Read more »
The Princess Bride - The Official Game...
The Princess Bride - The Official Game 1.1 Device: iOS Universal Category: Games Price: $3.99, Version: 1.1 (iTunes) Description: An epic game based on the beloved classic movie? Inconceivable! Play the world of The Princess Bride... | Read more »

Price Scanner via MacPrices.net

B&H Slashes $400 Off 14″ M5 Pro MacBook P...
B&H has launched a substantial new discount on Apple’s 14-inch M5 Pro MacBook Pro, taking $400 off the Space Black configuration with 24GB RAM and a 1TB SSD. Regularly priced at $2499, the 14-... Read more
Take $50 off iPad at Amazon, prices start at...
Amazon again has 11th-generation A16 WiFi iPads in stock on sale for $50 off MSRP, starting at $399. Shipping is free: – 11″ 11th-generation 128GB WiFi iPads: $399 $50 off MSRP – 11″ 11th-generation... Read more
Amazon Lowers 13″ M5 MacBook Air to $1149, No...
Amazon has increased its discounts on Apple’s 13-inch M5 MacBook Air, dropping the standard 16GB/512GB configuration to just $1149. Apple sells this configuration for $1299, so Amazon’s latest price... Read more
Amazon and B&H Slash Mac mini Prices, M6...
Amazon and B&H are offering new discounts on Apple’s M6 and M5 Pro Mac minis, with savings reaching $149.01 and prices starting at just $779. Configuration Apple MSRP Retailer Sale Price Savings... Read more
Amazon Takes Up to $150 Off 15″ M5 MacBook Ai...
Amazon has launched new discounts on Apple’s 15-inch M5 MacBook Airs, with savings of up to $150 and prices starting at just $1399. Configuration Apple MSRP Amazon Price Savings 15″ M5 16GB/512GB $... Read more
Amazon Takes Up to $150 Off 13″ M5 MacBook Ai...
Amazon has launched a new round of discounts on Apple’s 13-inch M5 MacBook Airs, with savings of up to $150 and prices starting at just $1199. Configuration Apple MSRP Amazon Price Savings 13″ M5... Read more
Verizon Launches iPhone Duo Pre-Orders With N...
Verizon has opened pre-orders for Apple’s new iPhone Duo, the company’s first foldable iPhone, with prices starting at $1999.99 and a new no-trade-in promotion available through its Simplicity Plan.... Read more
Amazon Drops 14″ M5 Max MacBook Pro to $3299,...
Amazon has dropped the price of Apple’s 14-inch M5 Max MacBook Pro with 36GB RAM and a 2TB SSD to $3299, saving a substantial $800 off Apple’s $4099 MSRP. This is one of the strongest discounts we’ve... Read more
Amazon Drops 14″ M5 Pro MacBook Pro with 2TB...
Amazon has dropped the price of Apple’s 14-inch M5 Pro MacBook Pro with 24GB RAM and a 2TB SSD to just $2399, saving a substantial $600 off Apple’s $2999 MSRP. This is one of the best discounts we’ve... Read more
B&H Takes Up to $220 Off New M5 Max Mac S...
B&H has launched new discounts on Apple’s M5 Max Mac Studio, with savings of up to $220 and prices starting at just $2299. Configuration Regular Price B&H Price Savings M5 Max 18-Core CPU/32-... Read more

Jobs Board

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.