在Swift中等同于或替代CGPathApply?

wfypjpf4  于 2023-03-28  发布在  Swift
关注(0)|答案(4)|浏览(131)

在预发布文档中,似乎没有CGPathApply的Swift版本。是否有等效版本或替代版本?我正在尝试获取CGPath的所有子路径,以便从不同的起点重新绘制它。

bmvo0sr5

bmvo0sr51#

2017年底至今

从iOS 11、macOS 10. 13、tvOS 11和watchOS 4开始,你应该使用CGPathapplyWithBlock方法。下面是一个取自this Swift package that rounds the corners of a CGPath的真实的例子。在这段代码中,self是一个CGPath

self.applyWithBlock {
    let points = $0.pointee.points

    switch $0.pointee.type {
    case .moveToPoint:
        if let currentSubpath, !currentSubpath.segments.isEmpty {
            copy.append(currentSubpath, withCornerRadius: radius)
        }
        currentSubpath = .init(firstPoint: points[0])
        currentPoint = points[0]

    case .addLineToPoint:
        append(.line(start: currentPoint, end: points[0]))
        currentPoint = points[0]

    case .addQuadCurveToPoint:
        append(.quad(points[0], end: points[1]))
        currentPoint = points[1]

    case .addCurveToPoint:
        append(.cubic(points[0], points[1], end: points[2]))
        currentPoint = points[2]

    case .closeSubpath:
        if var currentSubpath {
            currentSubpath.segments.append(.line(start: currentPoint, end: currentSubpath.firstPoint))
            currentSubpath.isClosed = true
            copy.append(currentSubpath, withCornerRadius: radius)
            currentPoint = currentSubpath.firstPoint
        }
        currentSubpath = nil

    @unknown default:
        break
    }
}

Swift 3.0

在Swift 3.0中,你可以像这样使用CGPath.apply

let path: CGPath = ...
// or let path: CGMutablePath

path.apply(info: nil) { (_, elementPointer) in
    let element = elementPointer.pointee
    let command: String
    let pointCount: Int
    switch element.type {
    case .moveToPoint: command = "moveTo"; pointCount = 1
    case .addLineToPoint: command = "lineTo"; pointCount = 1
    case .addQuadCurveToPoint: command = "quadCurveTo"; pointCount = 2
    case .addCurveToPoint: command = "curveTo"; pointCount = 3
    case .closeSubpath: command = "close"; pointCount = 0
    }
    let points = Array(UnsafeBufferPointer(start: element.points, count: pointCount))
    Swift.print("\(command) \(points)")
}

Swift 2.2

通过添加@convention(c),现在可以直接从Swift调用CGPathApply。下面是一个可以实现必要功能的 Package 器:

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

(Note我的代码中没有提到@convention(c),但在Core Graphics模块中的CGPathApply声明中使用了它。
示例用法:

let path = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 100), cornerRadius: 15)
path.CGPath.forEach { element in
    switch (element.type) {
    case CGPathElementType.MoveToPoint:
        print("move(\(element.points[0]))")
    case .AddLineToPoint:
        print("line(\(element.points[0]))")
    case .AddQuadCurveToPoint:
        print("quadCurve(\(element.points[0]), \(element.points[1]))")
    case .AddCurveToPoint:
        print("curve(\(element.points[0]), \(element.points[1]), \(element.points[2]))")
    case .CloseSubpath:
        print("close()")
    }
}
eagi6jfj

eagi6jfj2#

(Hint:如果你必须支持iOS 11之前的iOS,使用接受的答案。如果你可以要求iOS 11,这个答案就容易多了。)
iOS 11开始,苹果官方对这个问题给出了答案:CGPath.applyWithBlock(_:)
这使得所有来自CGPath.apply(info:function:)是一个C函数,不允许以通常的快捷方式在函数中传输信息的问题的肮脏技巧变得不必要。
下面的代码允许你做:

let pathElements = path.pathElements()

要做到这一点,复制和粘贴

import CoreGraphics

extension CGPath {
    func pathElements() -> [PathElement] {
        
        var result = [PathElement]()

        self.applyWithBlock { (elementPointer) in
            let element = elementPointer.pointee
            switch element.type {
            case .moveToPoint:
                let points = Array(UnsafeBufferPointer(start: element.points, count: 1))
                let el = PathElement.moveToPoint(points[0])
                result.append(el)
            case .addLineToPoint:
                let points = Array(UnsafeBufferPointer(start: element.points, count: 1))
                let el = PathElement.addLineToPoint(points[0])
                result.append(el)
            case .addQuadCurveToPoint:
                let points = Array(UnsafeBufferPointer(start: element.points, count: 2))
                let el = PathElement.addQuadCurveToPoint(points[0], points[1])
                result.append(el)
            case .addCurveToPoint:
                let points = Array(UnsafeBufferPointer(start: element.points, count: 3))
                let el = PathElement.addCurveToPoint(points[0], points[1], points[2])
                result.append(el)
            case .closeSubpath:
                result.append(.closeSubpath)
            @unknown default:
                fatalError()
            }
        }
        
        return result
    }

}

public enum PathElement {

    case moveToPoint(CGPoint)
    case addLineToPoint(CGPoint)
    case addQuadCurveToPoint(CGPoint, CGPoint)
    case addCurveToPoint(CGPoint, CGPoint, CGPoint)
    case closeSubpath

}

或者以这段代码为例来说明如何自己使用CGPath.applyWithBlock(_:)
为了完整起见,以下是Apple的官方文档:https://developer.apple.com/documentation/coregraphics/cgpath/2873218-applywithblock
自iOS 13**以来,苹果官方给出了一个更优雅的答案:使用SwiftUI(即使您的UI不在SwiftUI中)
cgPath转换为SwiftUI Path

let cgPath = CGPath(ellipseIn: rect, transform: nil)
let path =  Path(cgPath)
path.forEach { element in
    switch element {
    case .move(let to):
        break
    case .line(let to):
        break
    case .quadCurve(let to, let control):
        break
    case .curve(let to, let control1, let control2):
        break
    case .closeSubpath:
        break
    }
}

变量element的类型是Path.Element,这是一个纯Swift枚举,所以甚至不需要技巧来获取元素的值。
为了完整起见,以下是Apple官方文档:https://developer.apple.com/documentation/swiftui/path/3059547-foreach

qhhrdooz

qhhrdooz3#

以下是Ole Begemann的great post(感谢@Gouldsc!)的亮点,适用于Swift 3,它允许访问组成UIBezierPath示例的各个元素:

extension UIBezierPath {

    var elements: [PathElement] {
        var pathElements = [PathElement]()
        withUnsafeMutablePointer(to: &pathElements) { elementsPointer in
            cgPath.apply(info: elementsPointer) { (userInfo, nextElementPointer) in
                let nextElement = PathElement(element: nextElementPointer.pointee)
                let elementsPointer = userInfo!.assumingMemoryBound(to: [PathElement].self)
                elementsPointer.pointee.append(nextElement)
            }
        }
        return pathElements
    }

}

public enum PathElement {

    case moveToPoint(CGPoint)
    case addLineToPoint(CGPoint)
    case addQuadCurveToPoint(CGPoint, CGPoint)
    case addCurveToPoint(CGPoint, CGPoint, CGPoint)
    case closeSubpath

    init(element: CGPathElement) {
        switch element.type {
        case .moveToPoint: self = .moveToPoint(element.points[0])
        case .addLineToPoint: self = .addLineToPoint(element.points[0])
        case .addQuadCurveToPoint: self = .addQuadCurveToPoint(element.points[0], element.points[1])
        case .addCurveToPoint: self = .addCurveToPoint(element.points[0], element.points[1], element.points[2])
        case .closeSubpath: self = .closeSubpath
        }
    }

}
disbfnqx

disbfnqx4#

Dmitry Rodionov开发了一个将Swift函数转换为CFunctionPointer的函数(参见https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift)。

#define kObjectFieldOffset sizeof(uintptr_t)

struct swift_func_object {
    uintptr_t *original_type_ptr;
#if defined(__x86_64__)
    uintptr_t *unknown0;
#else
    uintptr_t *unknown0, *unknown1;
#endif
    uintptr_t function_address;
    uintptr_t *self;
};


uintptr_t _rd_get_func_impl(void *func)
{
    struct swift_func_object *obj = (struct swift_func_object *)*(uintptr_t *)(func + kObjectFieldOffset);
    
    //printf("-->Address of C-Func %lx unk=%lx ori=%lx<--\n", obj->function_address, obj->unknown0, obj->original_type_ptr);
    return obj->function_address;
}

我成功地使用了CGPathApply沿着Swift回调函数(代码为http://parker-liddle.org/CGPathApply/CGPathApply.zip),尽管正如Dmitry所说,这是一个反向工程函数,不受支持。

相关问题