缩放 CGPath 以适应 UIVIew

Posted

技术标签:

【中文标题】缩放 CGPath 以适应 UIVIew【英文标题】:Scale CGPath to Fit UIVIew 【发布时间】:2013-03-26 17:27:44 【问题描述】:

我需要知道如何缩小或放大 CGPath 以适应 UIView?

设置背景颜色为黄色,以便看清视图

self.frame = CGRectMake(0, 0, 181, 154);
self.backgroundColor = [UIColor yellowColor];

绘制 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];

然后我得到这个:

我想要的是:

感谢开发者:)

【问题讨论】:

你如何首先获得路径?根据路径的设置方式,在形状层上设置frame 可能很简单。 @DBD 我用过这个类,link 【参考方案1】:

我假设您想要完全填充(不扭曲)形状以适合您的视图。我还假设您已经有一个带有路径的形状层,因为问题被标记为 CAShapeLayer。


在这种情况下,您将获得形状图层路径的边界框并计算适当的比例因子以应用于路径。

取决于形状的纵横比和它应该适合它的视图将是确定比例因子的宽度或高度。

获得比例因子后,您将创建一个变换以应用于路径。由于路径可能不是从 (0,0) 开始,您还可以将变换中的路径转换(移动)到边界框的原点。

如果您希望新路径位于视图的中心,您需要计算移动它的距离。如果您不需要该代码,您可以注释掉它,但我也将它包含在其他阅读此答案的人中。

最后你有了一个新的路径,所以你要做的就是创建一个新的形状层,分配路径,适当地设置它的样式并将它添加到视图中。

// 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

在我的示例代码(和形状)中,它看起来像这样

【讨论】:

谢谢,“视野中心”真的很有帮助。 我正在解决一个类似的问题,但我不知道 CGPath 上存在这个 API。百万感谢! 只是一个小提示。您负责释放从 CGPathCreateCopyByTransformingPath 返回的返回 CGPath。【参考方案2】:

David Rönnqvist 的回答的swift 4.0 版本

 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!
    

【讨论】:

【参考方案3】:

Swift 5 版本的 David Rönnqvist,基于 Tarek 的回答,但经过重构并修复了 scaleFactor * 2 错误:

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)!
    

【讨论】:

以上是关于缩放 CGPath 以适应 UIVIew的主要内容,如果未能解决你的问题,请参考以下文章

调整 UIView 的大小以适应 CGPath

在方向更改时自动缩放 UIView 以按比例缩放以适合父视图

如何缩放 UIImage 以适应 UIScrollView 的尺寸?

旋转影响比例变换锚点

自动布局:缩放填充不起作用

缩放文本以适应文本字段;缩放文本字段以适应文本。