Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to subclass CALayer for use as another CALayer mask?

I' m trying to subclass CALayer for use as a mask on another layer.

I want to use my CALayer subclass in place of a CAGradientLayer and use it for a gradient mask as explained here.

However I wish to use a custom CALayer that uses an internal CGGradient for drawing instead, as this should produce smoother results than CAGradientLayer (see here). I don't care about performance, I want better gradient quality.

I'm following this example for creating my CGGradient and storing it on the CALayer.. However, I cannot get the mask to draw.

I don't know where to put the draw code: CALayer's display nor drawInContext:(CGContextRef)ctx nor drawInContext:(CGContextRef)ctx seem to be called when it is used as a mask.

Bear with me as I'm new with CoreAnimation. So, how to fix this so my CALayer subclass works as a replacement for CAGradientLayer but draws using a CGGradient?


My current code here:

@interface CANiceGradientLayer : CALayer

@property (nonatomic) CGGradientRef gradient;
@property (atomic) CGPoint startPoint;
@property (atomic) CGPoint endPoint;

@end

@implementation CANiceGradientLayer

- (instancetype)initWithGradientRef:(CGGradientRef)gradient startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint
{
    if ( !(self = [super init]) )
    {
        return nil;
    }

    self.gradient = CGGradientRetain(gradient);
    self.startPoint = startPoint;
    self.endPoint = endPoint;

    return self;
}

- (void)dealloc
{
    CGGradientRelease(self.gradient);
}

- (void)display
{
    NSLog(@"display");
}

- (void)drawInContext:(CGContextRef)ctx
{
    NSLog(@"drawInContext:");
    CGContextDrawLinearGradient(ctx, self.gradient, self.startPoint, self.endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);;
}

- (void)renderInContext:(CGContextRef)ctx
{
    NSLog(@"renderInContext:");
}

@end

And here is how I create it:

size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0,  // Start color
                          1.0, 1.0, 1.0, 0.0 }; // End color

CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);


self.collectionViewTickerMaskLayer = [[CANiceGradientLayer alloc] initWithGradientRef:gradient startPoint:CGPointZero endPoint:CGPointZero];


self.collectionViewTickerMaskLayer.anchorPoint = CGPointZero;
view.layer.mask = self.collectionViewTickerMaskLayer;

If I use a CAGradientLayer instead, it works fine (but the gradient looks bad).

like image 470
Ricardo Sanchez-Saez Avatar asked Nov 01 '22 20:11

Ricardo Sanchez-Saez


1 Answers

Call -setNeedsDisplay on your layer subclass before setting it to the mask and you'll only need to override -drawInContext: of the CALayer subclass. That will help you get the method to be called (CALayer calls -drawInContext: using its default implementation of -display which is called after -setNeedsDisplay). You may also have to set the frame of the layer:

self.collectionViewTickerMaskLayer.frame = view.layer.bounds;
self.collectionViewTickerMaskLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;

You have a logic issue in this code. You are using an endpoint which is CGPointZero, so even if the methods were called, no gradient would even be drawn to mask your layer.

like image 149
Alex Zielenski Avatar answered Nov 15 '22 04:11

Alex Zielenski