I'm trying to make a CAShapeLayer with a bezier path in it expand with a view (which is animated, of course). I am aware that I could change the size of the path with CATransform3DMakeScale(, , ) But this won't allow me to make the path an exact size (in points).
Does anybody know how to do this?
You would do this using good old fashioned math.
Simple solution
To phrase your question differently: you have something of one size (the path/shape layer), and you want to scale it so that it becomes another size.
To know how much you want to scale along X and Y (separately) you divide the size you want to fit to by the current size. You can get the bounding box of the path using
let boundingBox = CGPathGetBoundingBox(path)
I'm assuming that you already have some size that you want to scale it to (here I'm calling mine containingSize
).
Using those two, you can calculate the two scale factors by dividing the dimension you are scaling to with the dimension you are scaling from
let xScaleFactor = containingSize.width / boundingBox.width
let yScaleFactor = containingSize.height / boundingBox.height
Using those, you can create the required scale transform
let scaleTransform = CATransform3DMakeMakeScale(xScaleFactor, yScaleFactor, 1.0)
Scaling this shape layer
using those two scale factors, will scale the shape layer to fill the container size. If the container size has the same aspect ratio as the path, everything will look as expected. If not, the scaled layer will appear stretched.
Fitting instead of filling
This problem (unless its what you want) can be solved by calculating a uniform scale factor, that is the smaller of the two, so that the scaled path fits the container instead of fills it.
We do this by finding out which scale factor is the most constrained one, and then apply that to both X and Y
let boundingBoxAspectRatio = boundingBox.width/boundingBox.height
let viewAspectRatio = containingSize.width/containingSize.height
let scaleFactor: CGFloat
if (boundingBoxAspectRatio > viewAspectRatio) {
// Width is limiting factor
scaleFactor = containingSize.width/boundingBox.width
} else {
// Height is limiting factor
scaleFactor = containingSize.height/boundingBox.height
}
let scaleTransform = CATransform3DMakeMakeScale(scaleFactor, scaleFactor, 1.0)
This will scale the path without changing its aspect ratio
Scaling the layer or scaling the path?
You might also have noticed that as the shape layer was scaled, the line width scaled as well, like if it was an image. There is a difference between scaling the layer, and scaling the path.
If you only want it to appear as the path of the shape layer is scaled, then you should scale the path instead of the layer. You can do this by creating a new path that is transformed, and using that path with your shape layer. Note that the scale factor is still calculated using the bounding box of the unscaled path.
var affineTransform = CGAffineTransformMakeScale(scaleFactor, scaleFactor)
let transformedPath = CGPathCreateCopyByTransformingPath(path, &affineTransform)
yourShapeLayer.path = transformedPath
This will scale up the path, without affecting the line width, etc of the shape layer.