iOS 16 开始把 TextKit 2 作为默认的文本渲染引擎,但是一直都有很多小问题,直到 iOS 17 中的 TextKit 2.1 才达到稳定状态,但是在 iOS 18 中苹果又对 TextKit 2 进行了大版本更新,最终对开发者的影响就是如果使用 TextKit 2 而且要兼容 iOS 16 的用户,那么必须自己处理一些列的兼容性问题,这对开发者来说显然得不偿失(即使你只想使用 UITextView 为产品提供简单的编辑功能)。
所以,目前的最佳实践是:让 UITextView 回退到 TextKit 1 ,由于苹果不再维护 TextKit 1 而且并没有将相关的方法标记为废弃,所以使用 TextKit 1 反而是比较稳妥的方案。
让 UITextView 使用 TextKit 1 非常简单,只需要在传入的 textContainer 中使用 NSLayoutManager 就可以了。
如果想要项目中的所有 UITextView 都是用 TextKit 1,可以自定义一个 UITextView 子类,在内部的初始化方法里提供使用 NSLayoutManager 的上 textContainer 。
一个简单的例子:
import ObjectiveC
import UIKit
@objc
public class MyTextView: UITextView {
private static var textStorageKey: UInt8 = 0
class func legacyTextContainer() -> NSTextContainer {
let textContainer = NSTextContainer()
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = NSTextStorage()
textStorage.addLayoutManager(layoutManager)
// Avoid causing the layoutManager and textStorage to be automatically released by ARC when the method returns.
objc_setAssociatedObject(textContainer, &textStorageKey, textStorage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return textContainer
}
public override init(frame: CGRect, textContainer: NSTextContainer?) {
var innerTextContainer = textContainer
if innerTextContainer == nil {
innerTextContainer = Self.legacyTextContainer()
}
super.init(frame: frame, textContainer: innerTextContainer)
}
public convenience init(frame: CGRect) {
self.init(frame: frame, textContainer: Self.legacyTextContainer())
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
把项目中的 UITextView 都替换成 MyTextView 就可以了。