In the pre-release documentation there appears to be no Swift version of CGPathApply. Is there an equivalent or alternative? I'm trying to get all subpaths of a CGPath so that I can redraw it from a different starting point.
Starting with iOS 11, macOS 10.13, tvOS 11, and watchOS 4, you should use the CGPath
's applyWithBlock
method. Here is a real example taken from this Swift package that rounds the corners of a CGPath
. In this code, self
is a 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
}
}
In Swift 3.0, you can use CGPath.apply
like this:
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)")
}
With the addition of @convention(c)
, you can now call CGPathApply
directly from Swift. Here's a wrapper that does the necessary magic:
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 that @convention(c)
isn't mentioned in my code, but is used in the declaration of CGPathApply
in the Core Graphics module.)
Example usage:
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()")
}
}