Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting correct frame of a newly created CAShapeLayer

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)

like image 380
Adam Avatar asked Dec 29 '11 00:12

Adam


1 Answers

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);
like image 67
jsh Avatar answered Jan 04 '23 02:01

jsh