I need to know how I can scale down or up CGPath so it can fit UIView?
Set the background color to yellow, To see the view clearly
self.frame = CGRectMake(0, 0, 181, 154);
self.backgroundColor = [UIColor yellowColor];
Draw CAShapeLayer
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = aPath;
shape.strokeColor = [[UIColor blueColor] CGColor];
shape.lineWidth = 5;
shape.fillColor = [[UIColor clearColor] CGColor];
[self.layer addSublayer:shape];
Then I get This:
What I want is this:
Thank you Developers :)
I'm assuming that you want to completely fill (without distorting) the shape to fit the view you. I'm also assuming that you have a shape layer with the path already, since the question is tagged CAShapeLayer.
In this case you would get the bounding box of the shape layers path and calculate the appropriate scale factor to apply to the path.
Depending on the aspect ratio of the shape and the view it is supposed to fit it will either be the widths or heights that determine the scale factor.
Once you have the scale factor you will create a transform to apply to the path. Since the path may not start at (0,0) you also translate (move) the path in the transform to the bounding boxes origin.
If you want the new path to be in the center of the view you need to calculate how much to move it. You can comment out that code if you don't need it but I included it for other people reading this answer as well.
Finally you have a new path so all you have to do is to create a new shape layer, assign the path, style it appropriately and add it to the view.
// I'm assuming that the view and original shape layer is already created
CGRect boundingBox = CGPathGetBoundingBox(shapeLayer.path);
CGFloat boundingBoxAspectRatio = CGRectGetWidth(boundingBox)/CGRectGetHeight(boundingBox);
CGFloat viewAspectRatio = CGRectGetWidth(viewToFitIn.frame)/CGRectGetHeight(viewToFitIn.frame);
CGFloat scaleFactor = 1.0;
if (boundingBoxAspectRatio > viewAspectRatio) {
// Width is limiting factor
scaleFactor = CGRectGetWidth(viewToFitIn.frame)/CGRectGetWidth(boundingBox);
} else {
// Height is limiting factor
scaleFactor = CGRectGetHeight(viewToFitIn.frame)/CGRectGetHeight(boundingBox);
}
// Scaling the path ...
CGAffineTransform scaleTransform = CGAffineTransformIdentity;
// Scale down the path first
scaleTransform = CGAffineTransformScale(scaleTransform, scaleFactor, scaleFactor);
// Then translate the path to the upper left corner
scaleTransform = CGAffineTransformTranslate(scaleTransform, -CGRectGetMinX(boundingBox), -CGRectGetMinY(boundingBox));
// If you want to be fancy you could also center the path in the view
// i.e. if you don't want it to stick to the top.
// It is done by calculating the heigth and width difference and translating
// half the scaled value of that in both x and y (the scaled side will be 0)
CGSize scaledSize = CGSizeApplyAffineTransform(boundingBox.size, CGAffineTransformMakeScale(scaleFactor, scaleFactor));
CGSize centerOffset = CGSizeMake((CGRectGetWidth(viewToFitIn.frame)-scaledSize.width)/(scaleFactor*2.0),
(CGRectGetHeight(viewToFitIn.frame)-scaledSize.height)/(scaleFactor*2.0));
scaleTransform = CGAffineTransformTranslate(scaleTransform, centerOffset.width, centerOffset.height);
// End of "center in view" transformation code
CGPathRef scaledPath = CGPathCreateCopyByTransformingPath(shapeLayer.path,
&scaleTransform);
// Create a new shape layer and assign the new path
CAShapeLayer *scaledShapeLayer = [CAShapeLayer layer];
scaledShapeLayer.path = scaledPath;
scaledShapeLayer.fillColor = [UIColor blueColor].CGColor;
[viewToFitIn.layer addSublayer:scaledShapeLayer];
CGPathRelease(scaledPath); // release the copied path
In my example code (and shape) it looked like this
Swift 5 version of David Rönnqvist, based on Tarek's answer but refactored and with the scaleFactor * 2
bug fixed:
extension CGPath {
func resized(to rect: CGRect) -> CGPath {
let boundingBox = self.boundingBox
let boundingBoxAspectRatio = boundingBox.width / boundingBox.height
let viewAspectRatio = rect.width / rect.height
let scaleFactor = boundingBoxAspectRatio > viewAspectRatio ?
rect.width / boundingBox.width :
rect.height / boundingBox.height
let scaledSize = boundingBox.size.applying(CGAffineTransform(scaleX: scaleFactor, y: scaleFactor))
let centerOffset = CGSize(
width: (rect.width - scaledSize.width) / (scaleFactor * 2),
height: (rect.height - scaledSize.height) / (scaleFactor * 2)
)
var transform = CGAffineTransform.identity
.scaledBy(x: scaleFactor, y: scaleFactor)
.translatedBy(x: -boundingBox.minX + centerOffset.width, y: -boundingBox.minY + centerOffset.height)
return copy(using: &transform)!
}
}
swift 4.0 version of David Rönnqvist's answer
func resizepath(Fitin frame : CGRect , path : CGPath) -> CGPath{
let boundingBox = path.boundingBox
let boundingBoxAspectRatio = boundingBox.width / boundingBox.height
let viewAspectRatio = frame.width / frame.height
var scaleFactor : CGFloat = 1.0
if (boundingBoxAspectRatio > viewAspectRatio) {
// Width is limiting factor
scaleFactor = frame.width / boundingBox.width
} else {
// Height is limiting factor
scaleFactor = frame.height / boundingBox.height
}
var scaleTransform = CGAffineTransform.identity
scaleTransform = scaleTransform.scaledBy(x: scaleFactor, y: scaleFactor)
scaleTransform.translatedBy(x: -boundingBox.minX, y: -boundingBox.minY)
let scaledSize = boundingBox.size.applying(CGAffineTransform (scaleX: scaleFactor, y: scaleFactor))
let centerOffset = CGSize(width: (frame.width - scaledSize.width ) / scaleFactor * 2.0, height: (frame.height - scaledSize.height) / scaleFactor * 2.0 )
scaleTransform = scaleTransform.translatedBy(x: centerOffset.width, y: centerOffset.height)
//CGPathCreateCopyByTransformingPath(path, &scaleTransform)
let scaledPath = path.copy(using: &scaleTransform)
return scaledPath!
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With