Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 473
user1021430 Avatar asked Jul 10 '14 19:07

user1021430


3 Answers

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.

like image 74
user1021430 Avatar answered Nov 01 '22 21:11

user1021430


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.

like image 7
pointum Avatar answered Nov 01 '22 21:11

pointum


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

like image 8
Martin R Avatar answered Nov 01 '22 20:11

Martin R