iOS SpriteKit – Font Loading Times of SKLabelNodes

The other day I encountered massive performance bottlenecks in my SpriteKit project, when setting the .text property of an SKLabelNode.

I discovered that I wasn’t alone.

After investigating many different techniques to pre-cache, thread and speed up font loading, I discovered the real solution was simply to load a very specific font. The documentation from Apple is somewhat poor, and leads newbies into the same trap.

If you’re experiencing problems with font loading speed it maybe because you’re loading the ENTIRE font family and all its variants.

If you want a font to load quickly, be explicit.
If I load “Chalkboard SE” it will take 4-6 seconds, and appear to work.

But if I load “ChalkboardSE-Regular” , it’s virtually instantaneous ~100ms or less.

To determine the exact font name you need, simply use a more specific font name from the list below, and pass it to your SKLabelNode constructor.

Here’s the code used to extract the list…

for( NSString* familyName in [[UIFont familyNames] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] )

{

NSLog(@"%@", @"\n");

for( NSString* explicitFontName in [UIFont fontNamesForFamilyName:familyName] )

{

NSLog(@"  %@", explicitFontName );

}

}

AcademyEngravedLetPlain

AlNile-Bold

AlNile

AmericanTypewriter-Light

AmericanTypewriter-CondensedLight

AmericanTypewriter-CondensedBold

AmericanTypewriter

AmericanTypewriter-Condensed

AmericanTypewriter-Bold

AppleColorEmoji

AppleSDGothicNeo-Thin

AppleSDGothicNeo-UltraLight

AppleSDGothicNeo-SemiBold

AppleSDGothicNeo-Medium

AppleSDGothicNeo-Regular

AppleSDGothicNeo-Bold

AppleSDGothicNeo-Light

ArialMT

Arial-BoldItalicMT

Arial-ItalicMT

Arial-BoldMT

ArialHebrew-Bold

ArialHebrew-Light

ArialHebrew

ArialRoundedMTBold

Avenir-Heavy

Avenir-Oblique

Avenir-Black

Avenir-Book

Avenir-BlackOblique

Avenir-HeavyOblique

Avenir-Light

Avenir-MediumOblique

Avenir-Medium

Avenir-LightOblique

Avenir-Roman

Avenir-BookOblique

AvenirNext-MediumItalic

AvenirNext-Bold

AvenirNext-UltraLight

AvenirNext-DemiBold

AvenirNext-HeavyItalic

AvenirNext-Heavy

AvenirNext-Medium

AvenirNext-Italic

AvenirNext-UltraLightItalic

AvenirNext-BoldItalic

AvenirNext-Regular

AvenirNext-DemiBoldItalic

AvenirNextCondensed-Regular

AvenirNextCondensed-MediumItalic

AvenirNextCondensed-UltraLightItalic

AvenirNextCondensed-UltraLight

AvenirNextCondensed-BoldItalic

AvenirNextCondensed-Italic

AvenirNextCondensed-Medium

AvenirNextCondensed-HeavyItalic

AvenirNextCondensed-Heavy

AvenirNextCondensed-DemiBoldItalic

AvenirNextCondensed-DemiBold

AvenirNextCondensed-Bold

BanglaSangamMN

BanglaSangamMN-Bold

Baskerville-Bold

Baskerville-SemiBoldItalic

Baskerville-BoldItalic

Baskerville

Baskerville-SemiBold

Baskerville-Italic

BodoniSvtyTwoITCTT-Book

BodoniSvtyTwoITCTT-Bold

BodoniSvtyTwoITCTT-BookIta

BodoniSvtyTwoOSITCTT-BookIt

BodoniSvtyTwoOSITCTT-Bold

BodoniSvtyTwoOSITCTT-Book

BodoniSvtyTwoSCITCTT-Book

BodoniOrnamentsITCTT

BradleyHandITCTT-Bold

ChalkboardSE-Light

ChalkboardSE-Regular

ChalkboardSE-Bold

Chalkduster

Cochin-Bold

Cochin-BoldItalic

Cochin-Italic

Cochin

Copperplate

Copperplate-Light

Copperplate-Bold

Courier

Courier-Oblique

Courier-BoldOblique

Courier-Bold

CourierNewPSMT

CourierNewPS-BoldMT

CourierNewPS-ItalicMT

CourierNewPS-BoldItalicMT

DamascusBold

Damascus

DamascusLight

DamascusMedium

DamascusSemiBold

DevanagariSangamMN

DevanagariSangamMN-Bold

Didot-Bold

Didot-Italic

Didot

DINAlternate-Bold

DINCondensed-Bold

EuphemiaUCAS

EuphemiaUCAS-Bold

EuphemiaUCAS-Italic

Farah

Futura-Medium

Futura-CondensedMedium

Futura-MediumItalic

Futura-CondensedExtraBold

GeezaPro-Bold

GeezaPro

Georgia-BoldItalic

Georgia-Bold

Georgia-Italic

Georgia

GillSans

GillSans-Italic

GillSans-BoldItalic

GillSans-Light

GillSans-LightItalic

GillSans-Bold

GujaratiSangamMN-Bold

GujaratiSangamMN

GurmukhiMN-Bold

GurmukhiMN

STHeitiSC-Medium

STHeitiSC-Light

STHeitiTC-Medium

STHeitiTC-Light

Helvetica-Oblique

Helvetica-Light

Helvetica-Bold

Helvetica

Helvetica-BoldOblique

Helvetica-LightOblique

HelveticaNeue-BoldItalic

HelveticaNeue-Light

HelveticaNeue-Italic

HelveticaNeue-UltraLightItalic

HelveticaNeue-CondensedBold

HelveticaNeue-MediumItalic

HelveticaNeue-Thin

HelveticaNeue-Medium

HelveticaNeue-ThinItalic

HelveticaNeue-LightItalic

HelveticaNeue-UltraLight

HelveticaNeue-Bold

HelveticaNeue

HelveticaNeue-CondensedBlack

HiraKakuProN-W6

HiraKakuProN-W3

HiraMinProN-W6

HiraMinProN-W3

HoeflerText-Regular

HoeflerText-BlackItalic

HoeflerText-Italic

HoeflerText-Black

IowanOldStyle-Bold

IowanOldStyle-BoldItalic

IowanOldStyle-Italic

IowanOldStyle-Roman

Kailasa

Kailasa-Bold

KannadaSangamMN

KannadaSangamMN-Bold

KhmerSangamMN

KohinoorDevanagari-Light

KohinoorDevanagari-Book

KohinoorDevanagari-Medium

LaoSangamMN

MalayalamSangamMN

MalayalamSangamMN-Bold

Marion-Regular

Marion-Italic

Marion-Bold

MarkerFelt-Thin

MarkerFelt-Wide

Menlo-BoldItalic

Menlo-Regular

Menlo-Bold

Menlo-Italic

DiwanMishafi

Noteworthy-Bold

Noteworthy-Light

Optima-Regular

Optima-Italic

Optima-Bold

Optima-BoldItalic

Optima-ExtraBlack

OriyaSangamMN

OriyaSangamMN-Bold

Palatino-Roman

Palatino-Italic

Palatino-Bold

Palatino-BoldItalic

Papyrus-Condensed

Papyrus

PartyLetPlain

SavoyeLetPlain

SinhalaSangamMN

SinhalaSangamMN-Bold

SnellRoundhand-Black

SnellRoundhand-Bold

SnellRoundhand

Superclarendon-Regular

Superclarendon-BoldItalic

Superclarendon-Light

Superclarendon-BlackItalic

Superclarendon-Italic

Superclarendon-LightItalic

Superclarendon-Bold

Superclarendon-Black

Symbol

TamilSangamMN

TamilSangamMN-Bold

TeluguSangamMN

TeluguSangamMN-Bold

Thonburi-Bold

Thonburi

Thonburi-Light

TimesNewRomanPS-BoldItalicMT

TimesNewRomanPSMT

TimesNewRomanPS-BoldMT

TimesNewRomanPS-ItalicMT

Trebuchet-BoldItalic

TrebuchetMS

TrebuchetMS-Bold

TrebuchetMS-Italic

Verdana-BoldItalic

Verdana-Italic

Verdana

Verdana-Bold

ZapfDingbatsITC

Zapfino

 

As for how I got here – Read my journey below!

 

See

http://stackoverflow.com/questions/20380954/delay-when-calling-sklabelnode

http://stackoverflow.com/questions/24478476/settext-is-slow-with-sprite-kit

http://stackoverflow.com/questions/23255143/sklabelnode-delays-app-start

Many workarounds are suggested in the attempt to prevent ‘stutter’ or pauses in the game, particularly during transitions between scenes where the font is used for the first time. The pause happens when the .text property is assigned (the font is obviously loaded on demand).

During testing, I noticed that keeping strong references to SKLabelNodes in a singleton cache doesn’t help – when transitioning to a new scene the fonts still re-loaded.

 

I decided to look at the differences in the time the thread is blocked loading the different font families.

There’s also a difference depending on if you’ve loaded another font beforehand.

The table – explained

The first timing is an average of 5 runs, where I simply iterated through all the font families installed on the device, and produced the time it took to load each without restarting the device.

The second set of timings show the cost of loading the font from cold (one run).

The third set of timings show the cost of loading the same font, having loaded Arial first (one run).

The final column shows the seconds potentially gained by loading Arial first – not including the time it took to load Arial.

 

Average time (s) over 5 runs loading all font family names  If Loaded First   If Loaded after Arial   Gain in doing so 
Academy Engraved LET

2.871

3.134

2.180

0.954

Al Nile

0.008

0.269

0.196

0.074

American Typewriter

0.015

0.039

0.007

0.032

Apple Color Emoji

0.015

0.257

0.213

0.044

Apple SD Gothic Neo

1.195

2.210

2.151

0.059

Arial

0.018

0.009

0.002

0.007

Arial Hebrew

0.006

0.226

0.180

0.046

Arial Rounded MT Bold

0.006

0.009

0.004

0.005

Avenir

1.421

2.165

2.154

0.011

Avenir Next

1.148

2.168

2.096

0.072

Avenir Next Condensed

1.195

2.409

2.234

0.175

Bangla Sangam MN

0.010

0.039

0.004

0.035

Baskerville

0.010

0.026

0.006

0.021

Bodoni 72

1.221

2.169

2.168

0.000

Bodoni 72 Oldstyle

1.261

3.046

2.508

0.538

Bodoni 72 Smallcaps

1.132

2.149

2.133

0.016

Bodoni Ornaments

0.004

0.006

0.004

0.003

Bradley Hand

1.252

2.192

2.066

0.126

Chalkboard SE

1.182

2.584

2.102

0.482

Chalkduster

0.008

0.021

0.004

0.017

Cochin

0.011

0.031

0.005

0.026

Copperplate

0.010

0.020

0.004

0.016

Courier

0.018

0.029

0.007

0.022

Courier New

0.002

0.023

0.006

0.018

Damascus

0.006

0.210

0.186

0.024

Devanagari Sangam MN

0.011

0.042

0.007

0.035

Didot

0.007

0.025

0.023

0.002

DIN Alternate

1.161

3.066

2.341

0.725

DIN Condensed

1.248

3.492

2.279

1.213

Euphemia UCAS

0.003

0.016

0.016

-0.001

Farah

0.003

0.237

0.215

0.022

Futura

1.174

2.185

2.317

-0.132

Geeza Pro

1.582

2.275

2.517

-0.242

Georgia

0.009

0.020

0.004

0.016

Gill Sans

0.004

0.036

0.035

0.002

Gujarati Sangam MN

0.008

0.016

0.007

0.009

Gurmukhi MN

0.005

0.013

0.005

0.008

Heiti SC

1.189

3.278

2.462

0.816

Heiti TC

1.191

2.474

2.179

0.295

Helvetica

0.001

0.023

0.007

0.016

Helvetica Neue

0.005

0.024

0.015

0.008

Hiragino Kaku Gothic ProN

1.275

2.939

2.229

0.710

Hiragino Mincho ProN

1.233

3.319

2.119

1.200

Hoefler Text

0.015

0.030

0.007

0.023

Iowan Old Style

1.246

2.151

2.281

-0.130

Kailasa

0.002

0.211

0.213

-0.002

Kannada Sangam MN

0.009

0.025

0.007

0.018

Khmer Sangam MN

0.013

0.011

0.006

0.005

Kohinoor Devanagari

1.166

3.374

2.112

1.262

Lao Sangam MN

0.003

0.009

0.005

0.004

Malayalam Sangam MN

0.007

0.015

0.005

0.010

Marion

1.432

3.151

2.494

0.657

Marker Felt

1.443

2.193

2.415

-0.222

Menlo

1.278

2.157

2.056

0.101

Mishafi

1.263

2.394

2.697

-0.304

Noteworthy

1.170

2.162

2.134

0.028

Optima

1.167

2.358

2.118

0.240

Oriya Sangam MN

0.006

0.017

0.006

0.011

Palatino

0.019

0.050

0.011

0.039

Papyrus

0.013

0.014

0.004

0.010

Party LET

1.177

3.299

2.140

1.159

Savoye LET

1.202

2.453

2.164

0.289

Sinhala Sangam MN

0.011

0.021

0.014

0.007

Snell Roundhand

0.010

0.064

0.006

0.058

Superclarendon

1.200

2.391

2.129

0.262

Symbol

0.002

0.181

0.226

-0.045

Tamil Sangam MN

0.008

0.016

0.004

0.011

Telugu Sangam MN

0.012

0.021

0.006

0.015

Thonburi

0.017

0.011

0.009

0.002

Times New Roman

0.018

0.034

0.007

0.028

Trebuchet MS

0.012

0.018

0.006

0.012

Verdana

0.010

0.026

0.006

0.020

Zapf Dingbats

0.002

0.263

0.255

0.008

Zapfino

0.008

0.063

0.011

0.053

Using threads

I also discovered a way of making the majority of the pause happen without blocking the main SpriteKit animation, and that was to invoke an NSThread in the background to load the fonts. This maybe ideal if you have other assets you are loading and you can then present a progress bar, or spinning loading symbol to help the user understand what’s going on. I’ve not looked into making atlases of my fonts or other tools like http://www.bmglyph.com.

In my first scene, I simply spawn a thread which loads the fonts.

[NSThreaddetachNewThreadSelector:@selector(cacheFont) toTarget:selfwithObject:nil];

And add a function which loads, and stores a strong reference to the label node to keep the font alive in memory. – I’ve not tested if that’s required, but S.O. mentioned it.

-(void)cacheFont
{
SKLabelNode* cacheThisFont = [SKLabelNode labelNodeWithFontNamed:@"Noteworthy"];
cacheThisFont.text = @"a"; // setting .text loads the font.

   // todo - possibly a strong reference to the node in a singleton or member on your app.
}

However this just feels wrong…

 

Scene transitions

I witnessed that having a bare NSObject singleton which kept strong references to the SKLabelNodes populated with text was not enough – subsequent scenes can still suffer a delay. I even tried using the [copy] message on the existing label nodes in the cache to speed things up, but that also demonstrated delays if the SKLabelNodes weren’t in any scene.

However, I did discover that keeping the SKLabelNodes as children in a scene, prevents the lengthy loading delay.

That means, that if you load all your fonts in scene 1 with a progress bar, you have to keep making sure that every scene you transition to has SKLabelNodes added to it.

In my architecture I lazy delete the previous scene on a timed action, which may be help keep the nodes alive between transitions.

However.. It seems odd to have to go to such trouble…

 

And then I discovered loading more SPECIFIC font names took no time at all….. Thanks Apple – for giving a bad code example!!!

 

One thought on “iOS SpriteKit – Font Loading Times of SKLabelNodes

  1. Thank you so much for posting this. It solved my issue (that I spent hours trying to solve). I appreciate it.

Leave a comment