According to Apple's "Using Swift with Cocoa and Objective-C", "In Swift, you can use each pair of toll-free bridged Foundation and Core Foundation types interchangeably". This makes working with Core Foundation sound way simpler than it actually is...
I am trying to work with a CFArray that is returned from CoreText. I have this code:
let lines: CFArrayRef = CTFrameGetLines(frame)
I see two possible ways to access members of this array. Neither is working for me right now.
Way #1 - Use the CFArray directly
let line: CTLineRef = CFArrayGetValueAtIndex(lines, 0)
This yields the error "'ConstUnsafePointer<()>' in not convertible to 'CTLineRef'". Casting does not seem to change this error.
Similarly, I would love to use lines "interchangeably" as a Swift array like it says that I can. However,
let line: CTLineRef = lines[0]
yields the error "'CFArrayRef' does not have a member named 'subscript'"
Way #2 - Convert the CFArray to a Swift array
var linesArray: Array = [CTLineRef]()
linesArray = bridgeFromObjectiveC(lines, linesArray.dynamicType)
Here, I declared a Swift array and set it equal to the bridged CFArray. This compiles without error, but when I run it, I get an EXC_BREAKPOINT crash on the second line. Perhaps I'm not using the Swift language correctly on this one...
Here is how to do this, based on the current state of the Swift compiler and Swift documentation. Hopefully this gets cleaned up in later betas.
UPDATE: Since Beta 5, reinterpretCast has been renamed to unsafeBitCast, and a CTLine object must be sent to it as an input. Way #2 still does not work.
Way #1 - Use the CFArray directly
let line: CTLine = reinterpretCast(CFArrayGetValueAtIndex(lines, 0))
Regarding Gary Makin's comments - The Ref can be dropped from CTLineRef, but this does not change ARC vs non-ARC. According to Using Swift with Cocoa and Objective-C pages 53-54, ref and non-ref are identical to the compiler. Attempting to call CFRelease causes a compiler error.
Way #2 - Convert the CFArray to a Swift array - Does not currently work
Ideally, we want to convert lines to a Swift array of CTLine objects since we know that's what is returned by CTFrameGetLines, giving us type safety after the conversion. Probably due to a compiler bug, the array can be converted to an [AnyObject] array, but not to [CTLine]. According to Apple's documentation, this should work:
let linesNS: NSArray = CTFrameGetLines(frame)
let linesAO: [AnyObject] = linesNS as [AnyObject]
let lines: [CTLine] = linesAO as [CTLine]
This converts CFArray to NSArray, then NSArray to Swift Array [AnyObject], then downcasts that array to the specific type CTLine. This compiles, but when it is run, there is an EXC_BREAKPOINT crash on the last line.
Apparently in Swift 2 you can cast CFArray as [AnyObject]
.
I spent a lot of time figuring out why my code stopped working after converting from Swift 1.2. In my case it turned out that some API changed from CFArray! to CFArray? and this cast was returning nil:
let cfArray = ... // Function that returns CFArray? instead of CFArray!
if let array = cfArray as? [AnyObject] { // nil
...
There's no warning that I'm optionally casting optional to non-optional, which makes no sense.
As of Swift 3, CFArray
can be bridged to [CTLine]
directly:
let lines = CTFrameGetLines(frame) as! [CTLine]
guard let firstLine = lines.first else {
return // no line
}
And in the same way:
let runs = CTLineGetGlyphRuns(firstLine) as! [CTRun]
guard let firstRun = runs.first else {
return // no run
}
(tested with Swift 3.1/Xcode 8.3.3 and Swift 4.0/Xcode 9).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With