探究 Text Kit 和 Core Text 的前世今生 (Text Kit 篇)

Binboy_王興彬發表於2016-11-23

上手實踐 Text Kit 的應用

前兩天參加 iDev 大會的間隙,嘗試著寫了一個“字裡行間”的 Demo,用 Collection View 將基本 UI 介面搭好後,其中的難點便是寫文章時對於富文字的編輯了。

探究 Text Kit 和 Core Text 的前世今生 (Text Kit 篇)
「字裡行間」小 Demo

那麼,據說 Text Kit,這個蘋果在釋出 iOS 7 時帶給開發者的利器可以實現這一點。

以下,便是我們要實現的效果了。或者也可以下載“字裡行間”體驗一下,相信你會喜歡上它的。

探究 Text Kit 和 Core Text 的前世今生 (Text Kit 篇)
編輯富文字

先來看看 Text Kit 的架構

探究 Text Kit 和 Core Text 的前世今生 (Text Kit 篇)
Text Kit 架構

  • NSTextStorage

    繼承自 NSAttributedString,用來儲存並管理 字串 以及 文字屬性,並在這些資訊發生修改時通知 NSLayoutManager。可以理解為觀察者模式中,被觀察的 Model。

  • NSLayoutManager

    中間元件,負責監聽NSTextStorage文字屬性修改發出的通知,並應用 Core Text 啟動佈局程式,並向 NSTextContainer 獲取可用空間進行填充渲染。可以理解為 MVC 中的 Controller

  • NSTextContainer

    控制文字在 UITextView 中文字的可繪製區域。

  • UITextView

    實現 UITextInput 協議,處理使用者互動,並將文字修改資訊轉發給 NSTextStorage 進行實際的更改。

開始寫程式碼吧

自定義一個繼承 NSTextStorage 的類

class ZiInteractiveTextStorage: NSTextStorage {
    let backingStore = NSMutableAttributedString()

    override var string: String {
        return backingStore.string
    }

    override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [String : Any] {
        return backingStore.attributes(at: location, effectiveRange: range)
    }

    override func replaceCharacters(in range: NSRange, with str: String) {
        print("replaceCharactersInRange\(range) withString:\(str)")

        beginEditing()
        backingStore.replaceCharacters(in: range, with: str)
        edited([.editedCharacters, .editedAttributes], range: range, changeInLength: (str as NSString).length - range.length)
        endEditing()
    }

    override func setAttributes(_ attrs: [String : Any]?, range: NSRange) {
        print("setAttributes:\(attrs) range:\(range)")

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

    public func performReplacemetsFor(changedRange: NSRange) {
        let extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRange(for: NSMakeRange(NSMaxRange(changedRange), 0)))
        applyStyleTo(extendedRange) // 自定義富文字格式的修改規則
    }

    override func processEditing() {
        performReplacemetsFor(changedRange: editedRange)
        super.processEditing()
    }
}複製程式碼

在控制器中建立使用自定義 NSTextStorageUITextView

class EditCreationViewController: UIViewController {
    var textView: UITextView!
    var textStorage: ZiInteractiveTextStorage!

    func createTextView() {

        let attrString = template.content.utf8Data?.attributedString

        textStorage = ZiInteractiveTextStorage()
        textStorage.append(attrString!)

        let newTextViewRect = view.bounds

        let layoutManager = NSLayoutManager()

        let containerSize = CGSize(width: newTextViewRect.width, height: CGFloat.greatestFiniteMagnitude)
        let container = NSTextContainer(size: containerSize)
        container.widthTracksTextView = true
        layoutManager.addTextContainer(container)
        textStorage.addLayoutManager(layoutManager)

        textView = UITextView(frame: newTextViewRect, textContainer: container)
        textView.delegate = self
        view.addSubview(textView)
     }
}複製程式碼

總結

這樣,就可以實現了輸入時按富文字格式的規則動態佈局文字樣式,至於如何使其更加美觀,那就需要更多地和設計一起慢慢打磨各處的細節了。

下一篇,我們再來看看 iOS 7.0 之前沒有 Text Kit 時,是如何應用 Core Text 進行排版佈局,也就是 Text Kit 中 NSLayoutManager 為我們做了些什麼。

更多參考

Getting to Know TextKit (Objective-C)

Text Kit Tutorial: Getting Started (Swift)

相關文章