在 MacCatalyst 上使用 UITextView 实现富文本编辑器
起因
使用 UITextView
实现富文本编辑器,给一行文本中局部添加不同样式,再继续输入时,局部样式会被自动覆盖。效果如下:
检查 textStorage
后发现原有样式被覆盖,怀疑输入文字后,样式在某个时刻发生了改变。
寻找原因
自定义一个 testStorage
,添加断点查看变更时机。代码如下:
class TextStorage: NSTextStorage {
private let store = NSMutableAttributedString()
override var string: String { store.string }
override var mutableString: NSMutableString {
store.mutableString
}
override func replaceCharacters(in range: NSRange, with str: String) {
beginEditing()
store.replaceCharacters(in: range, with: str)
edited(.editedCharacters, range: range, changeInLength: str.count - range.length)
endEditing()
}
override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
beginEditing()
store.setAttributes(attrs, range: range)
edited([.editedAttributes], range: range, changeInLength: 0)
endEditing()
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
store.attributes(at: location, effectiveRange: range)
}
}
private let textView: UITextView = {
// 创建自定义的 NSTextStorage
let customTextStorage = TextStorage()
// 创建 NSLayoutManager 和 NSTextContainer
let layoutManager = NSLayoutManager()
customTextStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: .zero)
layoutManager.addTextContainer(textContainer)
return UITextView(frame: .zero, textContainer: textContainer)
}()
// 初始样式设置
textView.text = "sjldkfsldfkljl"
textView.textStorage.addAttribute(.backgroundColor, value: UIColor.purple, range: NSRange(location: 0, length: textView.text.count))
textView.textStorage.addAttribute(.backgroundColor, value: UIColor.red, range: NSRange(location: 0, length: textView.text.count - 1))
运行后,输入 o
,会先执行 replaceCharacters
方法,再执行 setAttributes
方法,看起来一切正常。
继续执行后,又执行了 replaceCharacters
方法和 setAttributes
方法,而且替换了整个文本?
第一次执行时能看到是键盘插入了文本,应该是正常运行的。
第二次是 _performTextCheckingAnnotationOperations
方法,怀疑是哪个属性需要调整,
到 UITextView
的定义中查找,没有找到
可能是在协议中定义的,顺着继承链往下找,在 UITextInputTraits
协议中找到了一个可能的属性,查看其默认值为 yes
,遂将其改为 no
试试
textView.spellCheckingType = .no
修改之后重新运行,发现恢复正常了?
总结
使用 UITextView
在 iOS
中运行时正常;使用 AppKit
中的 NSTextView
时,也正常。但使用 UITextView
在 Mac Catalyst
环境运行时,却出现问题,一度怀疑是苹果的 BUG,没想到是特性。
结论
关闭拼写检查textView.spellCheckingType = .no
后,运行正常。
textView.spellCheckingType = .no
textView.text = "sjldkfsldfkljl"
textView.textStorage.addAttribute(.backgroundColor, value: UIColor.purple, range: NSRange(location: 0, length: textView.text.count))
textView.textStorage.addAttribute(.backgroundColor, value: UIColor.red, range: NSRange(location: 0, length: textView.text.count - 1))