【问题标题】:How to get the CGPoint(s) of a CGPath如何获取 CGPath 的 CGPoint(s)
【发布时间】:2012-10-11 03:26:04
【问题描述】:

如何获得一个数组,其中包含给定 CGPath (CGMutablePathRef) 中包含的所有 CGPoint(s)?

【问题讨论】:

    标签: iphone objective-c ios


    【解决方案1】:

    使用 Swift 2.x(适用于 Swift 3.xSwift 4.xSwift 5.x 请在下面阅读..),我发现了这篇关于 C Callbacks in Swift 的精彩文章。

    正如她所说,试图获得“所有CGPoint(s)”,正如Lily Ballard 所解释的那样,可能是个坏主意。

    所以,我认为也许最好的方法是获取用于创建特定CGPath 的路径元素点:

    //MARK: - CGPath extensions
    extension CGPath {
        func forEach(@noescape body: @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            func callback(info: UnsafeMutablePointer<Void>, element: UnsafePointer<CGPathElement>) {
                let body = unsafeBitCast(info, Body.self)
                body(element.memory)
            }
            print(sizeofValue(body))
            let unsafeBody = unsafeBitCast(body, UnsafeMutablePointer<Void>.self)
            CGPathApply(self, unsafeBody, callback)
        }
    
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.MoveToPoint:
                    arrayPoints.append(element.points[0])
                case .AddLineToPoint:
                    arrayPoints.append(element.points[0])
                case .AddQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .AddCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
    }
    

    使用此扩展程序,您可以执行以下操作:

    var bezier = UIBezierPath(ovalInRect: CGRectMake(0, 0, 400, 300))
    let myOval = bezier.CGPath
    let junctionPoints = myOval.getPathElementsPoints()
    print("junction points are: \(junctionPoints)")
    

    Swift 3.x 和 Swift 4.1(请看下面的 Swift 4.2 或主要版本..)

    (由于@convention(c)的语法重新引入,有一些更正):

    extension CGPath {
    
        func forEach( body: @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
                let body = unsafeBitCast(info, to: Body.self)
                body(element.pointee)
            }
            print(MemoryLayout.size(ofValue: body))
            let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
            self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
        }
    
    
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
    
        func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                default: break
                }
            }
            return (arrayPoints,arrayTypes)
        }
    }
    

    Swift > 4.1(也是 Swift 5.x)和 iOS 9.x 和 > 兼容

    extension CGPath {
        func forEach( body: @escaping @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
                let body = unsafeBitCast(info, to: Body.self)
                body(element.pointee)
            }
            //print(MemoryLayout.size(ofValue: body))
            let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
            self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
        }
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
        func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                default: break
                }
            }
            return (arrayPoints,arrayTypes)
        }
    }
    

    【讨论】:

    • 谢谢!这适用于 Xcode 8.3.2。 (虽然我不得不重命名 forEach() 以防止与其他 CGPath/UIBezierPath 扩展发生冲突。)关于这个主题,我现在为所有扩展添加前缀,给它们一个穷人的命名空间。
    • @Alessandro Ornano:在我使用 linePath.apply(CGAffineTransform(rotationAngle: getPathElementsPoints() 后由于某种原因返回了一个空数组。您知道为什么会发生这种情况以及如何解决吗?
    • 在使用 Xcode10 beta6、Swift 4.2 时,我在 let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self) 上收到错误消息 Converting non-escaping value to 'T' may allow it to escape
    • @smnk 谢谢你的报告,我已经更新了我的答案
    • 再次感谢您。这非常有帮助。
    【解决方案2】:

    苹果新增实例方法CGPath.applyWithBlock,可用于iOS11.0+和macOS10.13+

    您仍然可以使用 CGPath.apply 检查路径的每个元素,如前面的答案中所述。 但如果你想避免使用 C 风格的指针和 unsafeBitCast你应该使用更方便的实例方法applyWithBlock。 例如,如果您想获取 CGPath 的 CGPoints 数组,您可以添加 extension 到 CGPath,在本例中为 计算属性 (点)收集 CGPath 的 CGPoints

    /// Extension to collect CGPath points
    extension CGPath {
    
      /// this is a computed property, it will hold the points we want to extract
      var points: [CGPoint] {
    
         /// this is a local transient container where we will store our CGPoints
         var arrPoints: [CGPoint] = []
    
         // applyWithBlock lets us examine each element of the CGPath, and decide what to do
         self.applyWithBlock { element in
    
            switch element.pointee.type
            {
            case .moveToPoint, .addLineToPoint:
              arrPoints.append(element.pointee.points.pointee)
    
            case .addQuadCurveToPoint:
              arrPoints.append(element.pointee.points.pointee)
              arrPoints.append(element.pointee.points.advanced(by: 1).pointee)
    
            case .addCurveToPoint:
              arrPoints.append(element.pointee.points.pointee)
              arrPoints.append(element.pointee.points.advanced(by: 1).pointee)
              arrPoints.append(element.pointee.points.advanced(by: 2).pointee)
    
            default:
              break
            }
         }
    
        // We are now done collecting our CGPoints and so we can return the result
        return arrPoints
    
      }
    }
    

    【讨论】:

    • 这很好用。不过,我不使用 .addQuadCurveToPoint 和 .addCurveToPoint 。这个答案应该被升级!!
    【解决方案3】:

    您可以使用CGPathApply() 遍历路径中的每个段并使用该段运行自定义函数。这将为您提供路径的所有信息。

    但是,如果“所有 CGPoint(s)”是指每个点都有一个像素渲染到它,那是一个无限大小的集合。虽然您当然可以使用 apply 函数来获取每个段,然后对于每个非移动段,使用该段的控制点评估您自己的数学,以获得您想要的任何密度的点列表。

    【讨论】:

      【解决方案4】:

      CGPath 是一种不透明的数据类型,不一定存储所有使用的点。除此之外,路径实际上可能不会绘制所有用作输入的点(例如,考虑贝塞尔控制点)。

      从路径中获取信息的唯一两个documented ways 是使用CGPathGetBoundingBox 获取边界框,或更复杂的方法是使用CGPathApply 调用回调函数,如果CGPathElement 类型。

      【讨论】:

      • 目的是将存储的 CGpath 绘制为 Bezier 路径。这是一个绘图程序。基本上,在屏幕上徒手绘制的路径存储在一个数组中,用于撤消/重做。我正在寻找一种从数组中检索存储路径并将其绘制为贝塞尔曲线而不是标准路径的方法。这就是为什么我试图获取原始路径中的所有点,然后通过 Bezier 例程处理它们。这是错误的方法吗?您对此有更好的解决方案吗?我不想使用 NSundomanager 并且我不能将路径存储为图像,因为这太占用 CPU。干杯
      • 为什么不将点存储在一个数组中并在绘制它们时生成路径?
      • 我也想过这个问题,但后来我认为将路径存储在数组中以用于撤消目的会更容易(我将处理不同的路径)。如果我确实按照您的建议存储了每个路径的点,那么我需要创建一个 CGPoints 数组的数组。我不太确定该怎么做!有什么想法吗?
      • 基本上,CGPath 是一个点数组,只是一个更复杂且对应用程序程序员不那么透明的点。如果您想处理这些点,您自己管理数据结构可能更容易。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-20
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多