objective-cdrawrectnsbezierpathnstrackingarea

Objective-C | NSBezierPath draw and color on action


I have a custom view class with a tracking area. I want that, when the mouse enters the tracking area, a bezier is drawn with a color, and when the mouse exits the area the bezier disappear. In order to make it disappear I read that the only way is to change its color with the window background color.

I managed to add the tracking area, but I don't know how to draw the bezier. If I put the code in

-(void)drawRect:(NSRect)dirtyRect

it's drawn when the app launches, but I don't want so. I tried with this:

@implementation MSBezier

- (void) viewWillMoveToWindow:(NSWindow *)newWindow {
    // Setup a new tracking area when the view is added to the window.
    NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:NSMakeRect(164.5, 17.5, 270, 65) options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil];
    [self addTrackingArea:trackingArea];
}

- (void) mouseEntered:(NSEvent*)theEvent {
    NSLog(@"Entered");
    color = [NSColor colorWithCalibratedRed: 0.044 green: 0.813 blue: 0.044 alpha: 0.441];
    CGFloat rectangleCornerRadius = 31;
    NSRect rectangleRect = NSMakeRect(164.5, 17.5, 270, 65);
    NSRect rectangleInnerRect = NSInsetRect(rectangleRect, rectangleCornerRadius, rectangleCornerRadius);
    rectanglePath = NSBezierPath.bezierPath;
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 180 endAngle: 270];
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 270 endAngle: 360];
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMaxY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 0 endAngle: 90];
    [rectanglePath lineToPoint: NSMakePoint(NSMinX(rectangleRect), NSMaxY(rectangleRect))];
    [rectanglePath closePath];
    [color setStroke];
    [rectanglePath setLineWidth: 3];
    [rectanglePath stroke];

}

- (void) mouseExited:(NSEvent*)theEvent {
    NSLog(@"Exited");
    color = [NSColor colorWithCalibratedRed: 0.949 green: 0.949 blue: 0.949 alpha: 1];
    CGFloat rectangleCornerRadius = 31;
    NSRect rectangleRect = NSMakeRect(164.5, 17.5, 270, 65);
    NSRect rectangleInnerRect = NSInsetRect(rectangleRect, rectangleCornerRadius, rectangleCornerRadius);
    rectanglePath = NSBezierPath.bezierPath;
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 180 endAngle: 270];
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 270 endAngle: 360];
    [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMaxY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 0 endAngle: 90];
    [rectanglePath lineToPoint: NSMakePoint(NSMinX(rectangleRect), NSMaxY(rectangleRect))];
    [rectanglePath closePath];
    [color setStroke];
    [rectanglePath setLineWidth: 3];
    [rectanglePath stroke];
}

@end

But the bezier isn't drawn.

Thanks for your help!

EDIT @uchuugaka

This is the code so far, which doesn't seem to do anything:

@implementation MSBezier

bool shouldDrawMyPath = YES;
NSBezierPath *rectanglePath;

- (void)viewWillDraw {

    if (shouldDrawMyPath == YES) {
        NSColor *color = [NSColor colorWithCalibratedRed: 0.044 green: 0.813 blue: 0.044 alpha: 0.441];
        CGFloat rectangleCornerRadius = 31;
        NSRect rectangleRect = NSMakeRect(164.5, 17.5, 270, 65);
        NSRect rectangleInnerRect = NSInsetRect(rectangleRect, rectangleCornerRadius, rectangleCornerRadius);
        rectanglePath = NSBezierPath.bezierPath;
        [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 180 endAngle: 270];
        [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMinY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 270 endAngle: 360];
        [rectanglePath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMaxY(rectangleInnerRect)) radius: rectangleCornerRadius startAngle: 0 endAngle: 90];
        [rectanglePath lineToPoint: NSMakePoint(NSMinX(rectangleRect), NSMaxY(rectangleRect))];
        [rectanglePath closePath];
        [color setStroke];
        [rectanglePath setLineWidth: 3];
    } else {
        rectanglePath = nil;
    }

}

- (void)drawRect:(NSRect)dirtyRect {
    [[NSColor clearColor] set];
    NSRectFill(self.bounds);

    if (shouldDrawMyPath == YES) {
        [rectanglePath stroke];
    }
}

- (void) viewWillMoveToWindow:(NSWindow *)newWindow {
    NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:NSMakeRect(164.5, 17.5, 270, 65) options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil];
    [self addTrackingArea:trackingArea];
}

- (void) mouseEntered:(NSEvent*)theEvent {
    NSLog(@"Entered");
    shouldDrawMyPath = YES;
    [self setNeedsDisplay:YES];
}

- (void) mouseExited:(NSEvent*)theEvent {
    NSLog(@"Exited");
    shouldDrawMyPath = NO;
    [self setNeedsDisplay:YES];
}

@end

I'm sure I'm doing something wrong.

EDIT 2

I just needed to set the color in drawRect:. So:

-(void)drawRect:(NSRect)dirtyRect {
    if (shouldDrawMyPath == YES) {
        NSColor *color = [NSColor colorWithCalibratedRed: 0.044 green: 0.813 blue: 0.044 alpha: 0.441];
        [color setStroke];
        [rectanglePath stroke];
    }
}

Solution

  • So this is pretty simple. You need to rearrange things. First create a BOOL property like shouldDrawMyPath this should default to NO.

    Next, in mouseEntered: Set that to YES Call self setNeedsDisplay:YES

    Next, mouseExited: Set that to NO Call self setNeedsDisplay:YES

    Add an NSBezierPath property. In viewWillDraw If shouldDrawMyPath == YES Set up your bezier path. Else set it to nil ( I am assuming your view might resize any time) If your view never resizes, you can create the path once.

    In drawRect First clear the board, especially if your view could resize. [[NSColor clearColor] set]; NSRectFill(self.bounds);

    Draw anything always there if anything.

    If shouldDrawMyPath == YES Fill and/or stroke your path. Else Do any other drawing without the path.

    As long as you set up your tracking area correctly this should get you going.

    Depending on specifics there are always optimizations.

    Never draw outside of drawRect unless you know what you're doing.

    Keep your drawRect code simple.

    Only draw what you need to when you need to. (You're not there yet, but there are a lot of optimizations that can be done to invalidate and only draw particular rects)