3

I would like to stroke the path of a CAShapeLayer with two alternating colours (black and white for example, think Preview.app selection boxes). I am not sure how to do this, or whether this is even possible with Quartz.

I have set up a CAShapeLayer to have a white border, and then set the path property to be a dashed black line. My hope was that this should give the effect of a black and white dashed lines. However, it seems that the path is drawn first the border is stroked on top, see screenshot (the fur belongs to my cat),

CAShapeLayer with white border and black stroked path, the path is drawn first followed yb the border.

Can anybody suggest a better approach or a way to get this to work?

The code,

// Stroke a white border
[shapeLayer setFrame:shapeRect];
[shapeLayer setBorderColor:[[NSColor whiteColor] CGColor]];
[shapeLayer setBorderWidth:1.0];

// Stroked a black dash path (along the border)
// and fill shape with clear colour
[shapeLayer setFillColor:[[NSColor clearColor] CGColor]];
[shapeLayer setStrokeColor:[[NSColor blackColor] CGColor]];
[shapeLayer setLineWidth:1.0];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
 [NSArray arrayWithObjects:[NSNumber numberWithInt:5],
  [NSNumber numberWithInt:5],
  nil]];

CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, shapeLayer.bounds);
[shapeLayer setPath:path];
CGPathRelease(path);
Daniel Farrell
  • 9,316
  • 8
  • 39
  • 62

2 Answers2

2

I think there are two problems with that approach (mixing layer border and shape stroke)

  1. The border is on the inside (of the bounds) but the stroke is made from the center (of the path).
  2. The border is drawn after the path is stoked

You can do something about #1 by inseting the path half of the strokeWidth

CGFloat halfWidth = 1.0/2.0;
CGPathAddRect(path, NULL, CGRectInset(shapeLayer.bounds, halfWidth, halfWidth));

However the border is still going to be drawn above the shape (at least I don't know how change the order).

I think the easiest solution is to have to shape layers in the same container layer. The container would be used to change the frame and the two layers would be used to the drawing.

The bottom layer would have a white stroke (no dash pattern) and the top layer would have a dashed black stroke. Since both shapes would have the same path it would look like a black and white dash pattern.

Doing it this way would also work with any shape.

David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • Thanks. So two layers is really the only option in your opinion? I was trying to stay away from that because it feels like a hack! – Daniel Farrell Aug 25 '13 at 12:26
  • @boyfarrell Either two layers or custom rendering. I would personally go with two layers. I'd assume you have only a few (if not only one) selection on screen so there shouldn't be any performance impact and the code is not too complex and could be well encapsulated in it's own class (with to private sublayer) – David Rönnqvist Aug 25 '13 at 13:49
  • Yes, that's good advice thanks. I plan to make a CAShapeLayer subclass that (say 'HostingShapeLayer') which has the property foregroundLayer. This property adds foreground layer as a sublayer to the hosting layer. The hosting layer also strokes a white path along the border. The sublayer can do to the black dashed line. Sound like reasonable design? Or would you go for something else? – Daniel Farrell Aug 25 '13 at 15:12
0

Did you try to use lineDashPhase property? Using it, you can first display dashed line with black, later - with changed color and lineDashPhase - with white color, over the same path.

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48
  • Thanks. I am using it already (for a different purpose) to do ants marching, http://www.cimgf.com/2009/10/20/marching-ants-with-core-animation I'm not sure I follow how your implementation would work? There is only one path property and one lineDashPhase property so I don't see how we can draw two lines. – Daniel Farrell Aug 25 '13 at 09:26
  • Sorry, missed that point. However, you can add two CAShapeLayers, but I don't think that it is a good way. – Nickolay Olshevsky Aug 25 '13 at 09:34
  • 1
    @NickolayOlshevsky what would you do instead? – David Rönnqvist Aug 25 '13 at 15:22