Setting correct frame of a newly created CAShapeLayer
Asked Answered
P

1

16

In short:

  1. Apple does NOT set the frame or bounds for a CAShapeLayer automatically (and Apple HAS NOT implemented an equivalent of [UIView sizeThatFits])
  2. If you set the frame using the size of the bounding-box of the path ... everything goes wrong. No matter how you try to set it, it screws-up the path

So, what's the correct way to programmatically set the frame of a newly-created CAShapeLayer with a newly-added CGPath ? Apple's docs are silent on the matter.

Things I've tried, that don't work:

  1. Create a CAShapeLayer
  2. Create a CGPath, add it to the layer
  3. Check the layer's frame - it's {{0,0},{0,0}}
  4. Set: layer.frame = CGPathGetBoundingBox( layer.path )
  5. Frame is now correct, but the path is now DOUBLE offset - changing the frame causes the path to effectively be shifted an extra (x,y) pixels

  6. Set: layer.bounds = CGPathGetBoundingBox( layer.path )

  7. ...it all goes nuts. Nothing makes sense any more
  8. Try fixing it by doing layer.position = CGPathGetBoundingBox( layer.path ).origin
  9. ...no dice; still nuts.

One thing I've tried that DID work, but causes problems elsewhere:

EDIT: this BREAKS as soon as you auto-rotate the screen. My guess: Apple's auto-rotate requires control of the "transform" property.

  1. Create a CAShapeLayer
  2. Create a CGPath, add it to the layer
  3. Check the layer's frame - it's {{0,0},{0,0}}
  4. Set: layer.frame = CGPathGetBoundingBox( layer.path )
  5. Set: layer.transform = CGAffineTransformMakeTranslation( CGPathGetBoundingBox( layer.path ).origin.x * -1, // same for y-coord: set it to "-1 * the path's origin

This works, but ... lots of 3rd party code assumes that the initial transform for a CALayer is Identity.

It shouldn't be this difficult! Surely there's something I'm doing wrong here?

(I've had one suggestion: "every time you add a path, manually run a custom function to shift all the points by -1 * (top-left-point.x, top-left-point.y)". Again, that works - but it's ridiculously complex)

Popp answered 29/12, 2011 at 0:5 Comment(2)
Not yet. I am currently using the workaround from the question itself. It's slow and its a pain to maintain. I think the problem is a bug in Apple's code, maybe?Popp
One small addendum: I eventually discovered that ".frame" is unsupported by Apple. This is bizarre, because it's used everywhere - but if you dig deep enough in the docs and / or Apple mailing lists, you discover notes from Apple engineers saying it's not intended to work and they won't fix bugs with it. You're expected/required to use .bounds for EVERYTHING, and treat .frame as "readonly". So its especially ironic that in this case setting .frame "almost works", where setting .bounds "never works" :)Popp
U
5

Setting layer.bounds to the path bounds is the right thing to do — you want to make the layer's local coordinate space match the path. But you then also need to set the layer's position property to move it into the right place in its superlayer.

(Setting .frame translates into the framework computing the right values of .bounds and .position for you, but it always leaves bounds.origin untouched, which is not what you want when your path bounds has a non-zero origin.)

So something like this should work, assuming you haven't changed the anchorPoint from its usual (.5, .5) value and want to position the layer flush to the origin of its superlayer:

CGRect r = CGPathGetBoundingBox(layer.path);
layer.bounds = r;
layer.position = CGPointMake(r.size.width*.5, r.size.height*.5);
Ulu answered 11/12, 2013 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.