NSView with gradient background

In Mac OS X 10.5 a new class NSGradient is introduced, that makes it really easy to work with gradients (as you might have guessed from the name). The sample code below shows a basic yet functional implementation of an NSView subclass that has either an colored or gradient background.

ColorGradientView.h

@interface ColorGradientView : NSView
{
  NSColor *startingColor;
  NSColor *endingColor;
  int angle;
}

// Define the variables as properties
@property(nonatomic, retain) NSColor *startingColor;
@property(nonatomic, retain) NSColor *endingColor;
@property(assign) int angle;

@end

ColorGradientView.m

#import "GradientView.h"
@implementation ColorGradientView

// Automatically create accessor methods
@synthesize startingColor;
@synthesize endingColor;
@synthesize angle;

- (id)initWithFrame:(NSRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    // Initialization code here.
    [self setStartingColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
    [self setEndingColor:nil];
    [self setAngle:270];
  }
  return self;
}

- (void)drawRect:(NSRect)rect {
  if (endingColor == nil || [startingColor isEqual:endingColor]) {
    // Fill view with a standard background color
    [startingColor set];
    NSRectFill(rect);
  }
  else {
    // Fill view with a top-down gradient
    // from startingColor to endingColor
    NSGradient* aGradient = [[NSGradient alloc]
        initWithStartingColor:startingColor
        endingColor:endingColor];
    [aGradient drawInRect:[self bounds] angle:angle];
  }
}

@end

Note that the code is based on the new garbage collection mechanism available in Objective-C 2.0, so there are no retain or release calls.

In the following picture you see a gradient effect from top to bottom to give a subtle 3d appearance:

To achieve this, add a NSView in Interface Builder and define its class as ColorGradientView. In your controller, add a ColorGradientView outlet (e.g. myGradientView) and in Interface Builder attach the view to the outlet. Add the following 3 lines to the - (void)awakeFromNib method in the controller:


[myGradientView setStartingColor:
    [NSColor colorWithCalibratedWhite:0.85 alpha:1.0]];
[myGradientView setEndingColor:
    [NSColor colorWithCalibratedWhite:0.7 alpha:1.0]];
[myGradientView setAngle:270];

In upcoming post I will go into details of how to create the Safari like buttons that you see in the screen shot.

9 Comments

  1. Psi says:

    “the code is based on the new garbage collection mechanism available in Objective-C 2.0, so there are no retain or release calls”

    Then what’s that autorelease doing there? ;)

  2. Stream says:

    It would be be useful to have [self setNeedsDisplay:YES], [self displayIfNeeded]; calls within custom setters :)

  3. Berrie says:

    Again, well spotted. I’ve corrected the property statement.

  4. Demitri says:

    Thanks for the code – this is nicely illustrative. One small error in the listing above: the third property statement should say “angle”, not “value”.

    Cheers,

    Demitri

  5. Berrie says:

    Well spotted, I’ve changed the example to fix these flaws.

  6. Joan Lluch says:

    As far as I understand the code has some memory management flaws (even if you are using garbage collection)

    [A] – The following line :

    startingColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];

    can potentially produce a released object access since the NSColor will be autoreleased in the next run loop, so you need to include [startingColor retain] to avoid it, or simply use [self setStartingColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]] ;

    The code actually works because you use [myGradientView setStartingColor: ] in your controller, but the above code is still faulty.

    [B] – The drawRect method produces a memory leak each time it is called (which may be potentially very often) because aGradient is never released. You must explicitly release it after using it (this is fine if garbage collection is enabled though)

    Regards, :)

    Joan

  7. Berrie says:

    Good point. Makes the code simpler and doesn’t distract from the core message.

    I’ve updated the code in the posting.

  8. David says:

    If you are using objc-c 2.0 then why not use properties? Get rid of the setters and have in interface:
    @property(copy) NSColor *startingColor;
    @property(copy) NSColor *endingColor;
    @property(assign) int angle;

    then in implementation section:
    @synthesize startingColor, endingColor, angle;

    But will be good to see article on using NSGradient with buttons.

Leave a Reply