How to draw String in CGContext?
Asked Answered
F

2

10

I am trying to draw a string in the overriden draw method of CALayer (I'm programming for iOS).

override func draw(in ctx: CGContext) {
    let font = UIFont.systemFont(ofSize: 30)
    let string = NSAttributedString(string: "23", attributes: [NSAttributedStringKey.font: font])
    string.draw(at: CGPoint(x: 200, y: 200))
}

This is however not drawing anything (at least nothing is visible). Changing fill and stroke color does not make a difference.

If I draw a line it will show, so the function is being called. I know there is a CATextLayer but I need to draw the string directly. How are you supposed to draw a string in CGContext in the Swift 4 era? No amount of net searching has yielded an answer.

Faculty answered 18/10, 2018 at 11:58 Comment(3)
I can't test it right now, but I know that not calling super on draw(ctx:) will probably be problematicPouf
CALayer documentation says: "The default implementation of this method does not do any drawing itself." Just to make sure I tried the code with a super-call: no difference.Faculty
I'm sorry, I don't know why I've associated your question with the UIView drawRect, my bad.Pouf
X
19

I assume you know all other settings. The key here is you have not make the CGContext as current one. Just add two lines code to solve the problem. Hope you get the answer.

   override func draw(in ctx: CGContext) {
   UIGraphicsPushContext(ctx)
    let font = UIFont.systemFont(ofSize: 30)
    let string = NSAttributedString(string: "23", attributes: [NSAttributedString.Key.font: font])
    string.draw(at: CGPoint(x: 200, y: 200))
   UIGraphicsPopContext()
}
Xe answered 18/10, 2018 at 14:20 Comment(1)
This solved it. Thanx a lot! I find it confusing that CGContext has all kinds of drawing methods but none to do basic text drawing.Faculty
J
8

The above answer works if a view is focused.

Unfortunately, it won't work in an offscreen Bitmap or CALayer context.

The right and universal way to draw a string in any CGContext is to use the CoreText api. It will work on all Apple platforms, and has a lot of power under the hood.

https://developer.apple.com/documentation/coretext

Example:

import Foundation
import Quartz

/// LazyFoxLayer
///
/// Will draw the "The lazy fox…" string in the bottom right corner of the layer,
/// with a margin of 10 pixels.

class LazyFoxLayer: CALayer {

    func draw(ctx: CGContext) {
        ctx.saveGState()
    
        // Parameters

        let margin: CGFloat = 10
        let color = CGColor.black
        let fontSize: CGFloat = 32
        // You can use the Font Book app to find the name
        let fontName = "Chalkboard" as CFString 
        let font = CTFontCreateWithName(fontName, fontSize, nil)

        let attributes: [NSAttributedString.Key : Any] = [.font: font, .foregroundColor: color]

        // Text

        let string = "The lazy fox…"
        let attributedString = NSAttributedString(string: string, 
                                                  attributes: attributes)

        // Render

        let line = CTLineCreateWithAttributedString(attributedString)
        let stringRect = CTLineGetImageBounds(line, ctx)

        ctx.textPosition = CGPoint(x: bounds.maxX - stringRect.width - margin, 
                                   y: bounds.minY + margin)

        CTLineDraw(line, ctx)

        ctx.restoreGState()
    }
}

Cheers :)

Jadwiga answered 28/9, 2020 at 7:23 Comment(4)
This worked very well. Thank you! One issue we're running into is the text is a little blurry. Is there anything we can to do to improve the drawing resolution or hit a pixel boundary better?Amalgam
Not sure..Maybe use standard text sizes (9, 11, 13) and maybe align text position on normalised values ( round(x), round(y) ). Check that CGContext.allowsAntialiasing is set to true.Jadwiga
It turned out “layer.contentsScale = 2” fixed the blurry rendering. Much appreciated again Moose. This was the only approach that we could find that worked directly on layers!Amalgam
FWIW, in more recent Swift versions, you'll need something like this: let attributes: [NSAttributedString.Key : Any]Yuzik

© 2022 - 2024 — McMap. All rights reserved.