从 iOS 11、macOS 10.13、tvOS 11 和 watchOS 4 开始,您应该使用 CGPath
的 applyWithBlock
方法。这里有一个真实的例子,取自这个用于圆角化 CGPath
的 Swift 包。在这段代码中,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中,您可以像这样使用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)")
}
通过添加@convention(c)
,您现在可以直接从Swift调用CGPathApply
。这是一个执行必要操作的包装器:
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)
}
}
(请注意,我的代码中没有提到@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()")
}
}
提示:如果你需要支持iOS 11以下的版本,请使用被接受的答案。如果可以要求iOS 11及以上版本,那么这个答案将更加简单。
自从iOS 11以来,苹果公司已经给出了官方答案:CGPath.applyWithBlock(_:)
。
这使得所有不必要的把戏变得不再需要,因为CGPath.apply(info:function:)
是一个C函数,不能以通常的Swifty方式在函数内外传输信息。
以下代码可帮助您完成:
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(_:)
。
为了完整起见,这是来自苹果的官方文档: 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枚举,因此甚至不需要任何技巧来从element中获取值。
为了完整起见,这是官方的Apple文档:https://developer.apple.com/documentation/swiftui/path/3059547-foreach
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
}
}
}
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所说,这是一个反向工程的函数而不是受支持的函数。