Clipping a CAGradientLayer with a duplicated CAShapeLayer (don't)

  • I was almost on the point of asking about this, but I found a solution, and I wanted to put it into Google in case anyone else runs into it.

    I had a CAShapeLayer that I had simply filled with a flat color. I decided to try for a gradient fill because Everything is Better with Gradients. CAShapeLayer doesn't do gradients, so I had to combine it with a masked CAGradientLayer.

    My first attempt looked like this

    * Create a CALayer subclass to contain the shape and the gradient.

    * Create a CAShapeLayer (shapeL) with my original shape.
    - Fill it with clearColor so the gradient would show through.
    - Stroke it with blackColor.

    * Create the gradient layer (gradL).
    - Set startPoint, endPoint, frame, colors, etc.

    * Create a duplicate CAShapeLayer (maskL) with [[CAShapeLayer alloc] initWithLayer: shapeL].
    - Adjust the fill color to blackColor, because masking goes by alpha.

    * Set gradL.mask to maskL, so the gradient will be clipped to the shape.

    * Add gradL and shapeL to self.
    - Add a shadow to self because Everything is Also Better with Shadows.

    The container layer drew only shapeL. Nothing I could do with the mask, the gradient, or the shape did any good. However, not setting gradL.mask at all did draw the gradient (as a rectangle).

    The trick was that -initWithLayer: did not produce a usable mask layer. If I created maskL as a new mask layer ([CAShapeLayer layer]), and initialized it to match shapeL (except for the fill), gradL was drawn as expected.

    <http://stackoverflow.com/questions/4733966/applying-a-gradient-to-cashapela
    yer
    > got me most of the way, but didn't extend to how to re-use the mask layer.

    — F
  • On Thu, Feb 9, 2012 at 11:09 AM, Fritz Anderson <fritza...> wrote:
    > * Create a duplicate CAShapeLayer (maskL) with [[CAShapeLayer alloc] initWithLayer: shapeL].
    >        - Adjust the fill color to blackColor, because masking goes by alpha.

    The CALayer documentation makes it quite clear that you should never,
    ever do this:

    "Note: Invoking this method in any other situation will produce
    undefined behavior. Do not use this method to initialize a new layer
    with an existing layer’s content."

    https://developer.apple.com/library/ios/#documentation/GraphicsImaging/Refe
    rence/CALayer_class/Introduction/Introduction.html


    --Kyle Sluder
  • On Feb 9, 2012, at 11:09 AM, Fritz Anderson wrote:

    > The trick was that -initWithLayer: did not produce a usable mask layer.

    Applications should not call -initWithLayer:. Its purpose of existence is so that if you have a CALayer subclass that has additional ivars that need copying, you can implement it and CoreAnimation can call it to do the right thing. The layers produced in this way are not expected to be useful as 'model' layers.

    > If I created maskL as a new mask layer ([CAShapeLayer layer]), and initialized it to match shapeL (except for the fill), gradL was drawn as expected.
    >
    > <http://stackoverflow.com/questions/4733966/applying-a-gradient-to-cashapela
    yer
    > got me most of the way, but didn't extend to how to re-use the mask layer.

    Did you add the stroke shape layer as a sublayer of the gradient layer? If so, that may explain why you didn't get your stroke – the mask applies to a layer and all of its sublayers. As such, the shape layer would have at least partially masked the stroke (its possible Core Animation simply rejected rendering the shape layer entirely, as its path rendering emphasizes speed over accuracy).

    --
    David Duncan
  • On 9 Feb 2012, at 1:25 PM, Kyle Sluder wrote:

    > The CALayer documentation makes it quite clear that you should never,
    > ever do this:

    Hence the last word of the subject of this thread.

    — F
  • On Feb 9, 2012, at 1:37 PM, Fritz Anderson wrote:

    > On 9 Feb 2012, at 1:25 PM, Kyle Sluder wrote:
    >
    >> The CALayer documentation makes it quite clear that you should never,
    >> ever do this:
    >
    > Hence the last word of the subject of this thread.

    Perhaps, but I believe that our point is that -initWithLayer: is not a method to use to duplicate a layer, its a method to create a shadow layer (which is something strictly for Core Animation's usage). If you duplicated a layer (by calling -init and setting all the appropriate properties) then there is no reason why it shouldn't work.
    --
    David Duncan
  • On 9 Feb 2012, at 4:30 PM, David Duncan wrote:

    > Perhaps, but I believe that our point is that -initWithLayer: is not a method to use to duplicate a layer, its a method to create a shadow layer (which is something strictly for Core Animation's usage). If you duplicated a layer (by calling -init and setting all the appropriate properties) then there is no reason why it shouldn't work.

    Absolutely.

    On 9 Feb 2012, at 1:09 PM, Fritz Anderson wrote:

    > The trick was that -initWithLayer: did not produce a usable mask layer. If I created maskL as a new mask layer ([CAShapeLayer layer]), and initialized it to match shapeL (except for the fill), gradL was drawn as expected.

    I assume this is what you just said — or are you getting at a different way to duplicate the layer?

    — F
  • On Feb 9, 2012, at 2:36 PM, Fritz Anderson wrote:

    > On 9 Feb 2012, at 1:09 PM, Fritz Anderson wrote:
    >
    >> The trick was that -initWithLayer: did not produce a usable mask layer. If I created maskL as a new mask layer ([CAShapeLayer layer]), and initialized it to match shapeL (except for the fill), gradL was drawn as expected.
    >
    > I assume this is what you just said — or are you getting at a different way to duplicate the layer?

    Nope, using +layer or -init and copying appropriate properties is an appropriately acceptable way to duplicate a layer.
    --
    David Duncan
previous month february 2012 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29        
Go to today