I create a spotlight that moves through the contents in my application, for example:


In the application example (shown above), the background layer is blue, and I have a layer on top of it that darkens everything except the circle that shows it in normal mode. It works for me (you can see how in the code below). My real application has actual content in other CALayers, not just blue.
Here is my problem: it does not revive. I use the CGContext
drawing to create a circle (which is an empty space in the black layer otherwise). When you click the button in my sample application, I draw a circle of a different size elsewhere.
I would like it to smoothly translate and scale, rather than jumping, as it is now. This may require a different way of creating a spotlight effect, or it may be a way that I don’t know to implicitly animate -drawLayer:inContext:
Easy to create sample application:
- Create a new Cocoa application (using ARC)
- Add quartz structure
- Drop user view and button on XIB
- Associate a custom view with a new class (SpotlightView) with the code below
- Delete
SpotlightView.h
since I included its contents in SpotlightView.m - Set the button output to
-moveSpotlight:
Refresh ( mask
property)
I like David Ronnqvist’s suggestion in the comments to use the mask
property of the darkened layer to cut a hole that I could then move independently. The problem is that for some reason, the mask
property works the opposite of how I expect the mask to work. When I point to a circular mask, all that appears is a circle. I expected the mask to work in the opposite way, masking the area with 0
alpha.
Masking seems like the right way, but if I need to fill the whole layer and cut a hole, I can also do it the way I originally placed. Does anyone know how to invert the -[CALayer mask]
property so that the area drawn inside is cut out from the layer image?
/ Update
Here is the code for SpotlightView
:
// // SpotlightView.m // #import <Quartz/Quartz.h> @interface SpotlightView : NSView - (IBAction)moveSpotlight:(id)sender; @end @interface SpotlightView () @property (strong) CALayer *spotlightLayer; @property (assign) CGRect highlightRect; @end @implementation SpotlightView @synthesize spotlightLayer; @synthesize highlightRect; - (id)initWithFrame:(NSRect)frame { if ((self = [super initWithFrame:frame])) { self.wantsLayer = YES; self.highlightRect = CGRectNull; self.spotlightLayer = [CALayer layer]; self.spotlightLayer.frame = CGRectInset(self.layer.bounds, -50, -50); self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; self.spotlightLayer.opacity = 0.60; self.spotlightLayer.delegate = self; CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; [blurFilter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"]; self.spotlightLayer.filters = [NSArray arrayWithObject:blurFilter]; [self.layer addSublayer:self.spotlightLayer]; } return self; } - (void)drawRect:(NSRect)dirtyRect {} - (void)moveSpotlight:(id)sender { [self.spotlightLayer setNeedsDisplay]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { if (layer == self.spotlightLayer) { CGContextSaveGState(ctx); CGColorRef blackColor = CGColorCreateGenericGray(0.0, 1.0); CGContextSetFillColorWithColor(ctx, blackColor); CGColorRelease(blackColor); CGContextClearRect(ctx, layer.bounds); CGContextFillRect(ctx, layer.bounds); // Causes the toggling if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) { self.highlightRect = CGRectMake(25, 25, 100, 100); } else { self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50, NSMaxY(self.layer.bounds) - 50, 25, 25); } CGRect drawnRect = [layer convertRect:self.highlightRect fromLayer:self.layer]; CGMutablePathRef highlightPath = CGPathCreateMutable(); CGPathAddEllipseInRect(highlightPath, NULL, drawnRect); CGContextAddPath(ctx, highlightPath); CGContextSetBlendMode(ctx, kCGBlendModeClear); CGContextFillPath(ctx); CGPathRelease(highlightPath); CGContextRestoreGState(ctx); } else { CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0); CGContextSetFillColorWithColor(ctx, blueColor); CGContextFillRect(ctx, layer.bounds); CGColorRelease(blueColor); } } @end