Font downloaded with CTFontDescriptorMatchFontDescriptorsWithProgressHandler can not be registered using CTFontManagerRegisterFontsForURL

Originator:an00na
Number:rdar://16597440 Date Originated:11-Apr-2014
Status:Closed Resolved:iOS 11
Product:iOS SDK Product Version:7.1
Classification:Serious Bug Reproducible:Always
 
Summary:
CTFontManagerRegisterFontsForURL always fails on real device(though works on simulator) when registering downloaded font with such errors:
Error Domain=com.apple.coretext Code=101 "The operation couldn’t be completed. (com.apple.coretext error 101 - Could not register the font file 'file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/6d88595cff5e67eb57545959f9fff6060644ea6d.asset/AssetData/Xingkai.ttc')" UserInfo=0x178264740 {NSDescription=Could not register the font file 'file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/6d88595cff5e67eb57545959f9fff6060644ea6d.asset/AssetData/Xingkai.ttc', CTFailedFontFileURL=file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/6d88595cff5e67eb57545959f9fff6060644ea6d.asset/AssetData/Xingkai.ttc}

I looked up. Error 101 is kCTFontManagerErrorFileNotFound.

It makes font reusing across sessions very difficult. I have to call CTFontDescriptorMatchFontDescriptorsWithProgressHandler every time my app launches and but still can not assure the font is available for my user to use immediately since CTFontDescriptorMatchFontDescriptorsWithProgressHandler is async. 

If CTFontManagerRegisterFontsForURL can be used to register the downloaded font, I can cache the font URL and try CTFontManagerRegisterFontsForURL first on launch. If it succeeds — most of the time it should be the case — I just use it. If not, I then prompt my user to re-download the font. I believe it is the way the API is designed to be used. Otherwise, it is almost useless.

For reference, the same issue described by other people:
1. http://prod.lists.apple.com/archives/cocoa-dev/2014/Jan/msg00269.html
2. http://www.objc.io/issue-5/iOS7-hidden-gems-and-workarounds.html
"On the Mac or in the Simulator you can obtain the kCTFontURLAttribute to get the absolute path of the font and speed up loading time, but this won’t work on iOS, since the folder is outside of your app - you need to call CTFontDescriptorMatchFontDescriptorsWithProgressHandler again."

Steps to Reproduce:
1. Launch app.
2. Tap any font to download it.
3. Relaunch app.
4. Tap the just downloaded font.
5. Read the console log to see how CTFontManagerRegisterFontsForURL fails.

Sample Project: http://cl.ly/0S0Q323D3C3p

Comments

Disappointingly this issue remains in iOS 10

After 3 years, iOS downloadable fonts are still useless in many reasonable use cases.

My response at 11-Aug-2014 05:19 PM

Thanks for the workaround.

However, wrapping an async process into a sync one using lock or semaphore is not practically useful. Especially, as you mentioned, "sometimes it needs to access a server for updating the info" — it may block the thread for networking and networking is indeterministic.

Also, fonts are by nature used by UI elements which means it is the main thread that is blocked. I can move the actual blocking code to a background thread. However, in effect, I'm still blocking my users from using the fonts that they just downloaded in last session. That is, I have to present UI with system fonts initially on launch while I'm doing font matching in background thread. Only until the font matching is done that can I switch to use the user-chosen fonts and give them a visual flash caused by sudden font changing. It is far from good user experience.

You said "Because font assets may be purged while your app is not running, you shouldn't save font URL and reuse it."

I know font assets may be purged. Actually I expect font be purged while my app is not running. That's why I try fonts registration before using them on launch. Let's see all the cases if CTFontManagerRegisterFontsForURL works for downloaded font URLs.

Case 1: Fonts still exist. I can just register and use them immediately on launch. No blocking or flashing.

Case 2: Fonts are purged. I can detect it because CTFontManagerRegisterFontsForURL will fail(genuninely fail because fonts are not available, not because file access control issues as it is now). I'll use the default system fonts and notify the user that he need to re-download the fonts if he still wants to use them.

As you can see, the whole process is deterministic without networking involved and the user experience is reasonably good — after all font purging does not happen so often.

So I still want CTFontManagerRegisterFontsForURL to be usable for downloaded font URLs. Thanks!

Engineering has provided the following information at 11-Aug-2014 04:15 PM

Because font assets may be purged while your app is not running, you shouldn't save font URL and reuse it.

If you need to know which fonts are already downloaded, please check the following sample code.

"semaphore" is used to make it look like a "sync" function, but accessing downloadable font info may take longer (sometimes it needs to access a server for updating the info), so we would not recommend to do it in the main thread. // code is irrelevant because it doesn't solve real issue.

Please let us know whether this resolves the issue for you.

This bug is really critical. Due to this bug the whole downloadable fonts thing is unusable. It is such a cool feature. What a pity!

As a result, many Chinese developers are now packing font files copied from OS X directly in their apps. It is improper use of the fonts — I believe it violates copyrights. It also unnecessarily uses extra space on users devices — 10 apps will have 10 copies of Xingkai.ttc, for example.

I initially reported this bug on 28-Oct-2013 for iOS 7.0.3. But it was closed as "Behaves correctly". Of course, it does not behave correctly. So I reported it again on 11-Apr-2014 for iOS 7.1. I verify it against each iOS release ever since and update my bug report accordingly. Now it is iOS 8 beta 5 and it is still there. It is almost one year since my initial bug report. Before WWDC I was quite confident that this bug would be fixed in iOS 8 because it seems to me a very trivial bug. Now I've lost faith.

Another guy encountered this bug: https://devforums.apple.com/message/933059


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!