Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scale CGPath to Fit UIVIew

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: enter image description here

What I want is this: enter image description here

Thank you Developers :)

like image 999
MAMN84 Avatar asked Mar 26 '13 17:03

MAMN84


3 Answers

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

enter image description here

like image 138
David Rönnqvist Avatar answered Nov 10 '22 23:11

David Rönnqvist


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)!
    }
}
like image 22
S2dent Avatar answered Nov 10 '22 23:11

S2dent


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!
    }
like image 8
Tarek hemdan Avatar answered Nov 10 '22 21:11

Tarek hemdan