天天品尝iOS7甜点 :: Day 22 :: Downloadable Fonts

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


在iOS中就预先安装了一些字体,但是这并不是完整的。为了能够节省磁盘的映射空间,iOS提供了一种机制用来下载和在运行的时候使用字体。

苹果提供了一组字体,它们可以被许可使用,包括非罗马字体,和一系列在桌面应用程序的字体。从iOS6开始,字体下载的功能已经能够使用了,但是在iOS7中,有一个更加大的字体列表可供使用.

下载字体存储在系统的某个地方——作为一个应用程序开发者,我们并没有权限去访问字体的存储空间。你需要用到的字体有可能已经被另外的程序请求下载了。然而,如果不是这样的话,而且用户也没有网络连接的情况下,所以我们的字体是不可用的。或者当有一个延迟的请求下载字体——我们等到字体可用的时候进行切换?

首先,我们需要在验证下载使用指定字体之前查看如何得到字体的列表,

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

Listing available fonts

下载字体的API并不是TextKit的一部分,而是在底层的CoreText中。这也就是意味着而不是处理Cocoa对象,我们可以看到许多CoreFoundation对象,然后我们可以进行免费的桥接来让生活变得更加的美好。

我们需要使用CoreText中的这个功能CTFontDescriptorCreateMatchingFontDescriptiors,然后我们用它来匹配一个属性kCTFontDownloadableAttribute,这个属性用来表示哪些字体是否可供下载的。

1
2
3
NSDictionary *descriptorOptions = @{(id)kCTFontDownloadableAttribute: @YES};
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorOptions);
CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL);

第一行,我们创建一个描述属性的NSDictionary——这里我们只指定了我们感兴趣的就是是否能够下载(downloadable).然后使用这个字典来创建一个CTFontDescriptorRef——需要注意的就是在这里我们需要把NSDictionary转成CFDictionaryRef类型——确保通用的桥接模式。最后我们调用一个可以提供给我们符合匹配描述字体的列表。

对于最后一个方法是阻塞的,并且可能需要ige网络的访问,所以我们将会在requestDownloadableFontList方法中进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)requestDownloadableFontList {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSDictionary *descriptorOptions = @{(id)kCTFontDownloadableAttribute: @YES};
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorOptions);
CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL);

dispatch_async(diapatch_get_main_queue(), ^{
[self fontListDownloadComplete:(NSArray *)CFBridgingRelese(fontDescriptors)];
});

// Need to release the font descriptor
CFRelease(descriptor);
});
}

附带的示例应用程序是一个表格视图,我们现在这些结果第一级显示字体名称。点击其中一个展示一个新的tableview导航控制器显示的所有字体。因此,在顶层,我们fontDownloadListComplete实现以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)fontListDownloadComplete:(NSArray *)fontList {
// Need to reorganise array into dictionary
NSMutableDictionary *fontFamilies = [NSMutableDictionary new];
for (UIFontDescriptor *descriptor in fontList) {
NSString *fontFamilyName = [descriptor objectForKey:UIFontDescriptorFamilyAttribute];
NSMutableArray *fontDescriptor = [fontFamilies objectForKey:fontFamilyName];
if(!fontDescriptors) {
fontDescriptors = [NSMutableArray new];
[fontFamilies setObject:fontDescriptors forKey:fontFamilyName];
}
[fontDescriptors addObject:desciptor];
}

_fontList = [fontFamilies copy];
[self.tableView reloadData];
}

这里我们只是重组字体描述符数组变成一个字典,按照字体家族进行分类。我们这里使用UIFontDescriptor来等级代替CTFontDescriptorRef.

一旦我们归类正确的数据,我们可以重新加载表。设置了tableview数据源,在viewDidLoad中:

1
2
3
4
5
6
7
8
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = @"Families";

[self requestDownloadableFontList];
}

我们运行程序之后就可以看到导航控制器是这个样子的.

下一个级别是在导航控制器中指定一个字体的家族,所以,我们需要创建一个NSArray属性来包含字体描述符的集合。我们把这些写在prepareForSegue:方法中:

1
2
3
4
5
6
7
8
9
10
11
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"ShowFamily"]) {
SCFontViewController *vc = [segue destinationViewController];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSString *fontFamilyName = [_fontList allKeys][indexPath.row];
NSArray *fontList = _fontList[fontFamilyName];
vc.fontList = fontList;
vc.title = fontFamilyName;
}
}

运行程序,然后进入到第二级别中就可以看到如下的表示:

Downloading a font - 下载字体

在最后一个阶段,如果这个字体可用的话,程序就显示这个字体到底看起来是什么样子的。否则就需要自己触发进行下载。

下载的操作对应于handleDownloadPressed:方法,这个功能中我们感兴趣的是CTFontDescriptorMatchFontDescriptorsWithProgressHandler.如果下载字体就需要一个CFArrayRef字体的描述符。它需要一块作为参数提供更新的用户。该方法立即返回,在后台队列上执行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (IBAction)handleDownloadPressed:(id)sender {
self.downloadProgressBar.hidden = NO;
CTFontDescriptorMatchFontDescriptorsWithProgressHandler((CFArrayRef)@[_fontDescriptor],
NULL,
^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
double progressValue = [[(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingPercentage] doubleValue];
if (state == kCTFontDescriptorMatchingDidFinish) {
dispatch_async(dispatch_get_main_queue(), ^{
self.downloadProgressBar.hidden = YES;
[self updateView];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
self.downloadProgressBar.progress = progressValue;
});
}
return (bool)YES;
});
}

在执行块中,我们提取当前进度的百分比,然后更新进度条。如果state表明当前的下载已经完成,则我们调用updateView.这个方法我们创建用来加载字体的简单字形内容。注意的一点,我们需要在主线程上面更新UI的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)updateView
{
NSString *fontName = [self.fontDescriptor objectForKey:UIFontDescriptorNameAttribute];
self.title = fontName;
UIFont *font = [UIFont fontWithName:fontName size:26.f];
if(font && [font.fontName isEqualToString:fontName]) {
self.sampleTextLabel.font = font;
self.downloadButton.enabled = NO;
self.detailDescriptionLabel.text = @"Font available";
} else {
self.sampleTextLabel.font = [UIFont systemFontOfSize:font.pointSize];
self.downloadButton.enabled = YES;
self.detailDescriptionLabel.text = @"This font is not yet downloaded";
}
}

运行应用程序,现在你就看可以浏览苹果提供的可用的字体列表,并且可以下载它们:

Conclusion - 总结

下载的字体是一个方便的功能,将允许您定制你的应用程序的外观,而无需许可字体。然而,重要的是要确保你处理用户没有网络连接的情况下,默认字体应该使用什么。

本文翻译自:iOS7 Day-by-Day :: Day 22 :: Downloadable Fonts

文章目录
  1. 1. Listing available fonts
  2. 2. Downloading a font - 下载字体
  3. 3. Conclusion - 总结
,