Search code examples
swiftmacoscocoaappkit

NSAttributedString draw location different on 10.13 and 10.14+


I use NSAttributedStrings draw(in: rect) to draw a string into a view (same problem occurs with a plain NSString). Now the outcome seems to be different to what deployment target I set in Xcode. If the deployment target is 10.13, the draw call renders it like this

enter image description here

where the red box represents rect. If I switch the deployment target to 10.14 or above without touching the code, the same draw call renders like this

enter image description here

Create an empty project, set the NSWindows content view class to TestView and create the class below.

class TestView: NSView {

    override func draw(_ dirtyRect: NSRect) {
        NSAttributedString(string: "9", attributes: [
            .font: NSFont.userFixedPitchFont(ofSize: 48)!,
            .foregroundColor: NSColor.labelColor
        ]).draw(in: bounds)
    }
}

Now switch between 10.13 and 10.14+ deployment target and the string location will shift. If you would check the strings .size() it returns 68 on 10.13 and 58 on 10.14+.

How do I make this consistent between platforms? Since it is inside a package the DT could vary, and I want to avoid different code paths.

Moreover the 10.14+ version seems more "correct".


Solution

  • I tried changing the font and noticed that the issue disappears if you're using the default system font. This led me to believe that the problem lies with the fixed-pitch font — it appears to have a different line-height depending on the DT.

    Setting a specific line-height via a paragraph style fixes it:

    override func draw(_ dirtyRect: NSRect) {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.minimumLineHeight = 58
        paragraphStyle.maximumLineHeight = 58
        NSAttributedString(string: "9", attributes: [
            .font: NSFont.userFixedPitchFont(ofSize: 48)!,
            .foregroundColor: NSColor.labelColor,
            .paragraphStyle: paragraphStyle
        ]).draw(in: bounds)
    }