1
votes

I've got a relatively simple Cocoa on Mac OS X 10.6 question to ask. I have a main NSView (ScreensaverView, actually) that is not layer backed and is otherwise unremarkable. It does some basic drawing in its drawRect via NSRectFill and NSBezierPath:stroke calls (dots and lines, basically).

I've also got a NSView-derived subclass that is acting as a child subview. I'm doing this with the goal to draw simple lines in the subview that draw on top of whatever is drawn in the main view, but then can be somehow "erased" revealing whatever the lines obscured. The code for this subview is quite simple:

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)dealloc
{   
    [super dealloc];
}

- (void)drawRect:(NSRect)dirtyRect {

    // Transparent background
    [[NSColor clearColor] set];
    NSRectFillUsingOperation(dirtyRect, NSCompositeCopy);

    // If needed for this update, draw line
    if (drawLine) {
        // OMITTED: Code that sets opaque NSColor and draws a line using NSBezierPath:stroke
    }

    // If needed for this update, "erase" line
    if (eraseLine) {
        [[NSColor blackColor] set]; // clearColor?
        // OMITTED: Code that draws the same line using NSBezierPath:stroke
    }
}

With the code as shown above, any time the subview draws, the main view goes black and you only see the subview line. When the subview isn't being updated, the main view contents appear again.

Things I've tried, with varying results:

  • I tried experimenting with making the subview return YES from an overridden isOpaque (which I realize isn't really correct). When I do this, both views draw properly during a subview update, however the subview line overwrites anything it is drawn on (ok) and then when erased, also leaves a large black line where it was (what I was trying to avoid). Trying to "erase" the line using clearColor instead of blackColor results in the line remaining on screen.

  • I tried making both (and/or just the subview) layer-backed views via calling [self setWantsLayer:YES] in init, and this results in a completely black screen.

I feel like I'm missing something really basic, but for whatever reason, I can't seem to figure it out. Any and all suggestions are greatly appreciated.

1

1 Answers

2
votes

I think you are fundamentally misunderstanding how view drawing works. Basically, every time drawRect: is called on your view, the drawing commands in drawRect: are executed. This might happen automatically or when you issue the view a setNeedsDisplay: message.

Normally, when drawRect: is called on the view, the view is erased and drawing begins anew. Nothing remains in the view from the previous call to drawRect:. You do not need to fill the view with [NSColor clearColor] in order for it to be transparent.

Note that this is only the case if your view returns NO from isOpaque, which is the default. If your view is returning YES from isOpaque then you will need to ensure you erase the view before drawing, however in your particular case you should not return YES from isOpaque because you want your view to be transparent.

You do not need to "erase" the line you've drawn. It will be erased for you. Instead, you need to simply not draw it when you don't want it drawn.

Basically, your view should store some flag (such as your BOOL ivar named drawLine or something similar). Then, in your drawing code you should simply check if this value is set. If it is, you should draw the line. If not, then just do nothing. Your code should be reduced to this:

- (void)drawRect:(NSRect)rect
{
    // If needed for this update, draw line
    if (drawLine) {
        // OMITTED: Code that sets opaque NSColor and draws a line using NSBezierPath:stroke
    }
}

If you want to change the state and redraw the view, all you need to do is change the value of the drawLine ivar and ask the view to redraw using [yourView setNeedsDisplay:YES].

You can easily do this with a timer:

- (void)awakeFromNib
{
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(update:) userInfo:nil repeats:YES];
}

- (void)update:(NSTimer*)timer
{
    drawLine = !drawLine;
    [self setNeedsDisplay:YES];
}