Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming a CAShapelayer to a specific size in swift

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?

like image 808
illuminaté Avatar asked Dec 02 '22 14:12

illuminaté


1 Answers

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

Unscaled image

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.

enter image description here

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

enter image description here

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.

enter image description here

like image 84
David Rönnqvist Avatar answered Dec 04 '22 03:12

David Rönnqvist