Inline below is is a method I acquired somewhere for extending NSBezierPath
using a convenience initializer that takes CGPath
as an argument so that it behaves more like UIBezierPath
on iOS.
It worked previously, but when I try to compile it (years later) on Swift 5, I get the the following compile time error:
A C function pointer cannot be formed by a closure that captures context
How can I resolve that?
convenience init(path : CGPath) {
path.apply(info: nil, function: { (_, elementPointer) in
let element = elementPointer.pointee
switch element.type {
case .moveToPoint:
let points = Array(UnsafeBufferPointer(start: element.points, count: 1))
self.move(to: points[0])
break
case .addLineToPoint:
let points = Array(UnsafeBufferPointer(start: element.points, count: 1))
self.line(to: points[0])
break
case .addQuadCurveToPoint:
let points = Array(UnsafeBufferPointer(start: element.points, count: 2))
let qp0 = self.currentPoint
let qp1 = points[0]
let qp2 = points[1]
let m = CGFloat(2.0 / 3.0)
var cp1 = NSPoint()
var cp2 = NSPoint()
cp1.x = (qp0.x + ((qp1.x - qp0.x) * m))
cp1.y = (qp0.y + ((qp1.y - qp0.y) * m))
cp2.x = (qp2.x + ((qp1.x - qp2.x) * m))
cp2.y = (qp2.y + ((qp1.y - qp2.y) * m))
self.curve(to: qp2, controlPoint1:cp1, controlPoint2:cp2)
case .addCurveToPoint:
let points = Array(UnsafeBufferPointer(start: element.points, count: 3))
self.curve(to:points[2], controlPoint1:points[0], controlPoint2:points[1])
break
case .closeSubpath:
self.close()
@unknown default:
break;
}
})
}
I’d suggest using path.applyWithBlock
. I’d also lose all of those unswifty break
statements and just access element.points
directly.
Perhaps something like:
convenience init(path: CGPath) {
self.init()
path.applyWithBlock { elementPointer in
let element = elementPointer.pointee
switch element.type {
case .moveToPoint:
move(to: element.points[0])
case .addLineToPoint:
line(to: element.points[0])
case .addQuadCurveToPoint:
let qp0 = self.currentPoint
let qp1 = element.points[0]
let qp2 = element.points[1]
let m = CGFloat(2.0 / 3.0)
let cp1 = NSPoint(x: qp0.x + ((qp1.x - qp0.x) * m),
y: qp0.y + ((qp1.y - qp0.y) * m))
let cp2 = NSPoint(x: qp2.x + ((qp1.x - qp2.x) * m),
y: qp2.y + ((qp1.y - qp2.y) * m))
curve(to: qp2, controlPoint1: cp1, controlPoint2: cp2)
case .addCurveToPoint:
curve(to: element.points[2], controlPoint1: element.points[0], controlPoint2: element.points[1])
case .closeSubpath:
close()
@unknown default:
break
}
}
}
Just as a proof of concept, I created a CGPath
with all of the different element types, and created a NSBezierPath
from that using the above. I then stroked both of them using their respective API (the NSBezierPath
in the heavy blue stroke, the CGPath
in a white stroke, on top of it). It’s a quick empirical validation that the conversion logic yielded equivalent paths: