Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 13 Custom Fonts download and installation

I want to download a font using CoreText API but it works in iOS 12, NOT available on iOS 13.

There is a demo project from Apple in 2013. But The API it used seems to be private since iOS 13.

/*     
    @param      progressBlock
Callback block to indicate the progress.
Return true to continue, and return false to cancel the process.
This block is called on a private serial queue on OS X 10.15, iOS 13, and later.
     
*/

@available(iOS 6.0, *)
public func CTFontDescriptorMatchFontDescriptorsWithProgressHandler(_ descriptors: CFArray, _ mandatoryAttributes: CFSet?, _ progressBlock: @escaping CTFontDescriptorProgressHandler) -> Bool

As the documentation declared:

This block is called on a private serial queue on OS X 10.15, iOS 13, and later.

Here is my code for font download:

let fontName = "STXingkai-SC-Light"
let attributes = [kCTFontNameAttribute : fontName] as CFDictionary
let fontDescription = CTFontDescriptorCreateWithAttributes(attributes)
// create font font descriptor and add it in an array
let descs = [fontDescription] as CFArray
CTFontDescriptorMatchFontDescriptorsWithProgressHandler(descs, nil) { (state, progressParamater) -> Bool in
    let progressValue = (progressParamater as Dictionary)[kCTFontDescriptorMatchingPercentage]?.doubleValue
    
    switch state {
    case .didBegin: print("didBegin")
    case .didFinish: print("didFinish")
    case .willBeginDownloading: print("willBeginDownloading")
    case .didFinishDownloading:
        print("--> download finish")
        DispatchQueue.main.async {
            self.fontLabel.font = UIFont(name: self.fontName, size: 20)
        }
    case .downloading:
        print("downloading#####\(progressValue ?? 0.0)")
        DispatchQueue.main.async {
            self.progressView.progress = Float(progressValue ?? 0.0)
        }
    case .didFailWithError:
        if let error = (progressParamater as Dictionary)[kCTFontDescriptorMatchingError] as? NSError {
            print(error.description)
        } else {
            print("ERROR MESSAGE IS NOT AVAILABLE")
        }
        
    default: print(String(reflecting: state))
    }
    
    return true
    
}

I tried this API on iOS 12 and Xcode 10, everything works fine and I can download a font with provided fontName.

font download successfully

But, when I use this one on Xcode 11 Version 11.0 beta 6 (11M392q) and macOS Catalina 10.15 Beta (19A526h), something went wrong and that method is no longer effective. And I got some output:

didBegin
__C.CTFontDescriptorMatchingState
didFinish
done
"Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=303 \"0 
font descriptors do not have information to specify a font file.\"
UserInfo={NSLocalizedDescription=0 
font descriptors do not have information to specify a font file.

0 font descriptors do not have information to specify a font file

The state will only have didBegin and didFinish, not calling downloading.

WWDC19 seeesion 227 has announced that CTFontManagerRegisterFontDescriptors(fontDescriptors:scope:enabled:registrationHandler:) is available and something about font management. But the source code from the session pdf is not clearly and I've tried, got an other error:

done
[Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=306 "The file is not in an allowed location. It must be either in the application’s bundle or an on-demand resource." UserInfo={NSLocalizedDescription=The file is not in an allowed location. It must be either in the application’s bundle or an on-demand resource., CTFontManagerErrorFontURLs=(
    "http://iweslie.com/fonts/HanziPenSC-W3.ttf"
), NSLocalizedFailureReason=Font registration was unsuccessful.}]
done

Here is my code referencing from WWDC19 session 227:

let urlString = "http://iweslie.com/fonts/HanziPenSC-W3.ttf"
let url = URL(string: urlString)! as CFURL
let fontURLArray = [url] as CFArray
CTFontManagerRegisterFontURLs(fontURLArray, .persistent, true) { (errors, done) -> Bool in
    if done {
        print("done")
    }
    print(errors as Array)
    return true
}

And got the error:

Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=306

"The file is not in an allowed location. It must be either in the application’s bundle or an on-demand resource."

like image 886
Weslie Avatar asked Oct 24 '25 05:10

Weslie


1 Answers

For other people looking for a solution for iOS 13 The easiest way to do it is probably using Apple Bundle and the 'new' CTFontManagerRegisterFontURLs api. Just gather all your urls in a list by utilizing Bundle.main.url:

let fontUrl = Bundle.main.url(forResource: fileName, withExtension: "ttf")

Then just register your fonts:

CTFontManagerRegisterFontURLs([fontUrl] as CFArray, .persistent, true) { (errors, done) -> Bool in
        if(done) {
            print("Done")
        }
        print(errors as Array)
        return true
    }

If you're using another solution and having problems with the font name it might be xcode which is at fault. A tip I saw in another thread for figuring out what xcode is naming your fonts are:

for family in UIFont.familyNames.sorted() {
        let names = UIFont.fontNames(forFamilyName: family)
        print("Family: \(family) Font names: \(names)")
    }

Also remember to enable Install Fonts capability under the Signing & Capabilities in your project settings.

like image 155
Zago Avatar answered Oct 26 '25 17:10

Zago