Create a translucent overlay with Core Animation levels

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

Spotlight aSpotlight b

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 
+6
source share
1 answer

I finally understood. What pushed me to answer was heard about CAShapeLayer . At first, I thought it would be an easier way to draw the contents of a layer, rather than draw and clear the contents of a standard CALayer . But I read the documentation about the path property of CAShapeLayer , which stated that it can be animated, but implicitly.

Although the layer mask could be more intuitive and elegant, it is not possible to use the mask to hide part of the owner’s layer, and not show the part, and therefore I couldn’t use it, I am pleased with this decision because it is pretty clear what is happening. I would like to use implicit animation, but the animation code is just a few lines.

Below, I changed the example code from the question to add smooth animation. (I deleted the CIFilter code because it was an extraneous one. The solution still works with filters.)

 // // SpotlightView.m // #import <Quartz/Quartz.h> @interface SpotlightView : NSView - (IBAction)moveSpotlight:(id)sender; @end @interface SpotlightView () @property (strong) CAShapeLayer *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 = [CAShapeLayer layer]; self.spotlightLayer.frame = self.layer.bounds; self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; self.spotlightLayer.fillRule = kCAFillRuleEvenOdd; CGColorRef blackoutColor = CGColorCreateGenericGray(0.0, 0.60); self.spotlightLayer.fillColor = blackoutColor; CGColorRelease(blackoutColor); [self.layer addSublayer:self.spotlightLayer]; } return self; } - (void)drawRect:(NSRect)dirtyRect {} - (CGPathRef)newSpotlightPathInRect:(CGRect)containerRect withHighlight:(CGRect)spotlightRect { CGMutablePathRef shape = CGPathCreateMutable(); CGPathAddRect(shape, NULL, containerRect); if (!CGRectIsNull(spotlightRect)) { CGPathAddEllipseInRect(shape, NULL, spotlightRect); } return shape; } - (void)moveSpotlight { CGPathRef toShape = [self newSpotlightPathInRect:self.spotlightLayer.bounds withHighlight:self.highlightRect]; CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; pathAnimation.fromValue = (__bridge id)self.spotlightLayer.path; pathAnimation.toValue = (__bridge id)toShape; [self.spotlightLayer addAnimation:pathAnimation forKey:@"path"]; self.spotlightLayer.path = toShape; CGPathRelease(toShape); } - (void)moveSpotlight:(id)sender { 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); } [self moveSpotlight]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0); CGContextSetFillColorWithColor(ctx, blueColor); CGContextFillRect(ctx, layer.bounds); CGColorRelease(blueColor); } @end 
+6
source

Source: https://habr.com/ru/post/909352/


All Articles