版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.08.27 |
前言
Core Text
框架主要用來做文字處理,是的iOS3.2+
和OSX10.5+
中的文本引擎吻商,讓您精細的控制文本布局和格式。它位于在UIKit
中和CoreGraphics/Quartz
之間的最佳點糟红。接下來這幾篇我們就主要解析該框架艾帐。感興趣的可以前面幾篇。
1. Core Text框架詳細解析(一) —— 基本概覽
2. Core Text框架詳細解析(二) —— 關(guān)于Core Text
3. Core Text框架詳細解析(三) —— Core Text總體概覽
4. Core Text框架詳細解析(四) —— Core Text文本布局操作
5. Core Text框架詳細解析(五) —— Core Text字體操作
開始
首先看一下本文的寫作環(huán)境:
Swift 4, iOS 11, Xcode 9
Core Text是一個低級底層文本引擎盆偿,與Core Graphics / Quartz
框架一起使用時柒爸,可以對布局和格式進行細粒度控制。
在iOS 7中事扭,Apple發(fā)布了一個名為Text Kit
的高級庫捎稚,它存儲,布局和顯示具有各種排版特征的文本。盡管Text Kit功能強大且通常在布局文本時足夠今野,但Core Text可以提供更多控制葡公。例如,如果您需要直接使用Quartz条霜,請使用Core Text催什。如果您需要構(gòu)建自己的布局引擎,Core Text
將幫助您生成“glyphs and position them relative to each other with all the features of fine typesetting.”宰睡。
本教程將指導(dǎo)您使用Core Text
創(chuàng)建一個非常簡單的雜志應(yīng)用程序蒲凶!
下面就新建立一個工程并開始我們的工作。
打開Xcode拆内,使用Single View Application Template
創(chuàng)建一個新的Swift universal project
豹爹,并將其命名為CoreTextMagazine
。
接下來矛纹,將Core Text
框架添加到項目中:
- 1)單擊項目導(dǎo)航器中的項目文件(左側(cè)的條帶)
- 2)在
“General”
下臂聋,向下滾動到底部的“Linked Frameworks and Libraries”
- 3)點擊
“+”
并搜索“CoreText”
- 4)選擇
“CoreText.framework”
并單擊“Add”
按鈕。 而已或南!
現(xiàn)在項目已經(jīng)設(shè)置好孩等,是時候開始編碼了。
Adding a Core Text View - 添加一個Core Text視圖
對于初學(xué)者采够,您將創(chuàng)建一個自定義UIView肄方,它將在其draw(_ :)
方法中使用Core Text
。
創(chuàng)建一個名為CTView
繼承自UIView的新Cocoa Touch
類文件蹬癌。
打開CTView.swift
权她,并在導(dǎo)入UIKit下添加以下內(nèi)容:
import CoreText
接下來,將此新自定義視圖設(shè)置為應(yīng)用程序中的主視圖逝薪。 打開Main.storyboard
隅要,打開右側(cè)的Utilities
菜單,然后在其頂部工具欄中選擇Identity Inspector
圖標(biāo)董济。 在Interface Builder
的左側(cè)菜單中步清,選擇View。 Utilities菜單的Class字段現(xiàn)在應(yīng)該是UIView虏肾。 要子類化主視圖控制器的視圖廓啊,請在“Class”字段中鍵入CTView
,然后按Enter鍵封豪。
接下來谴轮,打開CTView.swift
并用以下內(nèi)容替換注釋掉的draw(_:)
:
//1
override func draw(_ rect: CGRect) {
// 2
guard let context = UIGraphicsGetCurrentContext() else { return }
// 3
let path = CGMutablePath()
path.addRect(bounds)
// 4
let attrString = NSAttributedString(string: "Hello World")
// 5
let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
// 6
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
// 7
CTFrameDraw(frame, context)
}
下面,讓我們一步一步地回顧一下:
- 1)創(chuàng)建視圖后吹埠,
draw(_ :)
將自動運行以渲染視圖的背景層第步。 - 2)展開您將用于繪圖的當(dāng)前圖形上下文疮装。
- 3)創(chuàng)建一個限制繪圖區(qū)域的路徑,在這種情況下是整個視圖的邊界
- 4)在Core Text中雌续,使用
NSAttributedString
(而不是String或NSString)來保存文本及其屬性。 將“Hello World”
初始化為屬性字符串胯杭。 - 5)
CTFramesetterCreateWithAttributedString
使用提供的屬性字符串創(chuàng)建CTFramesetter
驯杜。CTFramesetter
將管理您的字體引用和繪圖框架。 - 6)通過讓
CTFramesetterCreateFrame
呈現(xiàn)path
中的整個字符串來創(chuàng)建CTFrame
做个。 - 7)
CTFrameDraw
在給定的上下文中繪制CTFrame
鸽心。
這就是你需要繪制一些簡單的文字! Build居暖,運行并查看結(jié)果顽频。
呃哦......那似乎不對,是嗎太闺? 與許多低級API一樣糯景,Core Text
使用Y-flipped坐標(biāo)系。 更糟糕的是省骂,內(nèi)容也垂直翻轉(zhuǎn)蟀淮!
在guard let context
語句的正下方添加以下代碼以修復(fù)內(nèi)容方向:
// Flip the coordinate system
context.textMatrix = .identity
context.translateBy(x: 0, y: bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
此代碼通過對視圖的上下文應(yīng)用轉(zhuǎn)換來翻轉(zhuǎn)內(nèi)容。
Build并運行應(yīng)用程序钞澳。 不要擔(dān)心狀態(tài)欄重疊怠惶,您將學(xué)習(xí)如何使用邊距修復(fù)此問題。
祝賀您的第一個Core Text應(yīng)用程序轧粟!
The Core Text Object Model - Core Text 對象模型
如果你對CTFramesetter
和CTFrame
感到有點困惑 - 這沒關(guān)系策治,因為是時候說明一下了。
以下是Core Text
對象模型的樣子:
創(chuàng)建CTFramesetter
引用并為其提供NSAttributedString
時兰吟,會自動為您創(chuàng)建CTTypesetter
實例以管理您的字體通惫。 接下來,使用CTFramesetter
創(chuàng)建一個或多個frames
混蔼,您將在其中呈現(xiàn)文本讽膏。
創(chuàng)建frame
時,為其提供要在其矩形內(nèi)渲染的文本子范圍拄丰。 Core Text會自動為每行文本創(chuàng)建一個CTLine
府树,并為具有相同格式的每段文本創(chuàng)建一個CTRun
。 例如料按,如果您將多個單詞連續(xù)顯示為紅色奄侠,則會創(chuàng)建一個CTRun,然后是另一個CTRun用于以下純文本载矿,然后是另一個用于粗體句子的CTRun等垄潮。Core Text根據(jù)提供的NSAttributedString
屬性為您創(chuàng)建CTRuns烹卒。 此外,這些CTRun對象中的每一個都可以采用不同的屬性弯洗,因此您可以很好地控制字距旅急,連字,寬度牡整,高度等藐吮。
Onto the Magazine App!
下載并取消歸檔 the zombie magazine materials。
將文件夾拖到Xcode項目中逃贝。 出現(xiàn)提示時谣辞,確保選中Copy items if needed
和Create groups
。
要創(chuàng)建應(yīng)用程序沐扳,您需要將各種屬性應(yīng)用于文本泥从。 您將創(chuàng)建一個簡單的文本標(biāo)記解析器,它將使用標(biāo)簽來設(shè)置雜志的格式沪摄。
創(chuàng)建一個名為MarkupParser
的新Cocoa Touch
類文件躯嫉,繼承自NSObject。
首先杨拐,快速瀏覽一下zombies.txt
和敬。 看看它在整個文本中如何包含括號格式標(biāo)簽? “img src”
標(biāo)簽引用雜志圖像和“font color / face”
標(biāo)簽確定文本顏色和字體戏阅。
打開MarkupParser.swift
并用以下內(nèi)容替換其內(nèi)容:
import UIKit
import CoreText
class MarkupParser: NSObject {
// MARK: - Properties
var color: UIColor = .black
var fontName: String = "Arial"
var attrString: NSMutableAttributedString!
var images: [[String: Any]] = []
// MARK: - Initializers
override init() {
super.init()
}
// MARK: - Internal
func parseMarkup(_ markup: String) {
}
}
在這里昼弟,您添加了保存字體和文本顏色的屬性;設(shè)置默認(rèn)值奕筐;創(chuàng)建了一個變量來保存parseMarkup(_ :)
生成的屬性字符串; 并創(chuàng)建了一個數(shù)組舱痘,最終將保存字典信息,定義文本中找到的圖像的大小离赫,位置和文件名芭逝。
編寫解析器通常很難,但是本教程的解析器將非常簡單并且僅支持打開標(biāo)記 - 這意味著標(biāo)記將設(shè)置其后面的文本樣式渊胸,直到找到新標(biāo)記旬盯。 文本標(biāo)記將如下所示:
These are <font color="red">red<font color="black"> and
<font color="blue">blue <font color="black">words.
并產(chǎn)生這樣的輸出:
讓我們得到解析!
將以下內(nèi)容添加到parseMarkup(_ :)
:
//1
attrString = NSMutableAttributedString(string: "")
//2
do {
let regex = try NSRegularExpression(pattern: "(.*?)(<[^>]+>|\\Z)",
options: [.caseInsensitive,
.dotMatchesLineSeparators])
//3
let chunks = regex.matches(in: markup,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSRange(location: 0,
length: markup.characters.count))
} catch _ {
}
- 1)
attrString
開始為空翎猛,但最終將包含已解析的標(biāo)記胖翰。 - 2)這個正則表達式匹配文本塊與標(biāo)簽緊隨其后。 它表示切厘,“通過字符串查找萨咳,直到找到一個開口括號,然后查看字符串疫稿,直到你敲到一個右括號(或文檔的末尾)培他【榱剑”
- 3)搜索標(biāo)記的整個范圍以進行
regex
匹配,然后生成結(jié)果NSTextCheckingResult
的數(shù)組舀凛。
現(xiàn)在俊扳,您已將所有文本和格式標(biāo)記解析為chunks
,您將循環(huán)遍歷chunks
以構(gòu)建屬性字符串猛遍。
但在此之前馋记,您是否注意到matches(in:options:range:)
如何接受NSRange
作為參數(shù)? 將NSRegularExpression
函數(shù)應(yīng)用于標(biāo)記String
時螃壤,會有很多NSRange
到Range
轉(zhuǎn)換抗果。
仍然在MarkupParser.swift
中筋帖,將以下extension
添加到文件末尾:
// MARK: - String
extension String {
func range(from range: NSRange) -> Range<String.Index>? {
guard let from16 = utf16.index(utf16.startIndex,
offsetBy: range.location,
limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: range.length, limitedBy: utf16.endIndex),
let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self) else {
return nil
}
return from ..< to
}
}
此函數(shù)將String
的起始和結(jié)束索引(由NSRange表示)轉(zhuǎn)換為String.UTF16View.Index
格式奸晴,即字符串的UTF-16
代碼單元集合中的位置;然后將每個String.UTF16View.Index
轉(zhuǎn)換為String.Index
格式日麸;組合時寄啼,產(chǎn)生Swift的范圍格式:Range
。 只要索引有效代箭,該方法將返回原始NSRange的Range表示墩划。
下面是時候回到處理文本和標(biāo)記塊了。
在parseMarkup(_ :)
里面添加以下內(nèi)容let chunks
(在do
塊中):
let defaultFont: UIFont = .systemFont(ofSize: UIScreen.main.bounds.size.height / 40)
//1
for chunk in chunks {
//2
guard let markupRange = markup.range(from: chunk.range) else { continue }
//3
let parts = markup[markupRange].components(separatedBy: "<")
//4
let font = UIFont(name: fontName, size: UIScreen.main.bounds.size.height / 40) ?? defaultFont
//5
let attrs = [NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: font] as [NSAttributedStringKey : Any]
let text = NSMutableAttributedString(string: parts[0], attributes: attrs)
attrString.append(text)
}
- 1)遍歷
chunks
嗡综。 - 2)獲取當(dāng)前
NSTextCheckingResult
的范圍乙帮,打開Range <String.Index>
并繼續(xù)塊,只要它存在极景。 - 3)將
chunk
拆分為由“<”
分隔的部分察净。 第一部分包含雜志文本,第二部分包含標(biāo)簽(如果存在)盼樟。 - 4)使用
fontName
創(chuàng)建字體氢卡,默認(rèn)情況下為“Arial”
,相對于設(shè)備屏幕的大小晨缴。 如果fontName
未生成有效的UIFont
译秦,請將font
設(shè)置為默認(rèn)字體。 - 5)創(chuàng)建字體格式的字典击碗,將其應(yīng)用于parts [0]以創(chuàng)建屬性字符串筑悴,然后將該字符串附加到結(jié)果字符串。
要處理“font”
標(biāo)記稍途,請在attrString.append(text)
之后插入以下內(nèi)容:
// 1
if parts.count <= 1 {
continue
}
let tag = parts[1]
//2
if tag.hasPrefix("font") {
let colorRegex = try NSRegularExpression(pattern: "(?<=color=\")\\w+",
options: NSRegularExpression.Options(rawValue: 0))
colorRegex.enumerateMatches(in: tag,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSMakeRange(0, tag.characters.count)) { (match, _, _) in
//3
if let match = match,
let range = tag.range(from: match.range) {
let colorSel = NSSelectorFromString(tag[range]+"Color")
color = UIColor.perform(colorSel).takeRetainedValue() as? UIColor ?? .black
}
}
//5
let faceRegex = try NSRegularExpression(pattern: "(?<=face=\")[^\"]+",
options: NSRegularExpression.Options(rawValue: 0))
faceRegex.enumerateMatches(in: tag,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSMakeRange(0, tag.characters.count)) { (match, _, _) in
if let match = match,
let range = tag.range(from: match.range) {
fontName = String(tag[range])
}
}
} //end of font parsing
- 1)如果少于兩個部分雷猪,則跳過循環(huán)體的其余部分。否則晰房,將第二部分存儲為
tag
求摇。 - 2)如果
tag
以“font”
開頭射沟,則創(chuàng)建一個正則表達式以查找字體的“ color”
值,然后使用該正則表達式來枚舉tag
的匹配“ color”
值与境。在這種情況下验夯,應(yīng)該只有一個匹配的顏色值。 - 3)如果
enumerateMatches(in:options:range:using :)
返回tag
中有效范圍的有效match
摔刁,請找到指示的值(例如<font color =“red”>
返回“red”
)并將“Color”
附加到表單中一個UIColor選擇器挥转。執(zhí)行該選擇器,然后將類的顏色設(shè)置為返回的顏色(如果存在)共屈,否則設(shè)置為黑色绑谣。 - 4)同樣,創(chuàng)建一個正則表達式來處理字體的
“face”
值拗引。如果找到匹配項借宵,請將fontName
設(shè)置為該字符串。
做得好矾削!現(xiàn)在壤玫,parseMarkup(_ :)
可以獲取標(biāo)記并為Core Text
生成NSAttributedString
。
是時候給你的應(yīng)用程序提供zombies.txt
了哼凯。
實際上欲间,UIView
的工作是顯示給定的內(nèi)容,而不是加載內(nèi)容断部。打開CTView.swift
并在上面添加以下draw(_:)
:
// MARK: - Properties
var attrString: NSAttributedString!
// MARK: - Internal
func importAttrString(_ attrString: NSAttributedString) {
self.attrString = attrString
}
接下來猎贴,從draw(_ :)
中刪除let attrString = NSAttributedString(string:“Hello World”)
。
在這里蝴光,您創(chuàng)建了一個實例變量來保存attributed string
她渴,以及一種從應(yīng)用程序的其他位置設(shè)置它的方法。
接下來虱疏,打開ViewController.swift
并將以下內(nèi)容添加到viewDidLoad()
:
// 1
guard let file = Bundle.main.path(forResource: "zombies", ofType: "txt") else { return }
do {
let text = try String(contentsOfFile: file, encoding: .utf8)
// 2
let parser = MarkupParser()
parser.parseMarkup(text)
(view as? CTView)?.importAttrString(parser.attrString)
} catch _ {
}
讓我們一步一步的細分下惹骂。
- 1)將
zombie.txt
文件中的文本加載到String
中。 - 2)創(chuàng)建一個新的解析器做瞪,輸入文本对粪,然后將返回的屬性字符串傳遞給
ViewController
的CTView
。
Build并運行應(yīng)用程序装蓬!
棒極了著拭? 感謝大約50行解析,您只需使用文本文件來保存雜志應(yīng)用程序的內(nèi)容牍帚。
后記
本篇主要講述了基于Core Text的Magazine App的制作儡遮,感興趣的給個贊或者關(guān)注~~~