views:

257

answers:

3

I'd like to change the fill of a button that I've drawn (I subclassed NSButton)

Here's the code I've got already:

- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
// Create the Gradient 
NSGradient *fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
// Create the path
aPath = [NSBezierPath bezierPath];

[aPath moveToPoint:NSMakePoint(10.0, 0.0)];
[aPath lineToPoint:NSMakePoint(85.0, 0.0)];
[aPath lineToPoint:NSMakePoint(85.0, 20.0)];
[aPath lineToPoint:NSMakePoint(10.0, 20.0)];
[aPath lineToPoint:NSMakePoint(0.0, 10.0)];
[aPath lineToPoint:NSMakePoint(10.0, 0.0)];

[fillGradient drawInBezierPath:aPath angle:90.0];
[fillGradient release];
}

- (void)mouseDown:(NSEvent *)theEvent {
    NSGradient *fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
    [fillGradient drawInBezierPath:aPath angle:-90.0];
}

and I get a EXC_BAD_ACCESS signal. How would I do this ?

A: 

Retain aPath after you create it, since it will have been autoreleased by the time the mouseDown: message is sent. Just make sure to release it in your class's dealloc or wherever else that it would be appropriate.

Edit:

Refer to Matt Ball's answer instead.

dreamlax
+4  A: 

The reason for the EXC_BAD_ACCESS is that the following line:

aPath = [NSBezierPath bezierPath];

creates an autoreleased bezierPath which will be released at the end of the current iteration of the run loop. To avoid the error, you'd need to change it to:

aPath = [[NSBezierPath bezierPath] retain];

However, you're approaching the problem from the wrong direction. Drawing should only be done in the -drawRect: method (or methods which are only called from -drawRect:). Instead of trying to draw in your mouseDown: method, you should create a new BOOL instance variable for your class (called, for instance, mouseIsDown) and set that in mouseDown:. Then use that boolean to determine how to fill the button:

- (void)drawRect:(NSRect)aRect {
    NSGradient *fillGradient = nil;
    if (mouseIsDown)
        fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
    else
        fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];

    // Do the rest of your drawRect method

}

- (void)mouseDown:(NSEvent *)theEvent {
    mouseIsDown = YES;
    [self setNeedsDisplay:YES];
}

- (void)mouseUp:(NSEvent *)theEvent {
    mouseIsDown = NO;
    [self setNeedsDisplay:YES];
}
Matt Ball
A: 

This isn't directly related to your question, but your question's title belies a false assumption that I want to address.

Changing the fill to a already drawn NSBezierPath

That's not possible.

In Cocoa, “fill” is a verb, not a property of an object like it is in Illustrator or Lineform. You do not set the fill of a path, nor change it later; you fill the path, and what you change by this is pixels in some unseen backing store. The real-world analogy would be setting up splines on a flat pane of glass, then filling the area bounded by the splines with paint and letting it dry. The “fill” is not a property of the splines; it's the act of pouring paint into the shape they define.

As with the analogy, you cannot remove or alter a fill you have previously done—the paint is stuck to the glass; there's no getting it off*. The only way to accomplish that effect is to re-do the fill with some other color. You can move the splines (create a new path) or add splines (add an intersecting subpath to the existing path) before filling, if you want to only change part of the existing drawing.

All of that applies to all drawing operations, including stroking paths and drawing raster images. Text drawing, too, as that's just another case of filling and/or stroking a path.

*OK, you probably could remove real paint from real glass, either chemically or by scraping. No analogy is perfect. ☺

Peter Hosey