庖丁UIKit之UIFont

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è)字體的空間:

font_structure

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è)要加入的字體的文件名。

font_plist

之后,在我們的系統(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多中文字體,比如中國特色的“隸書”:

font_book

這里會(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。

參考:

  1. Text Programming Guide for iOS

  2. UIFont Class Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容