Outline text in SpriteKit

OutlinedText
I don’t know why, but there is no easy way to outline text in SpriteKit. You should expect SKLabelNode to have properties like strokeColor and strokeWidth. But as of this writing you don’t have a lot of options to customize SKLabelNode. Eventually Apple’s Core Text API, SKShapeNode and a little bit of magic did the trick.

Let’s get started

The first approach I found was using the ASAttributedLabelNode. Usage is extremely simple:

let myLabel = ASAttributedLabelNode(size: self.size)
myLabel.attributedString = myMutableString
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(myLabel)

The advantage is that it is an SKSpriteNode, so we can still use it as if it were an SKLabelNode. We can position it, add SKActions, and so on. The disadvantage is that it “eats” my letters. The thicker the border, the less letter I have left. Look at the following examples of a normal letter and the same letter outlined.

NormalText

FontEaten
ASAttributedLabelNode is a great class. It adds Attributed Strings to SpriteKit, but for outlining text it is not suitable.

Core Text

I continued my search and found a solution. The basic idea is to use an SKShapeNode to draw the border and add it as a child of your SKLabelNode. Something like this:

if let path = createBorderPathForText() {
    let border = SKShapeNode()

    border.strokeColor = borderColor
    border.lineWidth = 7;
    border.path = path
    border.position = positionBorder(border)
    labelNode.addChild(border)
}

The difficult part is how to create a border for your text. This is where Core Text kicks in. With the function CTFontGetGlyphsForCharacters you retrieve the glyphs for all characters in your text string. For every glyph you can create the CGPath using CTFontCreatePathForGlyph. The only thing you have to do is add the CGPath of all characters together and use this in your SKShapeNode. You can do this using the function CGPathAddPath. To obtain the relative positions of the glyphs/characters you can use the function CTFontGetAdvancesForGlyphs. Putting it all together:

private func createBorderPathForText() -> CGPathRef? {
    let chars = getTextAsCharArray()
    let borderFont = CTFontCreateWithName(self.fontName, self.fontSize, nil)
        
    var glyphs = Array(count: chars.count, repeatedValue: 0)
    let gotGlyphs = CTFontGetGlyphsForCharacters(borderFont, chars, &glyphs, chars.count)
        
    if gotGlyphs {
        var advances = Array(count: chars.count, repeatedValue: CGSize())
        CTFontGetAdvancesForGlyphs(borderFont, CTFontOrientation.OrientationHorizontal, glyphs, &advances, chars.count);
            
        let letters = CGPathCreateMutable()
        var xPosition = 0 as CGFloat
        for index in 0...(chars.count - 1) {
            let letter = CTFontCreatePathForGlyph(borderFont, glyphs[index], nil)
            var t = CGAffineTransformMakeTranslation(xPosition , 0)
            CGPathAddPath(letters, &t, letter)
            xPosition = xPosition + advances[index].width
        }
            
        return letters
    } else {
        return nil
    }
}

MKOutlinedLabelNode

I extended the SKLabelNode and put all the stuff in it. Now if I want to outline my label, all I have to do is the following:

let textNode = MKOutlinedLabelNode(fontNamed: "Helvetica", fontSize: 32)
textNode.borderColor = UIColor.whiteColor()
textNode.fontColor = UIColor.blueColor()
textNode.outlinedText = "Level 1"

And this will create the text outlined the way I want it.

OutlinedText

Hope this helps you. Let me know what you think of the solution or if you have an even better or cleaner solution.

The sourcecode is available on github.