Search code examples
cocoacore-graphicsnsimagensscrollviewnsbuttoncell

Prevent NSButtonCell image from drawing outside of its containing NSScrollView


I have asked (and answered) a very similar question before. That question was able to be solved because I knew the dirtyRect, and thus, knew where I should draw the image. Now, I am seeing the same behavior with an subclassed NSButtonCell:

enter image description here

In my subclass, I am simply overriding the drawImage:withFrame:inView method to add a shadow.

- (void)drawImage:(NSImage *)image withFrame:(NSRect)frame inView:(NSView *)controlView {   

    NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
    [ctx saveGraphicsState];

    // Rounded Rect
    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame cornerRadius:kDefaultCornerRadius];
    NSBezierPath *shadowPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 0.5, 0.5) cornerRadius:kDefaultCornerRadius]; // prevents the baground showing through the image corner

    // Shadow
    NSColor *shadowColor = [UAColor colorWithCalibratedWhite:0 alpha:0.8];
    [NSShadow setShadowWithColor:shadowColor
                  blurRadius:3.0
                      offset:NSMakeSize(0, -1)];
    [[NSColor colorWithCalibratedWhite:0.635 alpha:1.0] set];
    [shadowPath fill];
    [NSShadow clearShadow];

    // Background Gradient in case image is nil
    NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[UAColor grayColor] endingColor:[UAColor lightGrayColor]];
    [gradient drawInBezierPath:shadowPath angle:90.0];
    [gradient release];

    // Image
    if (image) {
        [path setClip];
        [image drawInRect:frame fromRect:CGRectZero operation:NSCompositeSourceAtop fraction:1.0];
    }

    [ctx restoreGraphicsState];

}

It seems all pretty standard, but the image is still drawing outside of the containing scrollview bounds. How can I avoid this?


Solution

  • You shouldn't call -setClip on an NSBezierPath unless you really mean it. Generally you want ‑addClip, which adds your clipping path to the set of current clipping regions.

    NSScrollView works by using its own clipping path and you're blowing that path away before drawing your image.

    This tripped me up for a long time too.