0

Using example code from many great people, I am able to make a complex shape using CAShapeLayer and present that no problem using a subclassed UIView:

@implementation BMTestLogo

+ (Class)layerClass {
    return [CAShapeLayer class];
}


- (void)layoutSubviews {
    [self setUpPaths];
    [self setLayerProperties];
    //    [self attachAnimations];
}

- (void)setLayerProperties {
    CAShapeLayer *layer = (CAShapeLayer *)self.layer;
    //    _gradient = [CAGradientLayer layer];

    CGMutablePathRef combinedPath = CGPathCreateMutableCopy([[_UIBezierPathsArray objectAtIndex:0] CGPath]);

    for (UIBezierPath* path in _UIBezierPathsArray) {
        CGPathAddPath(combinedPath, NULL, path.CGPath);
    }

    layer.path = combinedPath;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.fillRule = kCAFillRuleEvenOdd;
}

This code works great, now trying to add a CAGradientLayer and mask over the shape, I keep getting a crash, specifically: QuartzCore CA::Layer::ensure_transaction_recursively(CA::Transaction*):

Here is the Gradient Code, note, this is taken from other, working examples on SO:

@implementation BMViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [_testLogo setNeedsDisplay];

    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.startPoint = CGPointMake(0.5,1.0);
    gradientLayer.endPoint = CGPointMake(0.5,0.0);
    gradientLayer.frame = CGRectMake(CGRectGetMinX(_testLogo.layer.bounds), CGRectGetMinY(_testLogo.layer.bounds), CGRectGetWidth(_testLogo.layer.bounds), CGRectGetHeight(_testLogo.layer.bounds));
    NSMutableArray *colors = [NSMutableArray array];
    for (int i = 0; i < 2; i++) {
        [colors addObject:(__bridge id)[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1].CGColor];
    }
    gradientLayer.colors = colors;
    NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
    NSNumber *stopTwo  = [NSNumber numberWithFloat:1.0];
    NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
    gradientLayer.locations = locations;
    gradientLayer.needsDisplayOnBoundsChange = YES;
    [gradientLayer setMask:_testLogo.layer];
    [_testLogo.layer insertSublayer:gradientLayer atIndex:0];

    // Do any additional setup after loading the view, typically from a nib.
}

I have tried to debug and research this issue, and the usual suspects of the UIColors not being CGColors or (__bridge id) properly are not there, I am using two locations for the gradient, with only 2 colors, and moreover I have tried dozens of different versions: with or without setNeeds display, properties vs. local declarations, the gradient layer inside the sub class and out, all still giving me the same crash when inserting thesublayer.

When I take out the setMask: call, it does not crash, and just fills the whole frame with the gradient.

This seems like I'm just missing something minor or obvious. Note, using iOS 6 and ARC -- would like to keep using CAShapeLayer and CAGradientLayer as it makes it a bit easier to animate (which is the end goal).

Community
  • 1
  • 1
Maximilian
  • 1,107
  • 11
  • 20

1 Answers1

3

The following code is triggering a stack overflow in ensure_transaction_recursively.

[gradientLayer setMask:_testLogo.layer];
[_testLogo.layer insertSublayer:gradientLayer atIndex:0];

The shape layer has a gradient layer subview which has a shape layer mask which has a gradient layer subview...

Since your goal is to have a gradient layer that is masked by a shape, BMTestLogo should be backed by the CAGradientLayer which then has a the CAShapeLayer as its mask. The shape layer should never reference the gradient layer.

Brian Nickel
  • 26,890
  • 5
  • 80
  • 110
  • Well Brian, that certainly did the trick! I should of used intuiton as it did say "RECURSIVELY!" -- do you have a moment to clarify a few things? 1) looking at this SO answer:http://stackoverflow.com/questions/4733966/applying-a-gradient-to-cashapelayer -- why does this work by setting the mask of the shape from the gradient layer and then adding the layer as well? Also, My goal was to recreate this: https://www.dropbox.com/sh/t0yfvxakw9oto69/Ji0bYznFVT -- which I can using Paintcode, My plan was to create 16 CAGradientLayer's to the 16 BezierPaths as CAShapeLayer – Maximilian Jul 29 '13 at 19:22
  • would this require 16 sepearte UIView backed as CAGradientLayers, each with their specific gradient according to the bezier paths and apply the same logic? – Maximilian Jul 29 '13 at 19:28
  • 1
    In that solution, `v.layer != gradientMask`. What they did was create a new shape layer, set it as the mask of a new gradient layer, and insert the gradient layer into the view's layer (presumably just a `CALayer`). – Brian Nickel Jul 29 '13 at 19:28
  • 1
    You can add as many sublayers as you want into a single `UIView`'s layer. There's no need to create custom backed views. – Brian Nickel Jul 29 '13 at 19:29
  • The end goal is to animate all of the detailed gradients of the icon in a "swirling" fashion, and I have found that using just 1 CAGradientLayer doesn't cut it -- I had a friend recomend creating three or so CAGradient Layers and haveing them animate in concentric circles ontop of the masked icon to simulate the effect, I definitly would consider that, but I'm a little bit lost with this reversal of a CAGradientLayer backed view masked by the CAShapeLayer. Sorry for the lengthy questions, thank you! – Maximilian Jul 29 '13 at 19:29