I am developing an app that contains lots of custom NSView objects being moved around. I have implemented a gaussian blur background filter for one of the custom NSView subclasses like so:
- (id)init {
self = [super init];
if (self) {
...
CIFilter *saturationFilter = [CIFilter filterWithName:@"CIColorControls"];
[saturationFilter setDefaults];
[saturationFilter setValue:@.5 forKey:@"inputSaturation"];
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[blurFilter setDefaults];
[blurFilter setValue:@2.0 forKey:@"inputRadius"];
self.wantsLayer = YES;
self.layer.backgroundColor = [NSColor clearColor].CGColor;
self.layer.masksToBounds = YES;
self.layer.needsDisplayOnBoundsChange = YES;
self.layerUsesCoreImageFilters = YES;
[self updateFrame]; //this is where the frame size is set
self.layer.backgroundFilters = @[saturationFilter, blurFilter];
...
return self;
}
else return nil;
}
This works great and creates a gaussian blur effect within the entire contents of the view. The problem is that I do not want the gaussian blur to cover the entire view. There is about an (intentional) 12px padding between the actual size of the NSView and the drawing of its content box:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSColor* strokeColor = [NSColor colorWithRed:.5 green:.8 blue:1 alpha:1];
NSColor* fillColor = [NSColor colorWithRed:.5 green:.8 blue:1 alpha:.2];
...
[strokeColor setStroke];
[fillColor setFill];
NSBezierPath *box = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(self.bounds.origin.x + 12, self.bounds.origin.y + 12, self.bounds.size.width - 24, self.bounds.size.height - 24) xRadius:6 yRadius:6];
box.lineWidth = 6;
[box stroke];
[box fill];
...
}
The reason for this padding is that there are some pieces of the GUI that inhabit this region and are drawn seamlessly into the containing box. I would like to mask the Blur effect to only have effect on the interior of the drawn box rather than the entire view. Here is what I have tried.
ATTEMPT 1: Create a sublayer
I created a sublayer in the NSView with the appropriately sized frame, and added the blur effect to this sublayer. PROBLEM: The blur effect seems to only apply to the immediate parent layer, so rather than blur the contents behind the NSView, it blurs the contents of the NSView's self.layer (which is basically empty).
ATTEMPT 2: Create a masking layer
I tried to create a masking layer and set it to self.layer.mask. However, since the positions of the GUI content do change (via the DrawRect function), I would need to get a copy of the current layer to use as the masking layer. I tried the following code, but it had no effect.
self.layer.mask = nil;
NSArray *bgFilters = self.layer.backgroundFilters;
self.layer.backgroundFilters = nil;
CALayer *maskingLayer = self.layer.presentationLayer;
self.layer.mask = maskingLayer;
self.layer.backgroundFilters = bgFilters;
ATTEMPT 3: Draw a masking layer directly
I could not find any examples of how to draw directly on a layer. I can not use a static UIImage to mast with, because, as I said above, the mask has to change with user interaction. I was looking for something equivalent to the DrawRect function. Any help would be appreciated.
SO...
It seems to me that the sublayer way would be the best and simplest way to go, if I could just figure out how to change the priority of the blur effect to be the background behind the NSView not the NSView's background layer behind the sublayer.
Well, I would still like to know if there is a more elegant way, but I have found a solution that works. Basically, I have created a masking layer from an NSImage drawn from a modified version of the drawRect function:
- (id)init {
self = [super init];
if (self) {
// SETUP VIEW SAME AS ABOVE
CALayer *maskLayer = [CALayer layer];
maskLayer.contents = [NSImage imageWithSize:self.frame.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
[self drawMask:self.bounds];
return YES;
}];
maskLayer.frame = self.bounds;
self.layer.mask = maskLayer;
return self;
}
else return nil;
}
- (void)drawMask:(NSRect)dirtyRect {
[[NSColor clearColor] set];
NSRectFill(self.bounds);
[[NSColor blackColor] set];
// SAME DRAWING CODE AS drawRect
// EXCEPT EVERYTHING IS SOLID BLACK (NO ALPHA TRANSPARENCY)
// AND ONLY NEED TO DRAW PARTS THAT EFFECT THE EXTERNAL BOUNDARIES
}