iOS CoreText如何通过CTFontManagerRegisterGraphicsFont获取已注册字体列表?

5

我正在通过以下方式动态注册字体:

- (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;
    }
}

第一次工作得很好。似乎如果我关闭应用程序并尝试再次注册同一字体,则会产生(预期的)错误:“无法加载字体:错误域=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>}。
这似乎是因为字体已经被注册了。 《CTFontManagerRegisterGraphicsFont》的文档说明如下:
“向字体管理器注册指定的图形字体。通过字体描述符匹配,可以发现已注册的字体。尝试注册已经注册或包含与已经注册的字体相同的PostScript名称的字体将失败。”
到底如何进行“通过字体描述符匹配”呢??
我该如何获取通过CTFontManagerRegisterGraphicsFont方法注册过的所有字体列表,以便在重新注册之前注销它们?
编辑:
我已经尝试使用CTFontManagerCopyAvailablePostScriptNamesCTFontManagerCopyAvailableFontFamilyNames方法,但两者都只打印出iOS上已有的字体名称。而非我通过CTFontManagerRegisterGraphicsFont注册的字体。
注意:我不是在询问iOS上已有的字体,这些可以通过[UIFont familyNames]迭代列出。

有人可以帮我吗? - sudoExclaimationExclaimation
“关闭应用程序”具体指什么? - rob mayoff
另外,您尝试过CTFontManagerCopyAvailablePostScriptNames吗? - rob mayoff
@robmayoff 是的,我已经尝试使用CTFontManagerCopyAvailablePostScriptNamesCTFontManagerCopyAvailableFontFamilyNames方法,但两者都只打印出iOS上已有字体的名称,而不是我通过CTFontManagerRegisterGraphicsFont注册的字体。 - sudoExclaimationExclaimation
@robmayoff,很抱歉打扰您了。您还有其他建议可以尝试吗? - sudoExclaimationExclaimation
显示剩余6条评论
3个回答

3
我向苹果的DTS(开发技术支持)提交了一个工单,他们回复说:
“您需要跟踪使用CTFontManagerRegisterGraphicsFont注册的字体。 CTFontManagerRegisterGraphicsFont返回的错误代码kCTFontManagerErrorAlreadyRegistered将告诉您是否已经注册了字体。使用字体描述符匹配来发现安装的字体可能不是一个好方法,因为系统可能选择对缺失字体执行字体替换。使用CTFontManagerRegisterGraphicsFont安装字体只是使其可供应用程序使用。它不是一种查询服务,用于发现已安装的字体。如果这对您的偏好不足够,请考虑提出功能请求,要求您想要接收的功能。”
因此,基本上我们需要自己跟踪我们注册的字体。
我最终使用的解决方法/变通方法是:
目前,我的应用程序允许用户使用“复制到MYAPP”按钮在字体文件上添加字体。这个解决方案也适用于我从服务器下载的字体文件。
为了使我的应用程序在ttf和otf文件的操作表中列出,在我的应用程序的info.plist中,我添加了一个新的文档类型:
    <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>

这使得我的应用程序可以在任何字体文件上显示在"操作表"中。因此,用户可以将字体文件放在Dropbox、Google Drive或任何其他文件共享应用程序中。然后,他们可以从那里将字体导入到我的应用程序中。导入后,我需要将字体文件从临时的"tmp inbox"文件夹移动到我的应用程序的"Documents fonts"目录中。"fonts"目录保存所有自定义字体。之后,我注册这个自定义字体,并将其名称添加到"self.arrayOfFonts"数组中。这是包含所有我的字体列表的数组。
此外,似乎"CTFontManagerRegisterGraphicsFont"仅适用于会话。因此,当应用程序从应用切换器中关闭并重新启动时,该字体不再注册。因此,在每次启动后,我都会浏览我的"documents fonts"文件夹,重新注册所有字体,并将其名称添加到"self.arrayOfFonts"数组中。
为了使其可行,请查看我的应用代码:
- (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;
}

注意:如您所见,我已将CTFontManagerUnregisterGraphicsFont注释掉。使用CTFontManagerUnregisterGraphicsFont注销字体似乎对我无效,因为它会出现错误,提示该字体正在使用中,因此无法注销。因此,当我需要删除字体时,我只需从self.arrayOfFonts数组和我的documents/fonts文件夹中删除即可。


1
自 iOS 13 起,您可以使用 CTFontManagerCopyRegisteredFontDescriptors 查询已安装在应用程序内部的字体。个人认为仍然缺少一种方法来查询由用户在应用程序外部安装的字体,使用第三方字体安装程序。 - Ely

1
你应该使用类似这样的东西:
- (void)getInstalledFonts {
    NSDictionary *descriptorOptions = @{(id)kCTFontDownloadableAttribute : @YES};
    CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorOptions);
    CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL);
    [self showExistingFonts:(NSArray *)CFBridgingRelease(fontDescriptors)];
    CFRelease(descriptor);
}

- (void)showExistingFonts:(NSArray *)fontList {
    NSMutableDictionary *fontFamilies = [NSMutableDictionary new];
    for(UIFontDescriptor *descriptor in fontList) {
        NSString *fontFamilyName = [descriptor objectForKey:UIFontDescriptorFamilyAttribute];
        NSMutableArray *fontDescriptors = [fontFamilies objectForKey:fontFamilyName];
        if(!fontDescriptors) {
            fontDescriptors = [NSMutableArray new];
            [fontFamilies setObject:fontDescriptors forKey:fontFamilyName];
        }

        [fontDescriptors addObject:descriptor];
    }

}

取自: https://www.shinobicontrols.com/blog/ios7-day-by-day-day-22-downloadable-fonts

我尝试了这个,并且使用fontFamilyName进行了NSLog。我测试了3种自定义字体,但它们中没有一种被列出。它打印出了所有名称,我也可以通过[UIFont familyNames]获取它们,但它没有打印我通过CTFontManagerRegisterGraphicsFont注册的自定义字体。 - sudoExclaimationExclaimation
尝试使用其他的descriptorOptions。有许多可用的选项。https://developer.apple.com/documentation/coretext/core_text_constants - Lefteris
我尝试了kCTFontDownloadedAttributekCTFontDownloadableAttribute,但它们都不起作用。我联系了苹果DTS,他们说我需要自己跟踪我的字体,并且“使用字体描述符匹配来发现您的字体是否已安装可能不是一个好方法,因为系统可能会选择对缺失的字体进行字体替换。”最终,我使用了一种解决方法,将其在此处分享: https://dev59.com/sqLia4cB1Zd3GeqPm6-s#45284412 - sudoExclaimationExclaimation
这很奇怪,因为他们的文档中正如你引用的那样说明了不同的情况。 - Lefteris
没错,我同意你的看法。但是目前我会采用绕过解决方案,因为我已经在这个问题上花费了很多时间。感谢你的回复! - sudoExclaimationExclaimation

0

我发现如果你曾经使用UIFont(name:)与动态加载的字体,那么CTFontManagerUnregisterFontsForURL不像上面所述一样起作用。

因此,解决方法是使用

let desc = UIFontDescriptor(name: <name>, size: <size>)

let font = UIFont(descriptor: desc, size: <size>)

如果您在任何时候使用UIFont(name :),则注销不会真正注销它。

我发现对于CTFontManager Register/Unregstier FontsForURL api和GraphicsFont api也是如此。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接