Manipulating Cells and Controls

This article contains miscellaneous tips and examples for manipulating cells and controls.

Setting the Size of a Cell or Control

To set the size of a cell (and any enclosing single-cell control) to an optimum size conforming with the human interface guidelines, do the following:

  1. If the cell contains text, set the font of the text to be consistent with one of the three standard sizes: regular, small, and mini. To do this use the NSFont class method systemFontSizeForControlSize:. The argument to this method is an NSControlSize constant declared by the NSControl class.

    float fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
    NSCell *theCell = [theControl cell];
    NSFont *theFont = [NSFont fontWithName:[[theCell font] fontName] size:fontSize];
    [theCell setFont:theFont];
  2. Set the control size to the same size as given for the font size, using the same constant. Use the NSControl setControlSize: method.

    [theCell setControlSize:NSMiniControlSize];
  3. Finally, send sizeToFit to the control to trim the extra width.

    [theControl sizeToFit];

Drawing a Focus Ring Inside a Cell's Bounds

The NSSetFocusRingStyle sets the style that a focus ring will be drawn in the next time you fill a bezier path. It takes a constant of type of NSFocusRingPlacement to tell the Application Kit to draw the focus ring over an image, under text, or (when neither text or image is a consideration) around a shape. You can use this function with a constant of NSFocusRingOnly to draw a focus ring just inside a cell's bounds.

Listing 1 shows how you draw such a focus ring. It requires you to override the NSCell drawWithFrame:inView:. In this method, if the cell is supposed to draw evidence of first-responder status, set the rectangle for the focus ring, call NSSetFocusRingStyle with an argument of NSFocusRingOnly, and then create and fill a bezier path defining that rectangle. Filling in this case simply draws the ring.

Listing 1  Drawing a focus ring just inside a cell's bounds

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
    // other stuff might happen here
    if ([self showsFirstResponder]) {
         // showsFirstResponder is set for us by the NSControl that is drawing  us.
        NSRect focusRingFrame = cellFrame;
        focusRingFrame.size.height -= 2.0;
        [NSGraphicsContext saveGraphicsState];
        NSSetFocusRingStyle(NSFocusRingOnly);
        [[NSBezierPath bezierPathWithRect: NSInsetRect(focusRingFrame,4,4)] fill];
        [NSGraphicsContext restoreGraphicsState];
    }
     // other stuff might happen here
}

Positioning an Image Within a Cell

If a cell is to display an image instead of (or in addition to) text, you can affect the placement of the image within the cell by overriding the imageRectForBounds: method, which is declared by both the NSCell and NSMenuItemCell classes. This method returns the rectangle the cell's image is drawn in, which is usually a rectangle slightly offset from the cell's bounds. Listing 2 gives an example.

Listing 2  Centering an image in its cell

- (NSRect) imageRectForBounds:(NSRect)theBounds {
    NSRect r = theBounds;
    // get size. If no image, result of method returning NSSize is undefined so assume NSZeroSize
    NSSize imageSize = [self image] != nil ? [[self image] size] : NSZeroSize;
    r.origin.x = floor((r.size.width/2)  - (imageSize.width/2)  + 0.5);
    r.origin.y = floor((r.size.height/2) - (imageSize.height/2) + 0.5);
    r.size     = imageSize;
    return r;
}

In this example, the cell centers the image in the cell. Note that it rounds the return values to the nearest pixel to avoid blurring that drawing with partial pixel offsets may cause. The code also sets the size field of the returned rectangle to the size of the image so that it is correctly drawn in the rectangle (assuming the NSImage object uses drawInRect:fromRect:operation:fraction: for drawing and not compositeToPoint:operation:).