swiftcocoansviewfontmetricsnsfont

Writing in NSView


I am trying to render text in a NSView canvas. I need to write three lines of text and ignore what's beyond. String.draw(in:withAttributes) with a provided rect seems perfect to do it. My code looks like this:

func renderText(_ string:String, x:Double, y:Double, numberOfLines: Int, withColor color:Color) -> Double {
    let font = NSFont.boldSystemFont(ofSize: 11)
    let lineHeight = Double(font.ascender + abs(font.descender) + font.leading)
    let textHeight = lineHeight * Double(numberOfLines) + font.leading // three lines
    let textRect = NSRect(x: x, y: y, width: 190, height: textHeight)
    string.draw(in: textRect, withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: color])
    return textHeight
}

renderText("Lorem ipsum...", x: 100, y: 100, numberOfLines: 3, withColor: NSColor.white)

Without adjustments, I get only two lines of text rendered:

enter image description here

I am following these guidelines: https://developer.apple.com/library/content/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html#//apple_ref/doc/uid/TP40009459-CH5-SW18

I am missing something?


Solution

  • Ultimately your text makes it to the screen by calling upon the classes that comprise Cocoa's text architecture, so it makes sense to get information about line height directly from these classes. In the code below I've created an NSLayoutManager instance, and set its typesetter behaviour property to match the value of the typesetter that is ultimately used by the machinery created by the function drawInRect:withAttributes:. Calling the layout manager's defaultLineHeight method then gives you the height value you're after.

    lazy var layoutManager: NSLayoutManager = {
        var layoutManager = NSLayoutManager()
        layoutManager.typesetterBehavior = .behavior_10_2_WithCompatibility
        return layoutManager
    }()
    
    func renderText(_ string:String, x:Double, y:Double, numberOfLines: Int, withColor color:NSColor) -> Double {
        let font = NSFont.boldSystemFont(ofSize: 11)
        let textHeight = Double(layoutManager.defaultLineHeight(for: font)) * Double(numberOfLines)
        let textRect = NSRect(x: x, y: y, width: 190, height: textHeight)
        string.draw(in: textRect, withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: color])
        return textHeight
    }