在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 初識 TextKit
與四軸無人機的通訊
在沙盒中編寫腳本
結構體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數據腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內部
同步數據
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數據模型和模型對象
Core Data
字符串本地化
View Controller 轉場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據驗證
數據同步
自定義 ViewController 容器轉場
游戲
調試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術:Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構建 iOS 應用
Android 中的 SQLite 數據庫支持
Fetch 請求
導入大數據集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉字符串
相機工作原理
Build 過程

初識 TextKit

iOS 7 的發(fā)布給開發(fā)者的案頭帶來了很多新工具。其中一個就是 TextKit。TextKit 由許多新的 UIKit 類組成,顧名思義,這些類就是用來處理文本的。在這里,我們將介紹 TextKit 的來由、它的組成,以及通過幾個例子解釋開發(fā)者怎樣將它派上大用場。

但是首先我們得先闡明一個觀點:TextKit 可能是近期對 UIKit 最重要的補充了。iOS 7 的新界面用純文本按鈕替換了大量的圖標和邊框。總的來說,文本和文本布局在新 OS 系統(tǒng)的視覺效果中所占有的重要性大大提高了。iOS7 的重新設計完全是被文本驅動,這樣說也許并不夸張——而文本全部是 TextKit 來處理的。

告訴你這個變動到底有多大吧:iOS7 之前的所有版本,(幾乎)所有的文本都是 WebKit 來處理的。對:WebKit,web 瀏覽器引擎。所有 UILabel、UITextField,以及 UITextView 都在后臺以某種方式使用 web views 來進行文本布局和渲染。為了新的界面風格,它們全都被重新設計以使用 TextKit。

iOS 上文本的簡短歷史

這些新類并不是用來替換開發(fā)者以前使用的類。對 SDK 來說,TextKit 提供的是全新的功能。iOS 7 之前,TextKit 提供的功能必須都手動完成。這是現有框架缺失的功能。

長期以來,只有一個基本的文本布局和渲染框架:CoreText。同樣也只有一個途徑讀取用戶的鍵盤輸入:UITextInput 協議。在 iOS6 中,為了簡單地獲取系統(tǒng)的文本選擇,也只有一個選擇:繼承 UITextView。

(這可能就是為什么我要公開自己十年開發(fā)文本編輯器的經驗的原因了)在渲染文本和讀取鍵盤輸入之間存在著巨大(跟我讀:巨大)的缺口。這個缺口可能也是導致很少有富文本或者語法高亮編輯器的原因了——毫無疑問,開發(fā)一個好用的文本編輯器得耗費幾個月的時間。

就這樣——如下是 iOS 文本(不那么)簡短歷史的簡短概要:

iOS 2:這是第一個公開的 SDK,包括一個簡單的文本顯示組件(UILabel),一個簡單的文本輸入組件(UITextField),以及一個簡單的、可滾動、可編輯的并且支持更大量文本的組件:UITextView。這些組件都只支持純文本,沒有文本選擇支持(僅支持插入點),除了設置字體和文本顏色外幾乎沒有其他可定制功能。

iOS 3:新特性有復制和粘貼,以及復制粘貼所需要的文本選擇功能。數據探測器(Data Detector)為文本視圖提供了一個高亮電話號碼和鏈接的方法。然而,除了打開或關閉這些特性外,開發(fā)者基本上沒有什么別的事情可以做。

iOS 3.2:iPad 的出現帶來了 CoreText,也就是前面提到的低級文本布局和渲染引擎(從Mac OS X 10.5 移植過來的),以及 UITextInput,就是前面也提到的鍵盤存取協議。Apple 將 Pages 作為移動設備上文本編輯功能的樣板工程1。然而,由于我前面提到的框架缺口,只有很少的應用使用它們。

iOS 4:iOS 3.2 發(fā)布僅僅幾個月后就發(fā)布了,文本方面沒有一丁點新功能。(個人經歷:在 WWDC,我走近工程師們,告訴他們我想要一個完善的 iOS 文本布局系統(tǒng)?;卮鹗牵骸芭丁峤粋€請求。”不出所料…)

iOS 5:文本方面沒啥變化。(個人經歷:在 WWDC,我和工程師們談及 iOS 上文本系統(tǒng)?;卮鹗牵骸拔覀儧]有看到太多這方面的請求…” 靠!)

iOS 6:有些動作了:屬性文本編輯被加入了 UITextView。很不幸的是,它很難定制。默認的 UI 有粗體、斜體和下劃線。用戶可以設置字體大小和顏色。粗看起來相當不錯,但還是沒法控制布局或者提供一個便利的途徑來定制文本屬性。然而對于(文本編輯)開發(fā)者,有一個大的新功能:可以繼承 UITextView 了,這樣的話,除了以前版本提供的鍵盤輸入外,開發(fā)者可以“免費”獲得文本選擇功能。而在這以前,開發(fā)者必須實現一個完全自定義的文本選擇功能,這可能是很多非純文本工具的開發(fā)半途而廢的原因。(個人經歷:我,WWDC,工程師們。我想要一個 iOS 的文本系統(tǒng)。回答:“嗯。吖。是的。也許?看,它只是不執(zhí)行…” 所以畢竟還是有希望,對吧?)

iOS 7:終于來了,TextKit。

功能

所以我們來了。iOS7 帶著 TextKit 登陸了。咱們看看它可以做什么!深入之前,我還想提一下,嚴格來說,這些新功能中的大部分以前都可以實現。如果你有大量的資源和時間來用 CoreText 構建一個文本引擎,這些都是可以實現的。但是在以前,構建一個完善的富文本編輯器可能花費你幾個月的時間,現在卻非常簡單。你只需要到在 Xcode 里打開一個界面文件,然后將 UITextView 拖到你的試圖控制器,就可以獲得所有以下這些功能:

字距調整(Kerning):所有的字符都有一個矩形的外邊框,這些邊框必須彼此相鄰來放置,這樣的想法已經過時了。例如,現代文本布局會考慮到一個大寫的“T”的“兩翼”下面有一些空白,所以它會把后面的小寫字母向左移讓它們更靠近點。這樣做的結果大大提高了文本的易讀性,特別是在更長的文字中:

http://wiki.jikexueyuan.com/project/objc/images/5-1.png" alt="" />

連寫:我認為這主要是個藝術功能,但當某些字符組合(如“f”后面是“l(fā)”)使用組合符號(所謂的字形(glyph))繪制時,有些文本確實看起來更好(更美觀)。

http://wiki.jikexueyuan.com/project/objc/images/5-2.png" alt="" />

圖像附件:現在可以向 Text View 中添加圖像了。

斷字:編輯文本時沒那么重要,但如果要以好看易讀的方式展現文本時,這就相當重要。斷字意味著在行邊界處分割單詞,從而為整體文本創(chuàng)建一個更整齊的排版和外觀。個人經歷: iOS 7 之前,開發(fā)者必須直接使用 CoreText。像這樣:首先以句子為基礎檢測文本語言,然后獲取句子中每個單詞可能的斷字點,然后在每一個可能的斷字點上插入定制的連字占位字符。準備好之后,運行 CoreText 的布局方法并手動將連字符插入到斷行。如果你想得到好的效果,之后你得檢查帶有連字符的文本沒有超出行邊界,如果超出了,在運行一次行的布局方法,這一次不要使用上次使用的斷字點。使用 TextKit 的話,就非常簡單了,設置 hyphenationFactor 屬性就可以啟用斷字。

http://wiki.jikexueyuan.com/project/objc/images/5-3.png" alt="" />

可定制性:對我來說,甚至比改進過的排版還多,這是個全新的功能。以前開發(fā)者必須在使用現有的功能和自己全部重頭寫之間做出選擇?,F在提供了一整套類,它們有代理協議,或者可以被覆蓋從而改變部分行為。例如,不必重寫整個文本組件,你現在就可以改變指定單詞的斷行行為。我認為這是個勝利。

更多的富文本屬性:現在可以設置不同的下劃線樣式(雙線、粗線、虛線、點線,或者它們的組合)。提高文本的基線非常容易,這可用來設置上標數字。開發(fā)者也不再需要自己為定制渲染的文本繪制背景顏色了(CoreText 不支持這些功能)。

序列化:過去沒有內置的方法從磁盤讀取帶文本屬性的字符串?;蛘咴賹懟卮疟P?,F在有了。

文本樣式:iOS 7 的界面引入了一個全局預定義的文本類型的新概念。這些文本類型分配了一個全局預定義的外觀。理想情況下,這可以讓整個系統(tǒng)的標題和連續(xù)文本具有一致的風格。通過設置應用,用戶可以定義他們的閱讀習慣(例如文本大小),那些使用文本樣式的應用將自動擁有正確的文本大小和外觀。

文本效果:最后也是最不重要的。iOS 7 有且僅有一個文本效果:凸版。使用此效果的文本看起來像是蓋在紙上面一樣。內陰影,等等。個人觀點:真的?靠…?在一個已經完全徹底不可饒恕地槍斃了所有無用的懷舊裝飾(skeuomorphism)的 iOS 系統(tǒng)上,誰會需要這個像文本蓋在紙上的效果?

結構

可能概覽一個系統(tǒng)最好的方法是畫一幅圖。這是 UIKit 文本系統(tǒng)——TextKit 的簡圖:

http://wiki.jikexueyuan.com/project/objc/images/5-4.png" alt="" />

從上圖可以看出來,要讓一個文本引擎工作,需要幾個參與者。我們將從外到里介紹它們:

字符串(String):要繪制文本,那么必然在某個地方有個字符串來存儲這段文本。在默認的結構中,NSTextStorage 保存并管理這個字符串,在這種情況中,它可以遠離繪制。但并不一定非得這樣。使用 TextKit 時,文本可以來自任何適合的來源。例如,對于一個代碼編輯器,字符串可以是一棵包含所有顯示的代碼的結構信息的注釋語法樹(annotated syntax tree,縮寫為 AST)。使用一個自定義的 NSTextStorage 就可以讓文本在稍后動態(tài)地添加字體或顏色高亮等文本屬性裝飾。這是第一次,開發(fā)者可以直接為文本組件使用自己的模型。要想實現這個功能,我們需要一個特別設計的 NSTextStorage,即:

NSTextStorage:如果你把文本系統(tǒng)看做一個模型-視圖-控制器(MVC)架構,這個類代表的是模型。NSTextStorage 是一個中樞,它管理所有的文本和屬性信息。系統(tǒng)只提供了兩個存取器方法存取它們,并另外提供了兩個方法來分別修改文本和屬性。后面我們將進一步了解這些方法?,F在重要的是你得理解 NSTextStorage 是從它的父類 NSAttributedString 繼承了這些方法。這就很清楚了,NSTextStorage——從文本系統(tǒng)看來——僅僅是一個帶有屬性的字符串,附帶一些擴展。這兩者唯一的重大不同點是 NSTextStorage 包含了一個方法,可以把所有對其內容進行的修改以通知的形式發(fā)送出來。我們等一下會介紹這部分內容。

UITextView:堆棧的另一頭是實際的視圖。在 TextKit 中,有兩個目的:第一,它是文本系統(tǒng)用來繪制的視圖。文本視圖它自己并會做任何繪制;它僅僅提供一個供其它類繪制的區(qū)域。作為視圖層級機構中唯一的組件,第二個目的是處理所有的用戶交互。具體來說,Text View 實現 UITextInput 的協議來處理鍵盤事件,它為用戶提供了一種途徑來設置一個插入點或選擇文本。它并不對文本做任何實際上的改變,僅僅將這些改變請求轉發(fā)給剛剛討論的 Text Storage。

NSTextContainer:每個 Text View 定義了一個文本可以繪制的區(qū)域。為此,每個 Text View 都有一個 Text Container,它精確地描述了這個可用的區(qū)域。在簡單的情況下,這是一個垂直的無限大的矩形區(qū)域。文本被填充到這個區(qū)域,并且 Text View 允許用戶滾動它。然而,在更高級的情況下,這個區(qū)域可能是一個無限大的矩形。例如,當渲染一本書時,每一頁都有最大的高度和寬度。 Text Container 會定義這個大小,并且不接受任何超出的文本。相同情況下,一幅圖像可能占據了頁面的一部分,文本應該沿著它的邊緣重新排版。這也是由 Text Container 來處理的,我們會在后面的例子中看到這一點。

NSLayoutManager:Layout Manager 是中心組件,它把所有組件粘合在一起:

  1. 這個管理器監(jiān)聽 Text Storage 中文本或屬性改變的通知,一旦接收到通知就觸發(fā)布局進程。
  2. 從 Text Storage 提供的文本開始,它將所有的字符翻譯為字形(Glyph)2
  3. 一旦字形全部生成,這個管理器向它的 Text Containers 查詢文本可用以繪制的區(qū)域。
  4. 然后這些區(qū)域被行逐步填充,而行又被字形逐步填充。一旦一行填充完畢,下一行開始填充。
  5. 對于每一行,布局管理器必須考慮斷行行為(放不下的單詞必須移到下一行)、連字符、內聯的圖像附件等等。
  6. 當布局完成,文本的當前顯示狀態(tài)被設為無效,然后 Layout Manager 將前面幾步排版好的文本設給 Text View。

CoreText:沒有直接包含在 TextKit 中,CoreText 是進行實際排版的庫。對于布局管理器的每一步,CoreText 被這樣或那樣的方式調用。它提供了從字符到字形的翻譯,用它們來填充行,以及建議斷字點。

Cocoa 文本系統(tǒng)

創(chuàng)建像 TextKit 這樣龐大復雜的系統(tǒng)肯定不是件簡單快速的事情,而且肯定需要豐富的經驗和知識。在 iOS 的前面 6 個主版本中,一直沒有提供一個“真正的”文本組件,這也說明了這一點。Apple 把它視為一個大的新特性,當然沒啥問題。但是它真的是全新的嗎?

這里有個數字:在 UIKit 的 131 個公共類中,只有 9 個的名字沒有使用UI作為前綴。這 9 個類使用的是舊系統(tǒng)的的、舊世界的(跟我讀:Mac OS)前綴 NS。而且這九個類里面,有七個是用來處理文本的。巧合?好吧…

這是 Cocoa 文本系統(tǒng)的簡圖。不妨和上面 TextKit 的那幅圖作一下對比。

http://wiki.jikexueyuan.com/project/objc/images/5-5.png" alt="" />

驚人地相似。很明顯,最起碼主要部分,兩者是相同的。很明顯——除了右邊部分以及 NSTextViewUITextView ——主要的類全部相同。TextKit 是(起碼部分是)從 Cocoa 文本系統(tǒng)移植到 iOS。(我之前一直請求的那個,耶?。?/em>

進一步比較還是能看出一些不同的。最值得注意的有:

  • 在 iOS 上沒有 NSTypesetterNSGlyphGenerator 這兩個類。在 Mac OS 上有很多方法來定制排版,在 iOS 中被極大地簡化了,去掉了一些抽象概念,并將這個過程合并到 NSLayoutManager 中來。保留下來的是少數的代理方法,以用來更改文本布局和斷行行為。

  • 這些 Cocoa 的類移植到 iOS 系統(tǒng)后新增了幾個非常便利的功能。在 Cocoa 中,必須手工地將確定的區(qū)域從 Text Container 分離出來(見上)。而 UIKit 類提供了一個簡單的 exclusionPaths 屬性就可以做到這一點。

  • 有些功能未能提供,比如,內嵌表格,以及對非圖像的附件的支持。

盡管有這些區(qū)別,總的來說系統(tǒng)還是一樣的。NSTextStorage 在兩個系統(tǒng)是是一模一樣的,NSLayoutManagerNSTextContainer 也沒有太大的不同。這些變動,在沒有太多去除對一些特例的支持的情況下,看來(某些情況下大大地)使文本系統(tǒng)的使用變得更為容易。我認為這是件好事。

事后回顧我從 Apple 工程師那里得到的關于將 Cocoa 文本系統(tǒng)移植到 iOS 的答案,我們可以得到一些背景信息。拖到現在并削減功能的原因很簡單:性能、性能、性能。文本布局可能是極度昂貴的任務——內存方面、電量方面以及時間方面——特別是在移動設備上。Apple 必須采用更簡單的解決方案,并等到處理能力能夠至少部分支持一個完善的文本布局引擎。

示例

為了說明 TextKit 的能力,我創(chuàng)建了一個小的演示項目,你可以在 GitHub 上找到它。在這個演示程序中,我只完成了一些以前不容易完成的功能。我必須承認寫這些代碼只花了我禮拜天的一個上午的時間;如果以前要做同樣的事情,我得花幾天甚至幾個星期。

TextKit 包括了超過 100 個方法,一篇文章根本沒辦法盡數涉及。而事實上,大多數時候,你需要的僅僅是一個正確的方法,TextKit 的使用和定制性也仍有待探索。所以我決定做四個更小的演示程序,而非一個大的演示程序來展示所有功能。每個演示程序中,我試著演示針對不同的方面和不同的類進行定制。

演示程序1:配置

讓我們從最簡單的開始:配置文本系統(tǒng)。正如你在上面 TextKit 簡圖中看到的,NSTextStorage、NSLayoutManagerNSTextContainer 之間的箭頭都是有兩個頭的。我試圖描述它們的關系是 1 對 N 的關系。就是那樣:一個 Text Storage 可以擁有多個 Layout Manager,一個 Layout Manager 也可以擁有多個 Text Container。這些多重性帶來了很好的特性:

  • 將多個 Layout Manager 附加到同一個 Text Storage 上,可以產生相同文本的多種視覺表現,而且可以把它們放到一起來顯示。每一個表現都有獨立的位置和大小。如果相應的 Text View 可編輯,那么在某個 Text View 上做的所有修改都會馬上反映到所有 Text View 上。
  • 將多個 Text Container 附加到同一個 Layout Manager 上,這樣可以將一個文本分布到多個視圖展現出來。很有用的一個例子,基于頁面的布局:每個頁面包含一個單獨的 Text View。所有這些視圖的 Text Container 都引用同一個 Layout Manager,這時這個 Layout Manager 就可以將文本分布到這些視圖上來顯示。

在 Storyboard 或者 Interface 文件中實例化 UITextView 時,它會預配置一個文本系統(tǒng):一個 Text Storage,引用一個 Layout Manager,而后者又引用一個 Text Container。同樣地,一個文本系統(tǒng)棧也可以通過代碼直接創(chuàng)建:

NSTextStorage *textStorage = [NSTextStorage new];

NSLayoutManager *layoutManager = [NSLayoutManager new];
[textStorage addLayoutManager: layoutManager];

NSTextContainer *textContainer = [NSTextContainer new];
[layoutManager addTextContainer: textContainer];

UITextView *textView = [[UITextView alloc] initWithFrame:someFrame
                                                                     textContainer:textContainer];

這是最簡單的方式。手工創(chuàng)建一個文本系統(tǒng),唯一需要記住的事情是你的 View Controller 必須 retain 這個 Text Storage。在棧底的 Text View 只保留了對 Text Storage 和 Layout Manager 的弱引用。當 Text Storage 被釋放時,Layout Manager 也被釋放了,這樣留給 Text View 的就只有一個斷開的 Text Container 了。

這個規(guī)則有一個例外。只有從一個 interface 文件或 storyboard 實例化一個 Text View 時,Text View 確實會自動 retain Text Storage。框架使用了一些黑魔法以確保所有的對象都被 retain,而無需手動建立一個 retain 環(huán)。

記住這些之后,創(chuàng)建一個更高級的設置也非常簡單。假設在一個視圖里面依舊有一個從 nib 實例化的 Text View,叫做 originalTextView。增加對相同文本的第二個文本視圖只需要復制上面的代碼,并重用 originalTextView 的 Text Storage:

NSTextStorage *sharedTextStorage = originalTextView.textStorage;

NSLayoutManager *otherLayoutManager = [NSLayoutManager new];
[sharedTextStorage addLayoutManager: otherLayoutManager];

NSTextContainer *otherTextContainer = [NSTextContainer new];
[otherLayoutManager addTextContainer: otherTextContainer];

UITextView *otherTextView = [[UITextView alloc] initWithFrame:someFrame
                                                textContainer:otherTextContainer];

將第二個 Text Container 附加到 Layout Manager 也差不多。比方說我們希望上面例子中的文本填充兩個 Text View,而非一個。簡單:

NSTextContainer *thirdTextContainer = [NSTextContainer new];
[otherLayoutManager addTextContainer: thirdTextContainer];

UITextView *thirdTextView = [[UITextView alloc] initWithFrame:someFrame
                                                textContainer:thirdTextContainer];

但有一點需要注意:由于在 otherTextView 中的 Text Container 可以無限地調整大小,thirdTextView 永遠不會得到任何文本。因此,我們必須指定文本應該從一個視圖回流到其它視圖,而不應該調整大小或者滾動:

otherTextView.scrollEnabled = NO;

不幸的是,看來將多個 Text Container 附加到一個 Layout Manager 會禁用編輯功能。如果必須保留編輯功能的話,那么一個 Text Container 只能附加到一個 Layout Manager 上。

想要一個這個配置的可運行的例子的話,請在前面提到的 TextKitDemo 中查看 “Configuration” 標簽頁。

演示程序2:語法高亮

如果配置 Text View 不是那么令人激動,那么這里有更有趣的:語法高亮!

看看 TextKit 組件的責任劃分,就很清楚語法高亮應該由 Text Storage 實現。因為 NSTextStorage 是一個類簇3,創(chuàng)建它的子類需要做不少工作。我的想法是建立一個復合對象:實現所有的方法,但只是將對它們的調用轉發(fā)給一個實際的實例,將輸入輸出參數或者結果修改為希望的樣子。

NSTextStorage 繼承自 NSMutableAttributedString,并且必須實現以下四個方法——兩個 getter 和兩個 setter:

- (NSString *)string;
- (NSDictionary *)attributesAtIndex:(NSUInteger)location
                     effectiveRange:(NSRangePointer)range;
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;

一個類簇的子類的復合對象的實現也相當簡單。首先,找到一個滿足所有要求的最簡單的類。在我們的例子中,它是 NSMutableAttributedString,我們用它作為實現自定義存儲的實現:

@implementation TKDHighlightingTextStorage
{
    NSMutableAttributedString *_imp;
}

- (id)init
{
    self = [super init];
    if (self) {
        _imp = [NSMutableAttributedString new];
    }
    return self;
}

有了這個對象,只需要一行代碼就可以實現兩個 getter 方法:

- (NSString *)string
{
    return _imp.string;
}

- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
{
    return [_imp attributesAtIndex:location effectiveRange:range];
}

實現兩個 setter 方法也幾乎同樣簡單。但也有一個小麻煩:Text Storage 需要通知它的 Layout Manager 變化發(fā)生了。因此 settter 方法必須也要調用 -edited:range:changeInLegth: 并傳給它變化的描述。聽起來更糟糕,實現變成:

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
    [_imp replaceCharactersInRange:range withString:str];
    [self edited:NSTextStorageEditedCharacters range:range
                                      changeInLength:(NSInteger)str.length - (NSInteger)range.length];
}

- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
{
    [_imp setAttributes:attrs range:range];
    [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
}

就這樣,我們在文本系統(tǒng)棧里面有了一個 Text Storage 的全功能替換版本。在從 Interface 文件中載入時,可以像這樣將它插入文本視圖——但是記住從一個實例變量引用 Text Storage:

_textStorage = [TKDHighlightingTextStorage new];
[_textStorage addLayoutManager: self.textView.layoutManager];

到目前為止,一切都很好。我們設法插入了一個自定義的文本存儲,接下來我們需要真正高亮文本的某些部分了?,F在,一個簡單的高亮應該就是夠了:我們希望將所有 iWords 的顏色變成紅色——也就是那些以小寫“i”開頭,后面跟著一個大寫字母的單詞。

一個方便實現高亮的辦法是覆蓋 -processEditing。每次文本存儲有修改時,這個方法都自動被調用。每次編輯后,NSTextStorage 會用這個方法來清理字符串。例如,有些字符無法用選定的字體顯示時,Text Storage 使用一個可以顯示它們的字體來進行替換。

和其它一樣,為 iWords 增加一個簡單的高亮也相當簡單。我們覆蓋 -processEditing,調用父類的實現,并設置一個正則表達式來查找單詞:

- (void)processEditing
{
    [super processEditing];

    static NSRegularExpression *iExpression;
    NSString *pattern = @"i[\\p{Alphabetic}&&\\p{Uppercase}][\\p{Alphabetic}]+";
    iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:pattern
                                                                           options:0
                                                                             error:NULL];

然后,首先清除之前的所有高亮:

    NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange];
    [self removeAttribute:NSForegroundColorAttributeName range:paragaphRange];

其次遍歷所有的樣式匹配項并高亮它們:

    [iExpression enumerateMatchesInString:self.string
                                  options:0 range:paragaphRange
                               usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
    {
        [self addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:result.range];
    }];
}

就是這樣。我們創(chuàng)建了一個支持語法高亮的動態(tài) Text View。當用戶鍵入時,高亮將被實時應用。而且這只需幾行代碼??岚桑?/p>

http://wiki.jikexueyuan.com/project/objc/images/5-6.png" alt="" />

請注意僅僅使用 edited range 是不夠的。例如,當手動鍵入 iWords,只有一個單詞的第三個字符被鍵入后,正則表達式才開始匹配。但那時 editedRange 僅包含第三個字符,因此所有的處理只會影響這一個字符。通過重新處理整個段落可以解決這個問題,這樣既完成高亮功能,又不會太過影響性能。

想要一個可以運行的 Demo 的話,請在前面提到的 TextKitDemo 中查看“Highlighting”標簽頁。

演示程序3:布局修改

如前所述,Layout Manager 是核心的布局主力。Mac OS 上 NSTypesetter 的高度可定制功能被并入 iOS 上的 NSLayoutManager。雖然 TextKit 不具備像 Cocoa 文本系統(tǒng)那樣的完全可定制性,但它提供很多代理方法來允許做一些調整。如前所述,TextKit 與 CoreText 更緊密地集成在一起,主要是基于性能方面的考慮。但是兩個文本系統(tǒng)的理念在一定程度上是不一樣的:

Cocoa 文本系統(tǒng):在 Mac OS上,性能不是問題,設計考量的全部是靈活性??赡苁沁@樣:“這個東西可以做這個事情。如果你想的話,你可以覆蓋它。性能不是問題。你也可以提供完全由自己實現的字符到字形的轉換,去做吧…”

TextKit:性能看來真是個問題。理念(起碼現在)更多的是像這樣:“我們用簡單但是高性能的方法實現了這個功能。這是結果,但是我們給你一個機會去更改它的一些東西。但是你只能在不太損害性能的地方進行修改?!?/p>

理念的東西就講這么多,現在讓我們來搞些實際的東西。例如,調整行高如何?聽起來不可思議,但是在之前的 iOS 發(fā)布版上調整行高需要使用黑科技或者私有 API。幸運的是,現在(再一次)不用那么費腦子了。設置 Layout Manager 的代理并實現僅僅一個方法即可:

- (CGFloat)      layoutManager:(NSLayoutManager *)layoutManager
  lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex
  withProposedLineFragmentRect:(CGRect)rect
{
    return floorf(glyphIndex / 100);
}

在以上的代碼中,我修改了行間距,讓它與文本長度同時增長。這導致頂部的行比底部的行排列得更緊密。我承認這沒什么實際的用處,但是它是可以做到的(而且肯定會有更實用的用例的)。

好,來一個更現實的場景。假設你的文本中有鏈接,你不希望這些鏈接被斷行分割。如果可能的話,一個 URL 應該始終顯示為一個整體,一個單一的文本片段。沒有什么比這更簡單的了。

首先,就像前面討論過的那樣,我們使用自定義的 Text Storage。但是,它尋找鏈接并將其標記,而不是檢測 iWords,如下:

static NSDataDetector *linkDetector;
linkDetector = linkDetector ?: [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink error:NULL];

NSRange paragaphRange = [self.string paragraphRangeForRange: NSMakeRange(range.location, str.length)];
[self removeAttribute:NSLinkAttributeName range:paragaphRange];

[linkDetector enumerateMatchesInString:self.string
                               options:0
                                 range:paragaphRange
                            usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
{
    [self addAttribute:NSLinkAttributeName value:result.URL range:result.range];
}];

有了這個,改變斷行行為就只需要實現一個 Layout Manager 的代理方法:

- (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex
{
    NSRange range;
    NSURL *linkURL = [layoutManager.textStorage attribute:NSLinkAttributeName
                                                  atIndex:charIndex
                                           effectiveRange:&range];

    return !(linkURL && charIndex > range.location && charIndex <= NSMaxRange(range));   

想要一個可運行的例子的話,請在前面提到的 TextKitDemo 中查看“Layout”標簽頁。以下是截屏:

http://wiki.jikexueyuan.com/project/objc/images/5-7.png" alt="" />

順便說一句,上面截屏里面的綠色輪廓線是無法用 TextKit 實現的。在這個演示程序中,我用了個小技巧來在 Layout Manager 的子類中給文本畫輪廓線。以特定的方法來擴展 TextKit 的繪制功能也不是件難事,你一定要看看!

演示程序4:文本交互

前面已經涉及到了 NSTextStorageNSLayoutManager,最后一個演示程序將涉及 NSTextContainer。這個類并不復雜,而且它除了指定文本可不可以放置在某個地方外,什么都沒做。

不要將文本放置在某些區(qū)域,這是很常見的需求,例如,在雜志應用中。對于這種情況,iOS 上的 NSTextContainer 提供了一個 Mac 開發(fā)者夢寐以求的屬性:exclusionPaths,它允許開發(fā)者設置一個 NSBezierPath 數組來指定不可填充文本的區(qū)域。要了解這到底是什么東西,看一眼下面的截屏:

http://wiki.jikexueyuan.com/project/objc/images/5-8.png" alt="" />

正如你所看到的,所有的文本都放置在藍色橢圓外面。在 Text View 里面實現這個行為很簡單,但是有個小麻煩:Bezier Path 的坐標必須使用容器的坐標系。以下是轉換方法:

- (void)updateExclusionPaths 
{
CGRect ovalFrame = [self.textView convertRect:self.circleView.bounds 
                                                   fromView:self.circleView];

    ovalFrame.origin.x -= self.textView.textContainerInset.left;
    ovalFrame.origin.y -= self.textView.textContainerInset.top;

    UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:ovalFrame];
    self.textView.textContainer.exclusionPaths = @[ovalPath];
}

在這個例子中,我使用了一個用戶可移動的視圖,它可以被自由移動,而文本會實時地圍繞著它重新排版。我們首先將它的 bounds(self.circleView.bounds)轉換到 Text View 的坐標系統(tǒng)。

因為沒有 inset,文本會過于靠近視圖邊界,所以 UITextView 會在離邊界還有幾個點的距離的地方插入它的文本容器。因此,要得到以容器坐標表示的路徑,必須從 origin 中減去這個插入點的坐標。

在此之后,只需將 Bezier Path 設置給 Text Container 即可將對應的區(qū)域排除掉。其它的過程對你來說是透明的,TextKit 會自動處理。

想要一個可運行的例子的話,請在前面提到的 TextKitDemo 中查看“Interaction”標簽頁。作為一個小噱頭,它也包含了一個跟隨當前文本選擇的視圖。因為,你也知道,沒有一個小小的丑陋的煩人的回形針擋住你的話,那還是一個好的文本編輯器演示程序嗎?


  1. Pages 確實——據 Apple 聲稱——絕對沒有使用私有 API。 我的理論:它要么使用了一個 TextKit 的史前版本,要么復制了 UIKit 一半的私有源程序?;蛘邇烧叩幕旌稀?#160;

  2. 字形(Glyphs):如果說字符是一個字母的“語義”表達,字形則是它的可視化表達。取決于所使用的字體,字形要么是貝塞爾路徑,或者位圖圖像,它定義了要繪制出來的形狀。也請參考卓越的 Wikipedia 上關于字形的[這篇文章][12]。 

  3. 在一個類簇中,只有一個抽象的父類是公共的。分配一個實例實際上就是創(chuàng)建其中一個私有類的對象。因此,你總是為一個抽象類創(chuàng)建子類,并且需要實現所有的方法。也請參考 [class cluster documentation][13]。 

上一篇:Metal下一篇:字符串渲染