UITextView 回退到 TextKit 1

Jul 27, 2025 • 预计阅读时间 1 分钟

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 就可以了。

iOSTextKit
版权声明:如果转发请带上本文链接和注明来源。

lvv.me

iOS/macOS Developer

在 FreeBSD 上加载自定义的 sshd_conf 配置

Firefox 禁用磁盘缓存