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!!!
Thank you so much for posting this. It solved my issue (that I spent hours trying to solve). I appreciate it.