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.
-
Have you tried it anyway? I've seen some other symbols that are actually available in Swift failing to show up as Swift in the docs. Also try cmd-clicking another CGPath function and see what shows up in the Swift-translated module "header". And if it's not there, [file a bug](http://bugreport.apple.com) — it should be. – rickster Jun 18 '14 at 06:52
4 Answers
Since Late 2017
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
}
}
Swift 3.0
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)")
}
Swift 2.2
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()")
}
}

- 375,296
- 67
- 796
- 848
-
Might be worth noting that the type of "element" in Swift is UnsafePointer
– Litherum May 30 '15 at 23:45 -
6Ole Begemann just published an excellent post on this topic http://oleb.net/blog/2015/06/c-callbacks-in-swift/. – Gouldsc Jun 23 '15 at 00:02
-
Why is the second argument a block? The docs say "A pointer to the user data that Quartz will pass to the function being applied, or NULL.". I am trying to convert to swift some code that passes an array in that parameter. – Nicolas Miari Feb 29 '16 at 09:13
-
The second argument is a block because a block encapsulates code and data. You can pass a block that refers to an array. – rob mayoff Feb 29 '16 at 13:42
(Hint: if you have to support an iOS before iOS 11, use the accepted answer. If you can require iOS 11, this answer is much easier.)
Since iOS 11, there is an official answer from Apple to this question: CGPath.applyWithBlock(_:)
.
This makes all the dirty tricks unnecessary that come from the problem that CGPath.apply(info:function:)
is a C function that does not allow information transported in and out of the function in a usual swifty way.
The following code allows you to do:
let pathElements = path.pathElements()
To be able to do that, copy & paste
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
}
or take this code as an example to how to use CGPath.applyWithBlock(_:)
yourself.
For completeness, this is the official documentation from Apple: https://developer.apple.com/documentation/coregraphics/cgpath/2873218-applywithblock
Since iOS 13 there is an even more elegant official answer from Apple: Use SwiftUI
(even if your UI isn't in SwiftUI
)
Transform your cgPath
into a 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
}
}
Variable element
is of type Path.Element which is a pure Swift Enum, so there aren't even tricks necessary to get the values out of element.
For completeness, this is th official Apple documentation: https://developer.apple.com/documentation/swiftui/path/3059547-foreach

- 6,275
- 3
- 44
- 89
Here's the highlights from Ole Begemann's great post (thanks @Gouldsc!), adapted for Swift 3, which allows for accessing the individual elements composing a UIBezierPath
instance:
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
}
}
}

- 337
- 6
- 7
-
2
-
I wonder is it possible to clear some of the points and create a separate UIBezierPath using remaining element points? Basically I want to erase some of the paths from original bezierPath. Thanks – Lasantha Basnayake Dec 11 '19 at 11:40
Dmitry Rodionov has produced a function for converting a Swift function to a CFunctionPointer (see 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;
}
I am using this successfully with CGPathApply along with a Swift callback function. (code at http://parker-liddle.org/CGPathApply/CGPathApply.zip) Although as Dmitry says this is a reverse engineered function and not a supported one.

- 81
- 4
-
Wow! That's some really cool hackery! I think there are other ways to achieve it now that don't require going to that depth, but it's great to see people having that kind of facility and pulling things like that off in the face of dire coding adversity! – clearlight Oct 20 '19 at 16:58