Search code examples
iosswiftswift2core-text

How to Get a C Pointer's rawvalue in Swift?


I'm working on a project that uses CoreText; I need to initialize a CTRunDelegateCallbacks:

var  imageCallback =  CTRunDelegateCallbacks(version: kCTRunDelegateCurrentVersion, dealloc: { (refCon) -> Void in
    print("RunDelegate dealloc")
    }, getAscent: { ( refCon) -> CGFloat in
        return 0
    }, getDescent: { (refCon) -> CGFloat in
        return 0
    }) { (refCon) -> CGFloat in
        return 0
}

The parameter refCon is UnsafeMutablePointer<Void> type, which is also called void * type in C. I want to get the pointer's raw value. How to do it?


Solution

  • I do not recommend converting the pointer to a non-pointer type. I'll show you how to do it at the end of this answer, but first I'll show you how you should really handle refCon.

    Create a struct to hold whatever information you need to pass through to the callbacks. Example:

    struct MyRunExtent {
        let ascent: CGFloat
        let descent: CGFloat
        let width: CGFloat
    }
    

    Then create an UnsafeMutablePointer using its alloc class method, and initialize the allocated storage:

    let extentBuffer = UnsafeMutablePointer<MyRunExtent>.alloc(1)
    extentBuffer.initialize(MyRunExtent(ascent: 12, descent: 4, width: 10))
    

    In your callbacks, convert the pointer argument to an UnsafePointer<MyRunExtent> and pull what you need out of its memory:

    var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { pointer in
        pointer.dealloc(1)
        }, getAscent: { pointer in
            return UnsafePointer<MyRunExtent>(pointer).memory.ascent
        }, getDescent: { pointer in
            return UnsafePointer<MyRunExtent>(pointer).memory.descent
        }, getWidth: { pointer in
            return UnsafePointer<MyRunExtent>(pointer).memory.width
    })
    

    Now you can create your delegate, using callbacks and extentBuffer:

    let delegate = CTRunDelegateCreate(&callbacks, extentBuffer)!
    

    Here's a test:

    let richText = NSMutableAttributedString(string: "hello \u{FFFC} world")
    richText.addAttribute(kCTRunDelegateAttributeName as String, value: delegate, range: NSMakeRange(6, 1))
    let line = CTLineCreateWithAttributedString(richText)
    let runs = (CTLineGetGlyphRuns(line) as [AnyObject]).map { $0 as! CTRun }
    runs.forEach {
        var ascent = CGFloat(0), descent = CGFloat(0), leading = CGFloat(0)
        let width = CTRunGetTypographicBounds($0, CFRangeMake(0, 0), &ascent, &descent, &leading)
        print("width:\(width) ascent:\(ascent) descent:\(descent) leading:\(leading)")
    }
    

    The output (in a playground):

    2015-12-21 12:26:00.505 iOS Playground[17525:8055669] -[__NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0x7f94bcb01dc0
    width:28.6875 ascent:9.240234375 descent:2.759765625 leading:0.0
    width:10.0 ascent:12.0 descent:4.0 leading:0.0
    width:32.009765625 ascent:9.240234375 descent:2.759765625 leading:0.0
    

    The first line of output is because the playground execution process can't encode the delegate to send back to Xcode for display, and turns out to be harmless. Anyway, you can see that the bounds of the middle run were computed using my callbacks and the content of my extentBuffer.

    And now, the moment you've been waiting for…

    You can get the pointer's “raw value” this way, if the pointer and an Int are the same size on the running system:

    let rawValue = unsafeBitCast(refCon, Int.self)
    

    If they're different sizes, you'll get a fatal error at runtime.

    You could cast it to a CGFloat this way, if the pointer and a CGFloat are the same size:

    let float = unsafeBitCast(refCon, CGFloat.self)