App界面中少不了的肯定是各種各樣的文字內(nèi)容。那如何讓文字在不同的位置都有相應(yīng)的表現(xiàn)效果呢?像Word操作一樣,自然干是通過設(shè)置各種各樣的字體來表達(dá)不同的效果。UIKit為此提供了UIFont類型來表示字體。當(dāng)然,UIFont只是粗略的對(duì)已有的字體進(jìn)行表現(xiàn),如果希望定制更炫的文本效果,比如數(shù)學(xué)表達(dá)式、電子書,則還需要求助于"CoreText.framework"。
創(chuàng)建系統(tǒng)默認(rèn)字體
iOS系統(tǒng)默認(rèn)支持好幾種字體,并且系統(tǒng)界面也是有特定字體的,比如iOS9就因?yàn)楦铝诵伦煮w--中文字體「蘋方」以及英文字體「San Francisco」,被大家甚是調(diào)侃了一番。
所以最簡單的創(chuàng)建字體的方式就是用系統(tǒng)默認(rèn)的字體,比如:
class func systemFont(ofSize fontSize: CGFloat) -> UIFonts // 獲取指定大小的系統(tǒng)字體
class func systemFont(ofSize fontSize: CGFloat, weight: CGFloat) -> UIFont // 獲取指定大小和粗細(xì)的系統(tǒng)字體
class func boldSystemFont(ofSize fontSize: CGFloat) -> UIFont //獲得指定大小的粗體系統(tǒng)字體
class func italicSystemFont(ofSize fontSize: CGFloat) -> UIFont // 獲取指定大小的斜體系統(tǒng)字體
class func monospacedDigitSystemFont(ofSize fontSize: CGFloat, weight: CGFloat) -> UIFont // 獲取指定大小和粗細(xì)的系統(tǒng)字體,并且其中的數(shù)字字符間距相等
這里介紹的幾種方法,無非是在字體大小、粗細(xì)、斜體上不同的,但都是系統(tǒng)當(dāng)前的默認(rèn)字體。除此之外,iOS10還提供了一套UI推薦標(biāo)準(zhǔn)字號(hào)方法:
class func preferredFont(forTextStyle style: UIFontTextStyle) -> UIFont
其通過枚舉值規(guī)定了各個(gè)場景的字號(hào),比如
- 大標(biāo)題
.title1類似HTML的h1 - 中標(biāo)題
.title2類似HTML的h2 - 小標(biāo)題
.title3類似HTML的h3 - 大抬頭
.headline - 小抬頭
.subheadline - 文本內(nèi)容主體
.body
用這里的枚舉,會(huì)更符合iOS Human Interface Guidelines
獲取系統(tǒng)支持的字體
當(dāng)然,系統(tǒng)除了默認(rèn)字體以外還內(nèi)置支持了很多自帶字體,我們可以通過UIFont的類方法:
class func fontNames(forFamilyName familyName: String) -> [String]
獲得系統(tǒng)內(nèi)置的字體族中所有字體的名稱。然后再通過
init?(name fontName: String, size fontSize: CGFloat)
用字體名來創(chuàng)建指定字體。
那這里字體族如何知道呢?一樣的UIFont提供了接口:
class var familyNames: [String] { get }
比如在iOS10.0.2上,系統(tǒng)自帶的字體:
let family = UIFont.familyNames
for fam in family {
let fonts = UIFont.fontNames(forFamilyName: fam)
print("Font Family: \(fam)")
for f in fonts {
print("\t\t Font:\(f)")
}
}
得到一個(gè)特別長的列表:
Font Family: Copperplate
Font:Copperplate-Light
Font:Copperplate
Font:Copperplate-Bold
Font Family: Heiti SC
Font Family: Kohinoor Telugu
Font:KohinoorTelugu-Regular
Font:KohinoorTelugu-Medium
Font:KohinoorTelugu-Light
Font Family: Thonburi
Font:Thonburi
Font:Thonburi-Bold
Font:Thonburi-Light
Font Family: Heiti TC
Font Family: Courier New
Font:CourierNewPS-BoldMT
Font:CourierNewPS-ItalicMT
Font:CourierNewPSMT
Font:CourierNewPS-BoldItalicMT
...
獲取字體屬性
字體的組成結(jié)構(gòu)和空間占用在Apple的Text Programming Guide for iOS有詳細(xì)描述,當(dāng)然我們這里不去深究CoreText的排班過程,僅看看對(duì)UIKit的影響,來看一個(gè)字體的空間:

UIFont提供了一系列的Getter來獲得這些屬性:
| 屬性 | 類型 | 含義 |
|---|---|---|
| pointSize | CGFloat | 字體大小 |
| ascender | CGFloat | 字體基線距離最高點(diǎn)的位置 |
| descender | CGFloat | 字體的基線距離最低點(diǎn)的位置 |
| leading | CGFloat | 前導(dǎo)距離 |
| capHeight | CGFloat | 主體高度 |
| xHeight | CGFloat | 重心高度 |
| lineHeight | CGFloat | 行高 |
這些屬性用中文描述出來,不是特別容易理解,可以將屬性名對(duì)照上面的圖進(jìn)行理解。
除了這幾個(gè)屬性,還可以通過下面的方法獲得字體的名稱:
var familyName: String { get }
var fontName: String { get }
前者獲取字體的家族名,后者獲得字體名。
創(chuàng)建自定義字體
說完系統(tǒng)字體,現(xiàn)在我們來說這篇文章的重點(diǎn),如何加載自定義字體。根據(jù)資源的提供者,我們可以分成三類來說:隨包Bundle、動(dòng)態(tài)數(shù)據(jù)文件以及Apple提供的系統(tǒng)擴(kuò)展
隨包Bundle字體
最簡單的方式就是如同做PC端應(yīng)用或者做游戲一樣,把一個(gè)字體包當(dāng)做一個(gè)Bundle資源打入ipa包中。所以我將他稱為"隨包Bundle字體"。比如要在App中加入Monaco這個(gè)程序員專屬字體。
首先將自己的字體文件像一個(gè)Bundle文件一樣加入到Xcode工程中,比如這里我加入一個(gè)MONACO.ttf的字體文件,然后在plist文件中添加“Fonts provided by application”表示數(shù)組的字段,里面每個(gè)單元就是一個(gè)要加入的字體的文件名。

之后,在我們的系統(tǒng)庫中就有了這個(gè)字體了,比如上面的枚舉系統(tǒng)的代碼就會(huì)看到:
Font Family: MONACO
Font:MONACO
最后我們再如上面介紹的調(diào)用:
let monacoFont = UIFont(name: "Monaco", size: UIFont.systemFontSize)
來創(chuàng)建一個(gè)Monaco字體。
這個(gè)方法雖然簡單,但是也帶來了一些缺點(diǎn)。比如發(fā)布包ipa會(huì)以為字體而變大。
動(dòng)態(tài)數(shù)據(jù)文件字體
上面說了,雖然“隨包Bundle字體”能很方便的解決增加一個(gè)系統(tǒng)不支持的字體的問題,但是隨之帶來的卻是ipa包的增大。那要如何解決呢?
做程序猿的自然就會(huì)想到,能不能資源不隨包發(fā)布,而在程序運(yùn)行的期間從網(wǎng)上下載到document目錄在加載呢?
答案當(dāng)然是肯定的,所以我又將其稱為“動(dòng)態(tài)數(shù)據(jù)文件字體”。但是要用到一個(gè)不屬于UIKit的技術(shù):CoreText里面的CTFontManagerRegisterGraphicsFont函數(shù),所以我們需要先 :
import CoreText
然后假設(shè)字體文件內(nèi)容下載到了NSData中
let fontData = NSData(contentsOfFile: fontURL!) //這里用本地文件模擬網(wǎng)絡(luò)下載到NSData中
來看完整代碼:
let monacoFont = UIFont(name: "Monaco", size: UIFont.systemFontSize)
print("monacoFont is \(monacoFont)")
let fontURL = Bundle.main.path(forResource: "MONACO", ofType: "ttf")
let fontData = NSData(contentsOfFile: fontURL!)
let providerRef = CGDataProvider(data: fontData!)
let fontRef = CGFont(providerRef!)
var error: Unmanaged<CFError>?
if CTFontManagerRegisterGraphicsFont(fontRef, &error) {
let mFont = UIFont(name: "Monaco", size: UIFont.systemFontSize)
print("mFont is \(mFont)")
}
可以得到輸出:
monacoFont is nil
mFont is Optional(<UICTFont: 0x100c11cc0> font-family: "MONACO"; font-weight: normal; font-style: normal; font-size: 14.00pt)
第一次沒有“MONACO”等注冊后就有了。
這里要注意下,Swift3以后CoreText.framework的改變。這里用了CGDataProvider以及CGFont。
Apple提供的系統(tǒng)擴(kuò)展字體
通過動(dòng)態(tài)下載字體文件好像基本上完美解決所有問題了,但是還有個(gè)小問題,很多字體尤其是中文字體都是有版權(quán)的,如果App發(fā)現(xiàn)量巨大,這塊被發(fā)現(xiàn)了就不好了。但是iOS系統(tǒng)就只提供了上面羅列出來的哪些字體么?好像都沒幾個(gè)中文的。
既然中國用戶為Apple共享了那么多美金,Apple自然也不會(huì)忘記中文的支持,我們會(huì)發(fā)現(xiàn)Mac上提供了“字體簿”(FontBook)是提供了N多中文字體,比如中國特色的“隸書”:

這里會(huì)看到有個(gè)“PostScript name”,Apple提供了一種通過這個(gè)名稱下載字體到自己的手機(jī)系統(tǒng)位置的方式,也就是為出廠的手機(jī)系統(tǒng)的位置(/private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/)新增一個(gè)字體,并且所有的應(yīng)用都可以用了(上面兩種方法都是針對(duì)當(dāng)前App)的。
這里我們查詢到圖中的隸書的PostScript為“STBaoliSC-Regular”,然后我們一樣用CoreText的服務(wù):
let fontPSName = "STBaoliSC-Regular"
var attr : [String:String] = [kCTFontNameAttribute as String : fontPSName]
let desc = CTFontDescriptorCreateWithAttributes(attr as CFDictionary)
var descs : [CTFontDescriptor] = [desc,]
CTFontDescriptorMatchFontDescriptorsWithProgressHandler(descs as CFArray, nil) { (stat, prama) -> Bool in
//
if .didFinish == stat {
let nishuFont = UIFont(name: fontPSName, size: UIFont.systemFontSize)
print("nishuFont is \(nishuFont)")
}
return true
}
如果你是使用的Xcode8的話,會(huì)發(fā)現(xiàn)這里有一堆的下載日志,當(dāng)然最后回打?。?/p>
"SandboxExtension" => <string: 0x174250170> { length = 249, contents = "524979fa2be9f37ea83d93d0f3f00b1ef8977255;00000000;00000000;0000000000000015;com.apple.assets.read;00000001;01000004;00000000014f47f8;/private/var/MobileAsset/Assets/com_apple_MobileAsset_Font3/3abf40766b4cf50b77ebd28e6affbd1849ea61c6.asset/AssetData" }
}
nishuFont is Optional(<UICTFont: 0x100c16b90> font-family: "STBaoliSC-Regular"; font-weight: normal; font-style: normal; font-size: 14.00pt)
這里可以看到,字體下載到了"/private/var/MobileAsset/Assets/com_apple_MobileAsset_Font3/3abf40766b4cf50b77ebd28e6affbd1849ea61c6.asset/AssetData",然后成功創(chuàng)建了字體。這里注意哈,字體名也是“PostScript name”而不是截圖中的字體名。
這里使用了CoreText的CTFontDescriptorMatchFontDescriptorsWithProgressHandler,一個(gè)下載字體的系統(tǒng)服務(wù)。既然是下載動(dòng)作,肯定是異步的了,所以這里用了一個(gè)閉包CTFontDescriptorProgressHandler:
typealias CTFontDescriptorProgressHandler = (CTFontDescriptorMatchingState, CFDictionary) -> Bool
來處理回調(diào)結(jié)果。CTFontDescriptorMatchingState定了了各個(gè)下載結(jié)果,這里,我們只關(guān)注了成功的結(jié)果。而CFDictionary里面則包含了進(jìn)度(kCTFontDescriptorMatchingPercentage)等信息
因?yàn)橄螺d動(dòng)作不是在UI線程里面,所以這里假設(shè)要更新UI上的下載進(jìn)度,需要通過dispatch_async來實(shí)現(xiàn)。
| stat | 下載狀態(tài) |
|---|---|
| didBegin | 開始下載時(shí) |
| didFinish | 下載完成時(shí) |
| willBeginQuerying | 第一次向服務(wù)器發(fā)起查詢時(shí) |
| stalled | 等待服務(wù)器相應(yīng) |
| willBeginDownloading | 每當(dāng)有新字體下載時(shí) |
| downloading | 正在下載 |
| didFinishDownloading | 一次下載完成 |
| didMatch | 當(dāng)找到一個(gè)字體 |
| case didFailWithError | 出錯(cuò)時(shí) |
| 下載信息key | 類型 | 下載信息值 | 對(duì)應(yīng)狀態(tài) |
|---|---|---|---|
| kCTFontDescriptorMatchingSourceDescriptor | UIFontDescriptor | 當(dāng)前字體下載完成時(shí) | .didFinish |
| kCTFontDescriptorMatchingDescriptors | Array | 要下載的字體描述, | willBeginQuerying |
| kCTFontDescriptorMatchingResult | Array | 匹配的字體描述UIFontDescriptor | .didMatch |
| kCTFontDescriptorMatchingPercentage | CFNumber | 進(jìn)度0-100之間 | .downloading |
| kCTFontDescriptorMatchingCurrentAssetSize | CFNubmer | 當(dāng)前下載大大小 | .downloading |
| kCTFontDescriptorMatchingTotalDownloadedSize | CFNumber | 總下載大小 | .downloading |
| kCTFontDescriptorMatchingError | CFError | 出錯(cuò)信息 | .didFailWithError |
總結(jié)
UIFont提供了對(duì)系統(tǒng)自帶字體的訪問,從而為UILabel、UITextView等提供豐富的字體支持。除了可以設(shè)置系統(tǒng)自己的字體之外,UIFont還可以創(chuàng)建自己提供的資源字體,字體資源既可以隨包打入ipa也可以放在網(wǎng)絡(luò)上通過下載到資源包中,同時(shí)Apple還提供了大量的擴(kuò)展字體。通過這些字體,我們可以對(duì)UI界面做非常大的自定義。但是UIFont并不能解決所有的文字排版問題,類似于數(shù)學(xué)表達(dá)式,電子書排版這種負(fù)責(zé)的文字排版,我們還需要借助CoreText.framework。