user1021430 user1021430 - 1 year ago 237
Swift Question

How to convert CFArray to Swift Array?

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...

Answer Source

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.