即使你按照官方的文档来操作依然有可能会掉进坑里,因为 swift 的实现和 objc 稍有不同。
NSTextStorage 是基于 NSMutableAttributedString 的一个虚基类。
这意味着当你写一个 NSTextStorage 的子类时,必须正确实现以下 4 个接口
override var string: String {
return innerStorage.string
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
return innerStorage.attributes(at: location, effectiveRange: range)
}
下面这个方法需要注意,不能使用 str.count - range.length ,必须要转为 NSString 用 length 的长度,否则在处理 emoji 表情字符的时候会引发死循环。
override func replaceCharacters(in range: NSRange, with str: String) {
innerStorage.replaceCharacters(in: range, with: str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
}
override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
innerStorage.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
}
以上代码里的 innerStorage
是私有变量,用来存储字符串和对应的属性。
它的定义如下:
private var innerStorage = NSTextStorage()
需要注意的是,不要使用 NSMutableAttributedString
,因为 swift 目前有 bug, NSMutableAttributedString
会导致内存暴涨。
正确的用法是使用一个 NSTextStorage
实例作为内置存储容器。
最后,完整的子类代码如下:
import UIKit
class NDTextStorage: NSTextStorage {
private var innerStorage = NSTextStorage()
override var string: String {
return innerStorage.string
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
return innerStorage.attributes(at: location, effectiveRange: range)
}
override func replaceCharacters(in range: NSRange, with str: String) {
innerStorage.replaceCharacters(in: range, with: str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
}
override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
innerStorage.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
}
}