TextKit框架詳細(xì)解析 (三) —— 一個(gè)簡(jiǎn)單布局示例(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.08.30

前言

TextKit框架是對(duì)Core Text的封裝,用簡(jiǎn)潔的調(diào)用方式實(shí)現(xiàn)了大部分Core Text的功能夹姥。 TextKit是一個(gè)偏上層的開發(fā)框架杉武,在iOS7以上可用,使用它可以方便靈活處理復(fù)雜的文本布局佃声,滿足開發(fā)中對(duì)文本布局的各種復(fù)雜需求艺智。TextKit實(shí)際上是基于CoreText的一個(gè)上層框架,其是面向?qū)ο蟮幕鳌=酉聛韼灼覀兙鸵黄鹂匆幌逻@個(gè)框架十拣。感興趣的看下面幾篇文章。
1. TextKit框架詳細(xì)解析 (一) —— 基本概覽和應(yīng)用場(chǎng)景(一)
2. TextKit框架詳細(xì)解析 (二) —— 基本概覽和應(yīng)用場(chǎng)景(二)

開始

注意志鹃!注意夭问!注意!:本文的寫作背景比較老曹铃,是iOS7缰趋,現(xiàn)在已經(jīng)沒人使用了,但是之所以這樣還是放出來陕见,就想說一下TextKit框架的體系和應(yīng)用范圍秘血,個(gè)別人可以忽略本文。

隨著Apple增加更多功能和特性评甜,iOS呈現(xiàn)文本的方式在過去幾年中變得越來越強(qiáng)大灰粮。 iOS 7的發(fā)布帶來了一些最重要的文本呈現(xiàn)更改。 現(xiàn)在忍坷,iOS 8以后以這種能力為基礎(chǔ)粘舟,使其更易于使用。

在iOS 6之前的過去佩研,Web視圖通常是使用混合樣式呈現(xiàn)文本的最簡(jiǎn)單方法柑肴,例如粗體,斜體或甚至帶有顏色旬薯。

2012年晰骑,iOS 6為許多UIKit控件添加了屬性字符串支持。 這使得在不使用渲染HTML的情況下實(shí)現(xiàn)這種類型的布局變得更加容易绊序。

在iOS 6中些侍,UIKit基于WebKitCore Graphics的字符串繪制函數(shù)控制其文本功能,如下面的分層圖所示:

注意:在這個(gè)圖中政模,有什么事情讓你覺得奇怪嗎? 沒錯(cuò) - UITextView使用WebKit蚂会。 iOS 6將文本視圖上的屬性字符串呈現(xiàn)為HTML淋样,這一事實(shí)對(duì)于尚未深入深入框架的開發(fā)人員來說并不明顯。

iOS 6中的屬性字符串確實(shí)對(duì)許多用例有用胁住。 但是趁猴,對(duì)于高級(jí)布局和多行渲染文本刊咳,Core Text仍然是唯一真正的選擇 - 相對(duì)靠近底層且繁瑣的框架。

但是儡司,從iOS 7開始娱挨,有一種更簡(jiǎn)單的方法。 憑借當(dāng)前簡(jiǎn)約的設(shè)計(jì)重點(diǎn)捕犬,避開多余裝飾并更多地關(guān)注排版 - 例如剝離所有邊框和陰影的UIButton跷坝,只留下文本 - iOS 7添加了一個(gè)用于處理文本和文本屬性的全新框架并不奇怪: Text Kit

現(xiàn)在架構(gòu)更加整潔碉碉,所有基于文本的UIKit控件(除了UIWebView)現(xiàn)在都使用Text Kit柴钻,如下圖所示:

Text Kit構(gòu)建于Core Text之上,繼承了Core Text框架的全部功能垢粮,并且令所有開發(fā)人員高興贴届,將其包含在改進(jìn)的面向?qū)ο蟮腁PI中。

在這個(gè)Text Kit教程中蜡吧,您將探索Text Kit的各種功能毫蚓,因?yàn)槟鸀閕Phone創(chuàng)建了一個(gè)簡(jiǎn)單但功能豐富的筆記記錄應(yīng)用程序,該應(yīng)用程序具動(dòng)態(tài)文本大小調(diào)整和動(dòng)態(tài)文本樣式昔善。

下面我們打開建立的工程并運(yùn)行元潘,界面應(yīng)該如下所示:

該應(yīng)用程序創(chuàng)建一個(gè)初始的Note實(shí)例數(shù)組,并將它們呈現(xiàn)在table view控制器中耀鸦。sb和segue在table view中檢測(cè)單元格選擇柬批,并處理到視圖控制器的轉(zhuǎn)換,用戶可以在其中編輯選定的note袖订。

瀏覽源代碼并稍微使用應(yīng)用程序氮帐,以了解應(yīng)用程序的結(jié)構(gòu)及其運(yùn)行方式。 完成后洛姑,請(qǐng)轉(zhuǎn)到下一部分上沐,其中討論了應(yīng)用中動(dòng)態(tài)類型的使用。


Dynamic Type - 動(dòng)態(tài)類型

Dynamic Type是iOS 7中改變游戲規(guī)則最多的功能之一楞艾,它將選擇權(quán)放在您的應(yīng)用程序上参咙,以符合用戶選擇的字體大小和權(quán)重。

在iOS 7中硫眯,打開Settings應(yīng)用并導(dǎo)航到General/AccessibilityGeneral/Text Size以查看影響應(yīng)用顯示文本方式的設(shè)置:

在iOS 8中蕴侧,打開Settings應(yīng)用并導(dǎo)航到General/Accessibility/Larger Text以訪問Dynamic Type文本大小。

iOS 7提供了通過增加字體權(quán)重來增強(qiáng)文本易讀性的功能两入,以及為支持動(dòng)態(tài)文本的應(yīng)用程序設(shè)置首選字體大小的選項(xiàng)净宵。

注意:當(dāng)Apple在WWDC 2013上發(fā)布Text Kit時(shí),他們強(qiáng)烈建議開發(fā)人員采用動(dòng)態(tài)類型。在WWDC 2014上择葡,蘋果公司走得更遠(yuǎn)了紧武。他們強(qiáng)調(diào)所有內(nèi)置應(yīng)用都支持動(dòng)態(tài)類型。此外敏储,他們使得動(dòng)態(tài)類型在iOS 8中更容易使用阻星。WWDC 2014會(huì)議226表 What’s New in Tables and Collection Views,涵蓋了對(duì)table views和collections的iOS 8動(dòng)態(tài)類型支持已添。建議你觀看它妥箕!用戶希望為iOS 7及更高版本編寫的應(yīng)用程序能夠遵守這些設(shè)置。

為了使用動(dòng)態(tài)類型酝碳,您需要使用styles指定字體矾踱,而不是明確說明字體名稱和大小。 iOS 7為UIFont添加了一個(gè)新方法preferredFontForTextStyle疏哗,它使用用戶的字體首選項(xiàng)為給定樣式創(chuàng)建字體呛讲。

下圖給出了六種不同字體樣式的示例:

左側(cè)的文本使用最小的用戶可選文本大小,中心的文本使用最大的文本返奉,右側(cè)的文本顯示啟用可訪問性粗體文本功能的效果贝搁。


Basic Support - 基本支持

實(shí)現(xiàn)對(duì)動(dòng)態(tài)文本的基本支持相對(duì)簡(jiǎn)單。 而不是在應(yīng)用程序中使用顯式字體芽偏,而是請(qǐng)求特定樣式的字體雷逆。 在運(yùn)行時(shí),應(yīng)用程序根據(jù)給定的樣式和用戶的文本首選項(xiàng)選擇合適的字體污尉。

使用iOS 8膀哲,Apple實(shí)現(xiàn)動(dòng)態(tài)類型比在iOS 7中更容易。特別是被碗,table views中的默認(rèn)標(biāo)簽自動(dòng)支持動(dòng)態(tài)類型某宪! 盡管如此,您可能希望支持iOS 7锐朴,和/或您可能希望在table views中使用自定義標(biāo)簽兴喂。 首先,您將學(xué)習(xí)如何處理iOS 7的動(dòng)態(tài)類型焚志。然后衣迷,您將了解Apple如何在iOS 8中讓您的生活更輕松。


Why iOS 7 is Great, but iOS 8 is Even Greater

初始化項(xiàng)目的deployment設(shè)置為iOS 8酱酬,在繼續(xù)之前壶谒,構(gòu)建并運(yùn)行應(yīng)用程序并嘗試將默認(rèn)文本大小更改為各種值。您將發(fā)現(xiàn)table view列表中的文本大小和單元格高度都會(huì)相應(yīng)更改膳沽。而且你不需要做任何事情佃迄!但是請(qǐng)注意泼差,notes本身并不反映文本大小設(shè)置的更改。

盡管不是很精彩呵俏,但iOS 7中的內(nèi)容仍然相當(dāng)不錯(cuò)。對(duì)于本文的大部分內(nèi)容滔灶,如果您使用的是iOS 7或iOS 8(只是確保您使用的是Xcode 6F账椤),現(xiàn)在录平,將應(yīng)用程序的deployment level設(shè)置為iOS 7麻车,然后在iOS模擬器中進(jìn)行操作。以下大部分內(nèi)容在iOS 8中也有用斗这,所以即使你不打算支持早于iOS 8的iOS版本动猬,它也是值得的。

注意:要在Xcode 6中將部署級(jí)別設(shè)置為iOS 7表箭,請(qǐng)選擇View/Navigators/Show Project Navigator赁咙。在右側(cè)面板中,選擇項(xiàng)目免钻,單擊info并在iOS Deployment Target彈出菜單中選擇iOS 7彼水。此外,在右側(cè)面板中選擇目標(biāo)极舔,并將部署目標(biāo)設(shè)置為iOS 7凤覆。確保模擬器充當(dāng)iOS 7設(shè)備也很重要。所以在iOS模擬器中選擇Hardware/Device/iOS 7/iPhone 5s拆魏。

現(xiàn)在您已經(jīng)準(zhǔn)備好作為iOS 7應(yīng)用程序運(yùn)行盯桦,繼續(xù)構(gòu)建并運(yùn)行。像以前一樣玩文字大小設(shè)置渤刃,你會(huì)發(fā)現(xiàn)遺憾的是拥峦,應(yīng)用程序忽略了你的設(shè)置。現(xiàn)在溪掀,您將做一些事情來使其在iOS 7中運(yùn)行事镣。

打開NoteEditorViewController.swift并將以下內(nèi)容添加到viewDidLoad的末尾:

textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)

請(qǐng)注意,您沒有指定Helvetica Neue等確切字體揪胃。 相反璃哟,您要求使用UIFontTextStyleBody文本樣式常量為正文添加適當(dāng)?shù)淖煮w。

接下來喊递,打開NotesListViewController.swift并在返回調(diào)用之前將以下內(nèi)容添加到tableView(_:cellForRowAtIndexPath :)方法:

cell.textLabel?.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)

同樣随闪,您指定了文本樣式,iOS將返回適當(dāng)?shù)淖煮w骚勘。

使用字體名稱的語(yǔ)義方法(例如UIFontTextStyleSubHeadline)有助于避免代碼中的硬編碼字體名稱和樣式 - 并確保您的應(yīng)用程序能夠按預(yù)期正確響應(yīng)用戶定義的排版設(shè)置铐伴。

再次構(gòu)建并運(yùn)行應(yīng)用程序撮奏,您會(huì)注意到table view和note屏幕現(xiàn)在支持當(dāng)前文本大小当宴;兩者之間的差異顯示在下面的屏幕截圖中:

這看起來很不錯(cuò) - 但是敏銳的讀者會(huì)注意到這只是解決方案的一半畜吊。 返回General/Text Size下的Settings應(yīng)用,然后再次修改文本大小户矢。 這一次玲献,切換回SwiftTextKitNotepad - 無需重新啟動(dòng)應(yīng)用程序 - 您會(huì)注意到您的應(yīng)用程序沒有響應(yīng)新的文本大小。


Responding to Updates - 響應(yīng)更新

打開NoteEditorViewController.swift并將以下代碼添加到viewDidLoad的末尾

NSNotificationCenter.defaultCenter().addObserver(self, 
    selector: "preferredContentSizeChanged:", 
    name: UIContentSizeCategoryDidChangeNotification,
    object: nil)

上面的代碼注冊(cè)該類以在首選內(nèi)容大小更改時(shí)接收通知梯浪,并在發(fā)生此事件時(shí)傳入要調(diào)用的方法(preferredContentSizeChanged)捌年。

接下來,將以下方法添加到類中:

func preferredContentSizeChanged(notification: NSNotification) {
  textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}

這只是根據(jù)新的首選大小設(shè)置文本視圖字體挂洛。

注意:您可能想知道為什么看起來您將字體設(shè)置為之前的相同值礼预。 當(dāng)用戶更改其首選字體大小時(shí),您必須再次請(qǐng)求首選字體虏劲;它不會(huì)自動(dòng)更新托酸。 更改字體首選項(xiàng)時(shí),通過preferredFontForTextStyle返回的字體將有所不同伙单。

打開NotesListViewController.swift并通過向類中添加以下代碼來覆蓋viewDidLoad函數(shù):

override func viewDidLoad() {
  super.viewDidLoad()
  NSNotificationCenter.defaultCenter().addObserver(self,
      selector: "preferredContentSizeChanged:", 
      name: UIContentSizeCategoryDidChangeNotification, 
      object: nil)
}

嘿获高,是不是你剛剛添加到NoteEditorViewController.swift的代碼? 是的吻育,它是 - 但你會(huì)以稍微不同的方式處理首選的字體更改念秧。

將以下方法添加到類中:

func preferredContentSizeChanged(notification: NSNotification) {
  tableView.reloadData()
}

上面的代碼只是指示table view重新加載其可見單元格,這會(huì)更新每個(gè)單元格的外觀布疼。 這將觸發(fā)對(duì)preferredFontForTextStyle()的調(diào)用并刷新字體選擇摊趾。

構(gòu)建并運(yùn)行您的應(yīng)用程序;更改文本大小設(shè)置游两,并驗(yàn)證您的應(yīng)用是否正確響應(yīng)新用戶首選項(xiàng)砾层。


Changing Layout - 改變布局

這部分看起來效果很好,但是當(dāng)你選擇一個(gè)非常小的字體大小時(shí)贱案,你的table view看起來有點(diǎn)稀疏肛炮,如右下圖所示:

這是動(dòng)態(tài)類型的棘手方面之一(在iOS 7中)。 為了確保您的應(yīng)用程序在各種字體大小中看起來很好宝踪,您的布局需要響應(yīng)用戶的文本設(shè)置侨糟。 自動(dòng)布局為您解決了很多問題,但這是您必須自己解決的一個(gè)問題瘩燥。

您的表行高度需要隨著字體大小的變化而變化秕重。 實(shí)現(xiàn)tableView(_:heightForRowAtIndexPath :)委托方法很好地解決了這個(gè)問題。

將以下代碼添加到NotesListViewController.swift厉膀,在標(biāo)記為Table view data source的部分中:

let label: UILabel = {
  let temporaryLabel = UILabel(frame: CGRect(x: 0, y: 0, width: Int.max, height: Int.max))
  temporaryLabel.text = "test"
  return temporaryLabel
}()

override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
  label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
  label.sizeToFit()
  return label.frame.height * 1.7
}

上面的代碼創(chuàng)建了一個(gè)UILabel的共享實(shí)例溶耘,table view用它來計(jì)算單元格的高度二拐。 然后,在tableView(_:heightForRowAtIndexPath :)中凳兵,將標(biāo)簽的字體設(shè)置為表視圖單元格使用的相同字體百新。 然后它在標(biāo)簽上調(diào)用sizeToFit,強(qiáng)制標(biāo)簽的frame緊緊圍繞文本留荔,并導(dǎo)致frame高度與表格行高度成比例吟孙。

構(gòu)建并運(yùn)行您的應(yīng)用程序;再次修改文本大小設(shè)置聚蝶,表格行現(xiàn)在動(dòng)態(tài)調(diào)整大小以適合文本大小,如下面的屏幕截圖所示:

如果您愿意藻治,現(xiàn)在可以在本教程的其余部分將deployment重置為iOS 8碘勉。


Letterpress Effect - 凸版印刷效果

凸版印刷效果為文本添加了細(xì)微的陰影和高光,使其具有深度感 - 就像文字略微壓入屏幕一樣桩卵。

注意:術(shù)語(yǔ)“凸版印刷 - letterpress”是對(duì)早期印刷機(jī)的一種認(rèn)可验靡,它印有一組刻在塊上的字母并將它們壓入頁(yè)面。 這些字母經(jīng)常在頁(yè)面上留下一個(gè)小縮進(jìn) - 這是一種意想不到但視覺上令人愉悅的效果雏节,這種效果在今天的數(shù)字排版中經(jīng)常被復(fù)制胜嗓。

打開NotesListViewController.swift并使用以下實(shí)現(xiàn)替換tableView(_:cellForRowAtIndexPath :):

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

  let note = notes[indexPath.row]
  let font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
  let textColor = UIColor(red: 0.175, green: 0.458, blue: 0.831, alpha: 1)
  let attributes = [
    NSForegroundColorAttributeName : textColor,
    NSFontAttributeName : font,
    NSTextEffectAttributeName : NSTextEffectLetterpressStyle
  ]
  let attributedString = NSAttributedString(string: note.title, attributes: attributes)

  cell.textLabel?.attributedText = attributedString

  return cell
}

上面的代碼使用凸版印刷樣式為表格單元格的標(biāo)題創(chuàng)建一個(gè)屬性字符串。

構(gòu)建并運(yùn)行您的應(yīng)用程序钩乍;您的table view現(xiàn)在將顯示具有良好凸版效果的文本辞州,如下所示:

Letterpress是一種微妙的效果 - 但這并不意味著你應(yīng)該過度使用它! 視覺效果可能會(huì)使您的文字更有趣寥粹,但它們并不一定能讓您的文字更清晰变过。


Exclusion Paths - 路徑排除

圍繞圖像或其他對(duì)象的流動(dòng)文本是大多數(shù)文字處理器的標(biāo)準(zhǔn)特征。 Text Kit允許您使用排除路徑- exclusion paths在復(fù)雜路徑和形狀周圍渲染文本涝涤。

告訴用戶note的創(chuàng)建日期是很方便的媚狰;您將在顯示此信息的note的右上角添加一個(gè)小的曲線視圖。

您將首先添加視圖本身 - 然后您將創(chuàng)建一個(gè)排除路徑以使文本環(huán)繞它阔拳。

1. Adding the View - 添加視圖

打開NoteEditorViewController.swift并將以下屬性聲明添加到類中:

var timeView: TimeIndicatorView!

顧名思義崭孤,這里有時(shí)間指示器子視圖。

接下來糊肠,將此代碼添加到viewDidLoad的最后:

timeView = TimeIndicatorView(date: note.timestamp)
textView.addSubview(timeView)

這只是創(chuàng)建新視圖的實(shí)例并將其添加為子視圖辨宠。

TimeIndicatorView計(jì)算自己的大小,但不會(huì)自動(dòng)執(zhí)行此操作罪针。 當(dāng)視圖控制器布局子視圖時(shí)彭羹,您需要一種機(jī)制來調(diào)用updateSize

最后泪酱,將以下兩個(gè)方法添加到類中:

override func viewDidLayoutSubviews() {
  updateTimeIndicatorFrame()
}

func updateTimeIndicatorFrame() {
  timeView.updateSize()
  timeView.frame = CGRectOffset(timeView.frame, textView.frame.width - timeView.frame.width, 0)
}

viewDidLayoutSubviews調(diào)用updateTimeIndicatorFrame派殷,它執(zhí)行兩項(xiàng)操作:調(diào)用updateSize設(shè)置子視圖的大小还最,并將子視圖放在文本視圖的右上角。

剩下的就是當(dāng)視圖控制器收到內(nèi)容大小已更改的通知時(shí)調(diào)用updateTimeIndicatorFrame毡惜。 將preferredContentSizeChanged的實(shí)現(xiàn)替換為以下內(nèi)容:

func preferredContentSizeChanged(notification: NSNotification) {
  textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
  updateTimeIndicatorFrame()
}

構(gòu)建并運(yùn)行您的項(xiàng)目拓轻,點(diǎn)擊列表項(xiàng),時(shí)間指示器視圖將顯示在項(xiàng)目視圖的右上角经伙,如下所示:

修改設(shè)備文本大小首選項(xiàng)扶叉,視圖將自動(dòng)調(diào)整為適合。

但是帕膜,有些事情看起來不太對(duì)勁枣氧。 note的文本在時(shí)間指示器視圖后面呈現(xiàn),而不是整齊地圍繞它流動(dòng)垮刹。 幸運(yùn)的是达吞,這是排除路徑旨在解決的確切問題。

打開TimeIndicatorView.swift并查看curvePathWithOrigin()荒典。 時(shí)間指示器視圖在填充背景時(shí)使用此代碼酪劫,但您也可以使用它來確定文本流動(dòng)的路徑。 啊哈 - 這就是為什么貝塞爾曲線的計(jì)算被分解成自己的方法寺董!

剩下的就是定義排除路徑本身覆糟。 打開NoteEditorViewController.swift并將以下代碼塊添加到updateTimeIndicatorFrame的最后:

let exclusionPath = timeView.curvePathWithOrigin(timeView.center)
textView.textContainer.exclusionPaths = [exclusionPath]

上面的代碼基于在時(shí)間指示器視圖中創(chuàng)建的Bezier路徑創(chuàng)建排除路徑,但是具有相對(duì)于文本視圖的原點(diǎn)和坐標(biāo)遮咖。

構(gòu)建并運(yùn)行項(xiàng)目并從列表中選擇一個(gè)項(xiàng)目滩字;現(xiàn)在,文本在時(shí)間指示器視圖周圍很好地流動(dòng)盯滚。

這個(gè)簡(jiǎn)單的例子只是劃分了排除路徑的能力踢械。 您可能會(huì)注意到exclusionPaths屬性需要一個(gè)路徑數(shù)組,這意味著每個(gè)容器都可以支持多個(gè)排除路徑魄藕。

此外内列,排除路徑可以根據(jù)需要簡(jiǎn)單或復(fù)雜。 需要渲染星形或蝴蝶形的文字嗎背率? 只要您可以定義路徑话瞧,exclusionPaths就會(huì)毫無問題地處理它!

當(dāng)文本容器在排除路徑更改時(shí)通知布局管理器時(shí)寝姿,您可以實(shí)現(xiàn)動(dòng)態(tài)或甚至動(dòng)畫排除路徑 - 只是不要指望您的用戶欣賞在他們嘗試閱讀時(shí)在屏幕上移動(dòng)的文本交排!


Dynamic Text Formatting and Storage - 動(dòng)態(tài)文本格式和存儲(chǔ)

您已經(jīng)看到Text Kit可以根據(jù)用戶的文本大小首選項(xiàng)動(dòng)態(tài)調(diào)整字體。 但是饵筑,如果字體可以根據(jù)實(shí)際文本本身動(dòng)態(tài)更新埃篓,那會(huì)不會(huì)很酷?

例如根资,如果您想自動(dòng)創(chuàng)建此應(yīng)用程序架专,該怎么辦:

  • 使用波形符(?)包圍的任何文本都是一個(gè)花哨的字體
  • 使用下劃線字符(_)斜體包圍任何文本
  • 將短劃線字符( - )包圍的任何文本劃掉
  • 將所有大寫字母的文字設(shè)為紅色

通過利用Text Kit框架的強(qiáng)大功能同窘,這正是您在本節(jié)中所要做的!

為此部脚,您需要了解Text Kit中的文本存儲(chǔ)系統(tǒng)的工作原理想邦。 這是一個(gè)圖表,顯示用于存儲(chǔ)委刘,呈現(xiàn)和顯示文本的“Text Kit stack”

在幕后丧没,Apple會(huì)在您創(chuàng)建UITextViewUILabelUITextField時(shí)自動(dòng)為您創(chuàng)建這些類锡移。在您的應(yīng)用中呕童,您可以使用這些默認(rèn)實(shí)現(xiàn),也可以自定義任何部分以獲得自己的行為淆珊。讓我們來看看每個(gè)類:

  • NSTextStorage將要呈現(xiàn)的文本存儲(chǔ)為屬性字符串拉庵,并通知布局管理器文本內(nèi)容的任何更改。您可能希望子類化NSTextStorage套蒂,以便在文本更新時(shí)動(dòng)態(tài)更改文本屬性。
  • NSLayoutManager獲取存儲(chǔ)的文本并在屏幕上呈現(xiàn)它茫蛹,它在您的應(yīng)用中充當(dāng)布局“引擎”操刀。
  • NSTextContainer描述應(yīng)用呈現(xiàn)文本的屏幕區(qū)域的幾何形狀。每個(gè)文本容器通常與UITextView相關(guān)聯(lián)婴洼。您可能希望子類化NSTextContainer以定義要在其中呈現(xiàn)文本的復(fù)雜形狀骨坑。

要在此應(yīng)用程序中實(shí)現(xiàn)動(dòng)態(tài)文本格式設(shè)置功能,您需要子類化NSTextStorage柬采,以便在用戶在文本中鍵入時(shí)動(dòng)態(tài)添加文本屬性欢唾。

一旦您創(chuàng)建了自定義NSTextStorage,您將用您自己的實(shí)現(xiàn)替換UITextView的默認(rèn)文本存儲(chǔ)實(shí)例粉捻。讓我們?cè)囈辉嚕?/p>


Subclassing NSTextStorage - 子類化NSTextStorage

右鍵單擊項(xiàng)目導(dǎo)航器中的SwiftTextKitNotepad組礁遣,選擇New File ...,然后選擇iOS / Source / Cocoa Touch Class并單擊Next肩刃。

將類命名為SyntaxHighlightTextStorage祟霍,使其成為NSTextStorage的子類,并確認(rèn)語(yǔ)言設(shè)置為Swift盈包。 單擊Next沸呐,然后單擊Create

打開SyntaxHighlightTextStorage.swift并在類聲明中添加一個(gè)新屬性:

let backingStore = NSMutableAttributedString()

文本存儲(chǔ)子類必須提供自己的持久性呢燥,因此使用NSMutableAttributedString后備存儲(chǔ) - 稍后將對(duì)此進(jìn)行更多介紹崭添。

接下來將以下內(nèi)容添加到類中:

override var string: String {
  return backingStore.string
}

override func attributesAtIndex(index: Int, effectiveRange range: NSRangePointer) -> [NSObject : AnyObject] {
  return backingStore.attributesAtIndex(index, effectiveRange: range)
}

這兩個(gè)聲明中的第一個(gè)覆蓋string計(jì)算屬性,推遲到后備存儲(chǔ)叛氨。 同樣呼渣,attributesAtIndex方法也委托給后備存儲(chǔ)棘伴。

最后將剩余的強(qiáng)制覆蓋添加到同一個(gè)文件中:

override func replaceCharactersInRange(range: NSRange, withString str: String) {
  println("replaceCharactersInRange:\(range) withString:\(str)")

  beginEditing()
  backingStore.replaceCharactersInRange(range, withString:str)
  edited(.EditedCharacters | .EditedAttributes, range: range, changeInLength: (str as NSString).length - range.length)
  endEditing()
}

override func setAttributes(attrs: [NSObject : AnyObject]!, range: NSRange) {
  println("setAttributes:\(attrs) range:\(range)")

  beginEditing()
  backingStore.setAttributes(attrs, range: range)
  edited(.EditedAttributes, range: range, changeInLength: 0)
  endEditing()
}

同樣,這些方法委托給后備存儲(chǔ)徙邻。 但是排嫌,它們還包含對(duì)beginEditingeditedendEditing的調(diào)用缰犁。 文本存儲(chǔ)類需要這三種方法淳地,以便在進(jìn)行編輯時(shí)通知其關(guān)聯(lián)的布局管理器。

您可能已經(jīng)注意到帅容,為了子類化文本存儲(chǔ)颇象,您需要編寫相當(dāng)多的代碼。 由于NSTextStorage是類集群的公共接口并徘,因此您不能僅將其子類化并覆蓋一些方法來擴(kuò)展其功能遣钳。 相反,您必須自己實(shí)現(xiàn)某些要求麦乞,例如屬性字符串?dāng)?shù)據(jù)的后備存儲(chǔ)蕴茴。

注意:類集群是Apple整個(gè)框架中常用的設(shè)計(jì)模式。類集群只是Abstract Factory模式的Objective-C實(shí)現(xiàn)姐直,它提供了一個(gè)通用接口倦淀,用于創(chuàng)建相關(guān)或依賴對(duì)象的族,而無需指定具體類声畏。 NSArrayNSNumber等熟悉的類實(shí)際上是類集群的公共接口撞叽。

Apple使用類集群將私有具體子類封裝在公共抽象超類下,并且它是這個(gè)抽象超類插龄,它聲明客戶端必須使用的方法才能創(chuàng)建其私有子類的實(shí)例愿棋。客戶端也完全不知道工廠正在分配哪個(gè)私有類均牢,因?yàn)樗慌c公共接口進(jìn)行交互糠雨。

使用類集群肯定簡(jiǎn)化了界面,使學(xué)習(xí)和使用類變得更加容易膨处,但重要的是要注意在可擴(kuò)展性和簡(jiǎn)單性之間進(jìn)行權(quán)衡见秤。創(chuàng)建集群的抽象超類的自定義子類通常要困難得多。

現(xiàn)在你有了一個(gè)自定義的NSTextStorage真椿,你需要?jiǎng)?chuàng)建一個(gè)使用它的UITextView鹃答。

后記

本篇主要講述了一個(gè)簡(jiǎn)單布局示例,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末突硝,一起剝皮案震驚了整個(gè)濱河市测摔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖锋八,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浙于,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挟纱,警方通過查閱死者的電腦和手機(jī)羞酗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紊服,“玉大人檀轨,你說我怎么就攤上這事∑坂停” “怎么了参萄?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)煎饼。 經(jīng)常有香客問我讹挎,道長(zhǎng),這世上最難降的妖魔是什么吆玖? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任筒溃,我火速辦了婚禮,結(jié)果婚禮上沾乘,老公的妹妹穿的比我還像新娘铡羡。我一直安慰自己,他們只是感情好意鲸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尽爆,像睡著了一般怎顾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漱贱,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天槐雾,我揣著相機(jī)與錄音,去河邊找鬼幅狮。 笑死募强,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崇摄。 我是一名探鬼主播擎值,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逐抑!你這毒婦竟也來了鸠儿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎进每,沒想到半個(gè)月后汹粤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡田晚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年嘱兼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贤徒。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芹壕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泞莉,到底是詐尸還是另有隱情哪雕,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布鲫趁,位于F島的核電站斯嚎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挨厚。R本人自食惡果不足惜堡僻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一吸重、第九天 我趴在偏房一處隱蔽的房頂上張望又碌。 院中可真熱鬧巍扛,春花似錦询枚、人聲如沸办成。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)负敏。三九已至壤躲,卻和暖如春城菊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碉克。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工凌唬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漏麦。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓客税,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親撕贞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子更耻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容