以类似于 CGRect、CGPoint 等的方式转换 UIBezierPath

Convert UIBezierPath in similar way to CGRect, CGPoint, etc

提问人:Victor Engel 提问时间:9/25/2023 更新时间:9/26/2023 访问量:66

问:

UIView具有在坐标空间之间转换点、矩形等的功能。我需要为 .举例来说,考虑两个视图,以及 .我们在坐标空间中有 。我们可以计算:UIBezierPathsviewAviewBrectA : CGRectviewA

let rectB = viewB.convert(rectA, from:viewA)

现在假设我们从矩形创建一个路径:

let pathA = UIBezierPath(rect: rectA)

问题是,如何计算.我们可以简单地说:rectB

let pathB = UIBezierPath(rect: rectB)

但这只有在我们有 .如果我们只有“pathA”怎么办?rectB

我想要做到这一点,我们需要一个转换。然后我们可以做:aTob

var pathB = pathA
pathB.apply(aTob)

所以问题归结为如何计算.为简单起见,假设没有旋转。aTob

因此,绘图应在屏幕上以与绘图相同的形状和位置绘制路径。pathAviewApathBviewB

Swift UIViewerPath cgaffineTransform

评论

0赞 Itai Ferber 9/25/2023
UIBezierPath有一个 apply() 方法,它采用 ;创建缩放转换(从 size of 转换为 size of )是否能获得预期的结果?CGAffineTransformviewAviewB
0赞 Victor Engel 9/25/2023
我的问题建议应用方法。问题是如何计算变换。在我的用例中,两个视图具有相同的超级视图,但可能具有不同的来源和不同的锚点。令我惊讶的是,似乎没有一个公共 API 可以在给定两个视图的情况下返回转换。
0赞 Itai Ferber 9/26/2023
啊,对不起!我不知何故错过了“应用”和“转换”指的是这种特定方法。
0赞 Victor Engel 9/26/2023
我最终做的是将边界转换为坐标,然后通过调整比例来计算变换,然后调整原点进行平移。我可能会编写一个扩展以使其更通用,并且也可以处理旋转。viewBviewA
0赞 Victor Engel 9/26/2023
我认为通用解决方案可能会使用递归函数,该函数通过共同祖先将一个视图遍历到另一个视图,将每个步骤的逆变换连接起来,直到共同祖先,然后是其余步骤的变换。请注意,仅当两个视图具有共同的祖先时,它才有效。

答:

1赞 HangarRash 9/25/2023 #1

一种解决方案是使用转换转换在贝塞尔路径上调用 apply()。

您可以通过将 的原点转换为 的坐标空间来计算变换所需的 x 和 y。viewBviewA

以下是一些可以放入 Swift Playground 的示例代码:

// Some setup
let rectA = CGRect(x: 40, y: 60, width: 200, height: 100)
let rectB = CGRect(x: 10, y: 80, width: 200, height: 100)
let viewA = UIView(frame: rectA)
let viewB = UIView(frame: rectB)
// Create pathA
let pathA = UIBezierPath(rect: rectA)

// Calculate the offset needed to move from viewA to viewB
let offset = viewA.convert(CGPoint.zero, from: viewB)
let tranform = CGAffineTransform(translationX: offset.x, y: offset.y)
// Copy pathA
let pathB = UIBezierPath()
pathB.append(pathA)
// Shift pathB
pathB.apply(tranform)
// Check result
print(pathA)
print(pathB)

输出:

<UIBezierPath: 0x600002c31e80; <MoveTo {40, 60}>,
 <LineTo {140, 60}>,
 <LineTo {140, 160}>,
 <LineTo {40, 160}>,
 <Close>
<UIBezierPath: 0x600002c12280; <MoveTo {10, 80}>,
 <LineTo {110, 80}>,
 <LineTo {110, 180}>,
 <LineTo {10, 180}>,
 <Close>

我相信这就是你要找的。的计算可能是倒退的。如果结果方向错误,请将该行更改为:offsetpathBoffset

let offset = viewB.convert(CGPoint.zero, from: viewA)

下面是用于将视图从一个视图转换为另一个视图的扩展:UIViewUIBezierPath

extension UIView {
    private func adjust(_ path: UIBezierPath, by offset: CGPoint) -> UIBezierPath {
        let transform = CGAffineTransform(translationX: offset.x, y: offset.y)
        let result = UIBezierPath()
        result.append(path)
        result.apply(tranform)

        return result
    }

    func convert(_ path: UIBezierPath, from view: UIView) -> UIBezierPath {
        let offset = convert(CGPoint.zero, from: view)

        return adjust(path, by: offset)
    }

    func convert(_ path: UIBezierPath, to view: UIView) -> UIBezierPath {
        let offset = convert(CGPoint.zero, to: view)

        return adjust(path, by: offset)
    }
}

此答案假定两个视图都没有应用非标识转换。根据 OP 自己的答案,显然其中一个或两个视图可能具有非身份转换。

0赞 Victor Engel 9/26/2023 #2

使用像 HangarRash 发布的扩展一样的扩展,这是一个在两个视图具有相同超视图时有效的解决方案。

func convert(_ path: UIBezierPath, from view: UIView) -> UIBezierPath {
    path.apply(view.transform)
    // we now have a path in view's superview's coordinates. Let's adjust by the origin.
    let viewTransform = CGAffineTransform(translationX: view.frame.origin.x, y: view.frame.origin.y)
    path.apply(viewTransform)
    
    // now the path is adjusted by view's origin. Next we need to subtract self's origin
    let selfTranslationTransform = CGAffineTransform(translationX: -self.frame.origin.x, y: -self.frame.origin.y)
    path.apply(selfTranslationTransform)
    
    // now adjust by self's transform
    path.apply(self.transform.inverted())
    return path
}

评论

0赞 HangarRash 9/26/2023
这如何扩展贝塞尔路径?你最终在这里做的就是翻译,你声称没有回答你的问题。我现在很困惑。
0赞 Victor Engel 9/26/2023
path.apply(view.transform) 在参数中的视图上应用任何比例。并且,path.apply(self.transform.inverted()) 应用了 self 的倒置变换,该变换也可能具有比例。这是使用随机缩放因子随机定位的视图进行测试的。如果有旋转,则失败。
0赞 HangarRash 9/26/2023
我现在意识到我误读了你的答案。第一个和最后一个(反转)转换来自两个不同的视图。我发表了我的第一条评论,认为他们来自同一个观点。因此,如果两个视图都没有非身份转换,我目前的答案是有效的。你的问题并不像它可能的那样清楚。您没有提到对视图应用了转换的视图。您只谈到了视图矩形和调整两个矩形之间的贝塞尔路径。
0赞 Victor Engel 9/26/2023
我提到了不同的坐标空间。我提到的两个矩形具有不同的坐标空间。我直截了当地说了。什么决定了坐标空间?原点和转换的位置。可以通过平移、缩放和旋转来修改或初始化转换。我说为了简单起见,假设没有旋转。剩下的就是另外两个了。再清楚不过了。
0赞 HangarRash 9/26/2023
是的,经过进一步的评论,这已经非常清楚了。作为作者,您确切地知道您的问题意味着什么。作为读者,并非所有这些细节都那么清楚。很高兴你解决了你的问题。