iosfontscore-textuifont

iOS CoreText get list of registered fonts via CTFontManagerRegisterGraphicsFont


I am registering fonts dynamically via the following:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{

    if ([url isFileURL])
    {
        // Handle file being passed in
        NSLog(@"handleOpenURL: %@",url.absoluteString);


        NSData *inData = [NSData dataWithContentsOfURL:url];
        CFErrorRef error;
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
        CGFontRef fontRef = CGFontCreateWithDataProvider(provider);

        UIFont *font;
        if (!CTFontManagerRegisterGraphicsFont(fontRef, &error)) {
            CFStringRef errorDescription = CFErrorCopyDescription(error);
            NSLog(@"Failed to load font: %@", error);
            CFRelease(errorDescription);
        } else {
            CFStringRef fontNameRef = CGFontCopyPostScriptName(fontRef);
            NSLog(@"fontNameRef: %@",fontNameRef);
            font = [UIFont fontWithName:(__bridge NSString *)fontNameRef size:80];
            [self.arrayOfFonts addObject:(__bridge NSString *)fontNameRef];
            [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshFont" object:nil];
            CFRelease(fontNameRef);
        }
        CFRelease(fontRef);
        CFRelease(provider);
        return YES;
    }
    else
    {
        return NO;
    }
}

It works fine first time. It appears that if I close the app and try to register the same font again, then it gives me the (expected) error "Failed to load font: Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=105 "Could not register the CGFont '<CGFont (0x1c00f5980): NeuropolXRg-Regular>'" UserInfo={NSDescription=Could not register the CGFont '<CGFont (0x1c00f5980): NeuropolXRg-Regular>', CTFailedCGFont=<CGFont (0x1c00f5980): NeuropolXRg-Regular>}"

This seems to be because the font is already registered. The documentation for CTFontManagerRegisterGraphicsFont states that it:

"Registers the specified graphics font with the font manager. Registered fonts are discoverable through font descriptor matching. Attempts to register a font that is either already registered or contains the same PostScript name of an already registered font will fail."

How exactly to do "through font descriptor matching"??

How can I get a list of all fonts which have been registered via the CTFontManagerRegisterGraphicsFont method so I can unregister them before registering again?

EDIT:

I have tried using the CTFontManagerCopyAvailablePostScriptNames and CTFontManagerCopyAvailableFontFamilyNames methods but both only print out the names of the fonts already available on iOS. Not the ones I registered via CTFontManagerRegisterGraphicsFont

NOTE: I am NOT asking about the fonts already available on iOS which can be listed by iterating over [UIFont familyNames].


Solution

  • I logged a ticket with Apple DTS (Developer Technical Support) and they said:

    "You need to keep track of the fonts you have registered using CTFontManagerRegisterGraphicsFont yourself. The error code kCTFontManagerErrorAlreadyRegistered returned by CTFontManagerRegisterGraphicsFont will tell you if you have already registered a font. Using font descriptor matching to discover if your fonts are installed probably isn’t a good way to go as the system may choose to perform font substitution for missing fonts. Installing a font using CTFontManagerRegisterGraphicsFont simply makes it available for your app’s use. It is not a query service for discovering installed fonts. If that is not sufficient for your preferences, then I think it would be a good idea for you to consider filing a feature request asking for the functionality you would like to receive."

    So basically, we need to keep track of the fonts we register ourselves.

    Solution/Work-around I ended up using:

    Currently my app allows users to add fonts using the Action Sheet's "Copy to MYAPP" button on a font file. This same solution also works for font files which I download from my server.

    In order for my app to be listed in the Action Sheet for .ttf and .otf files, in my app's info.plist, I added a new Document type:

        <key>CFBundleDocumentTypes</key>
        <array>
            <dict>
                <key>CFBundleTypeIconFiles</key>
                <array/>
                <key>CFBundleTypeName</key>
                <string>Font</string>
                <key>LSItemContentTypes</key>
                <array>
                    <string>public.opentype-font</string>
                    <string>public.truetype-ttf-font</string>
                </array>
            </dict>
        </array>
    

    This allows my app to show in the action sheet on any font file. So user can put the font file on dropbox, google drive or any other file sharing app. Then they can import the font from there into my app. After importing, I need to move the font file from the temporary tmp inbox folder to my app's Documents fonts directory. The fonts directory keeps all the custom fonts. After that, I register this custom font and add the name to the self.arrayOfFonts array. This is the array which contains the list of all my fonts.

    Also it appears that CTFontManagerRegisterGraphicsFont is only for the session. So when the app is closed from the App Switcher and relaunched, that font is no longer registered. So after every startup, I go through my documents/fonts folder and re-register all the fonts and add their names to the self.arrayOfFonts array.

    Rest of my app code to get this working:

    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
    
        if ([url isFileURL])
        {
            // Handle file being passed in
            NSLog(@"handleOpenURL: %@, extension: %@",url.absoluteString,url.pathExtension);
            [self moveFontFrom:url];
            return YES;
        }
        else
        {
            return NO;
        }
    }
    
    -(void)moveFontFrom:(NSURL*)fromurl{
        NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0] stringByAppendingPathComponent:@"fonts"];
        // New Folder is your folder name
        NSError *error1 = nil;
        if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath]){
            [[NSFileManager defaultManager] createDirectoryAtPath:stringPath withIntermediateDirectories:NO attributes:nil error:&error1];
        }
        NSLog(@"error1: %@", error1.debugDescription);
        NSURL *tourl = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@",stringPath,[[fromurl absoluteString] lastPathComponent]] isDirectory:NO];
    
        NSLog(@"Trying to move from:\n\n%@\n\nto:\n\n%@\n\n", fromurl.absoluteString,tourl.absoluteString);
    
        NSError* error2;
        if ([[NSFileManager defaultManager] fileExistsAtPath:tourl.path]){
            [[NSFileManager defaultManager] removeItemAtPath:tourl.path error:&error2];
            NSLog(@"Deleting old existing file at %@ error2: %@", tourl.path,error2.debugDescription);
        }
    
    
        NSError* error3;
        [[NSFileManager defaultManager] moveItemAtURL:fromurl toURL:tourl error:&error3];
        NSLog(@"error3: %@", error3.debugDescription);
    
        if (!error3) {
            NSString *fontName = [self registerFont:tourl checkIfNotify:YES];
            if (fontName) {
                if (![self.arrayOfFonts containsObject:fontName]) {
                    [self.arrayOfFonts addObject:fontName];
                    [self.arrayOfFonts sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
                    [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshFont" object:nil userInfo:@{@"font":fontName}];
                }
            }
        }
    }
    
    -(void)startupLoadFontsInDocuments{
    
        self.arrayOfFonts = [NSMutableArray new];
        NSString *location = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0] stringByAppendingPathComponent:@"fonts"];
        NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:location error:NULL];
        for (NSInteger count = 0; count < [directoryContent count]; count++)
        {
            NSLog(@"File %ld: %@/%@", (count + 1), location,[directoryContent objectAtIndex:count]);
            NSString *fontName = [self registerFont:[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@",location,[directoryContent objectAtIndex:count]] isDirectory:NO] checkIfNotify:NO];
            if (fontName) {
                if (![self.arrayOfFonts containsObject:fontName]) {
                    [self.arrayOfFonts addObject:fontName];
                }
            }
        }
        [self.arrayOfFonts sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    }
    
    -(NSString*)registerFont:(NSURL *)url checkIfNotify:(BOOL)checkIfNotify{
        NSData *inData = [NSData dataWithContentsOfURL:url];
        CFErrorRef registererror;
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
        CGFontRef fontRef = CGFontCreateWithDataProvider(provider);
        NSString *fontName = (__bridge NSString *)CGFontCopyPostScriptName(fontRef);
        BOOL registerFontStatus = CTFontManagerRegisterGraphicsFont(fontRef, &registererror);
        if (!registerFontStatus) {
            CFStringRef errorDescription = CFErrorCopyDescription(registererror);
            NSError *registererr = (__bridge NSError*)registererror;
            if ([registererr code]==kCTFontManagerErrorAlreadyRegistered) {
                NSLog(@"Font is already registered!");
            }
            NSLog(@"Failed to load font: %@", registererror);
            CFRelease(errorDescription);
    
            /*CFErrorRef unregistererror;
        BOOL unregisterFont = CTFontManagerUnregisterGraphicsFont(fontRef, &unregistererror);
        NSLog(@"Font unregister status: %d",unregisterFont);
        CFStringRef unregistererrorDescription = CFErrorCopyDescription(unregistererror);
        NSError *unregistererr = (__bridge NSError*)unregistererror;
        NSInteger code = [unregistererr code];
    
        NSLog(@"Failed to unregister font: %@", unregistererr);
        CFRelease(unregistererrorDescription);*/
    
            if (checkIfNotify) {
                UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Already added" message:@"That font is already added to the app. Please select it from the fonts list." preferredStyle:UIAlertControllerStyleAlert];
                [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    
                }]];
                [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
            }
        } else {
            CFStringRef fontNameRef = CGFontCopyPostScriptName(fontRef);
            fontName = (__bridge NSString*)fontNameRef;
            CFRelease(fontNameRef);
    
            NSLog(@"fontName: %@",fontName);
        }
        CFRelease(fontRef);
        CFRelease(provider);
        return fontName;
    }
    

    NOTE: As you will noticed, I have commented out the CTFontManagerUnregisterGraphicsFont. The CTFontManagerUnregisterGraphicsFont used to unregister fonts doesn't seem to work for me as it gives an error saying the Font is in use and hence can't be un-registered. So when I need to remove a font, I simply remove it from the self.arrayOfFonts array and my documents/fonts folder.