作者:Damien – TikTok iOS 工程师 – 字节跳动
审阅:折腾范儿_唯敬,iOS/前端 开发者,上任于阿里巴巴,喜爱研究跨平台动态化混合前端相关的内容,现在从事移动应用客户端/前端相关开发作业。
引言
Session:developer.apple.com/videos/play…
经过本文你将会了解到 TextKit 1 到 TextKit 2 的改变内容,而且能够深化了解 TextKit 2 的内部的作业原理和机制。
什么是TextKit
TextKit 是 Apple 在 iOS 7 中推出的文本处理工具包。用于驱动处理文本的核算、布局、烘托等文本相关的中心功用,它经过很少的几个类协助开发者完结在之前必须运用 CoreText 经过复杂核算才干完结的任务,提高了 iOS 开发者关于文字展示和交互的开发效率,实际上它也是依据 CoreText 封装的高层次 API。然而在早在 20 多年前,TextKit 就现已初次出现在 OpenStep 的体系上。多年来,它从 macOS 10 就开端与咱们一同成长和开展。从 iOS 第一个版别到 iOS 7 以及今日的 macOS 11 和 iOS 14,TextKit 在底层默默地为一切 Apple 的设备供给了最根本的文本功用服务着。
以上介绍的 TextKit 咱们能够称之为 TextKit 1,几十年以来,规划和技能工程原理发生了很大改变,TextKit 1 最原始的规划准则,也现已有些过期了,因而供给与新技能完美集成的一起仍能供给高标准高功能的 API 变得更具挑战性。这也是为什么推出下一代 TextKit 的原因,TextKit 2 是 Apple 的下一代文本引擎,建立在一系列前瞻性规划准则之上。让咱们一同来了解一下吧。
TextKit 2
其实在 MacOS Big Sur 中,Apple 更新了操作体系中许多文本组件底层完结,在幕后现已运用了 TextKit 2。老生常谈,让咱们来先看下 TextKit 2 的架构:
TextKit 1 和 TextKit 2 在体系中是共同存在的。就像它的前身一样,TextKit 2 建立在 Foundation、Quartz 和 Core Text 之上。UIKit 和 AppKit 中的文本控件建立在 TextKit 2 之上。
当然 TextKit 2 还保留了其前身的 MVC 规划。Model 和 Controller 是咱们这次最大的改变部分,其中增加许多的新的朋友,当然也不要害怕,咱们后文中会一一介绍。
那么先让咱们从规划准则开端逐渐深化咱们今日的内容。TextKit 2 的中心规划准则是
- 准确性
- 安全性
- 高功能
一切三个准则都很重要,这是构建 TextKit 2 的最中心的思维,Apple 期望能够尽或许的去完美的完结这些特性,然而,现实中也做一些平衡。首要我会简略的介绍下各个中心准则的内容,以便让你对今日的内容有一个开端的概念:
- 为了准确性,TextKit 2 笼统了字形处理。
- 为了安全性,TextKit 2 愈加重视值语义。
- 为了高功能,TextKit 2 运用依据视口的布局和烘托。
没错,这个便是咱们今日介绍的中心内容,接下去,让咱们一个个来深化了解。
准确性
首要咱们将会从准确性开端介绍,在准确性方面,Apple 笼统了字形处理,为世界文本供给共同的体验。Apple 设备在被世界各地的用户运用着,因而为一切言语的文本供给正确的布局、烘托和交互是十分重要的。咱们期望每个人都能够在他们的设备上阅览文本并与之交互。然而在 TextKit 1 的一些 API 的规划使得很难以正确的办法处理世界化文本。
在开端本章节之前,咱们能够先了解下排版体系中关于文本显现部分的重要概念,也能够对排版体系做一个根本的入门。
字符(Characters)与字形(Glyphs)
排版体系中文本显现的一个重要的过程便是字符到字形的转化。
字符(Characters):字符是信息自身的元素,而字形是字符的图形表征,字符还会有其它表征比方发音。字符在核算机中其实便是一个编码,某个字符集中的编码,比方 Unicode 字符集,就包括了大都数存在的字符。
字形(Glyphs):字形是图形,一般都存储在字体文件中,字形也有它的编码,也便是它在字体中的索引。一个字符能够对应多个字形(不同的字体,或者同种字体的不同款式:粗体斜体等)多个字符也或许对应一个字形。
许多西方言语中,一个字形 Glyphs 一般代表一个字符。
但这并不总是正确的。你能够有多个字形代表一个字符,也能够相反。一个字形能够代表多个字符。这种用于表示多个字符的单个字形称为连字。
在西方言语中没有太多的连字,它们一般不会影响文本的易读性。你能够在没有连字的情况下阅览它。但并非一切言语都如此。像阿拉伯语和梵文这样的言语运用了许多连字,连字则会影响可读性。如阿拉伯文字的这个词。这是一个乌尔都语单词,意思是”时间”。右侧用连字制作的完整单词与左侧的单个字符看起来十分不同。
在 TextKit 1 中的许多 API 需求运用字形索引或规模。例如要获取某些文本的边界矩形,你需求知道你想要的文本的字形规模,在西方言语中,索引的获取是相对简单且正确的,然而在连字的情况下,获取正确的索引则不是一个简略的作业。
因为许多 TextKit 1 API 需求字形规模,因而运用这些 API 或许会损坏复杂言语的布局和烘托的准确性。这便是 TextKit 2 笼统字形处理的原因。TextKit 2 运用 CoreText 烘托一切文本,在 TextKit 2 中,你不需求办理复杂字形。相反,你能够运用更高级别的笼统目标来控制文本布局和交互。
阅览参考资料: devma.cn/blog/2016/0…
/post/684490…
NSTextSelection & NSTextSelectionNavigation
它包括文本挑选的一切必要上下文信息,例如其词语粒度、上下文关联性以及构成挑选的文本规模。NSTextSelection
上的这些特点都是只读的,因而你不会修正挑选目标的实例然后错误的更改它们。
假如需求改动挑选的文本的信息,那么能够直接运用 NSTextSelectionNavigation
目标来查询和更新挑选的文本规模。
NSTextLocation & NSTextRange
它们与 UIKit 中的 UITextPosition 和 UITextRange 类十分相似,仅仅你不在需求对它们进行子类化。 大多数情况下,你能够在 TextKit 2 中运用默许方位和规模目标。运用目标而不是数字而是更具表现力的文档模型,因为规模是依据互相相对的方位界说的。如下的 HTML 文档目标模型便是一个很好的例子。
这便是 TextKit 2 中对准确性所做的一些改动。
安全性
接下来是安全性方面的介绍。在这方面,TextKit 2 愈加强调值语义,以更好地与 Swift 和 SwiftUI 等技能的保持共同。什么是值语义呢?值类型保留数据的仅有副本,以避免该数据发生改变,这能够避免意外共享相关内存然后引发未感知的改变,然后让咱们的代码更安全、更稳定。但是值类型并不是完结此效果的仅有办法。不可变类也具有初始化后无法更改的特性,这也能够避免了其数据的改变。这些类的行为相似于值类型,因而咱们将它们称为具有值语义。
在 TextKit 2 中的许多类都是以这种办法规划的。为了阐明这种规划的优势,让咱们重新梳理一下 TextKit 1 烘托流程:
简略的阐明:文字更新导致文本存储的改变而且通知布局办理器,然后布局办理器将生成字形、定位它们并将它们直接制作到视图中。运用这种办法直接制作到视图中的办法,很难弄清楚在何处分隔文本以创立自界说制作空间。
让咱们在来看下 TextKit 2 的烘托流程:
新的流程则会是:
文本的更新经过一个称为 NSTextContentManager
的新目标。NSTextContentManager
目标将文本分成 NSTextElement
。当需求进行布局时,NSTextLayoutManager
会向 NSTextContentManager
获取 NSTextElement
。然后 NSTextLayoutManager
将 NSTextElement
布局到文本容器中,并生成包括布局和定位信息的 NSTextLayoutFragment
。当需求显现时,NSTextLayoutFragment
会传递给 NSTextViewportLayoutController
控制器,它会在你挑选的烘托目标(无论是 View 还是 Layer)中处理这些片段的定位和布局。此过程涉及许多新目标。这也是强调值语义的当地,咱们能够经过在正确的机遇( delegate 办法)进行更改,创立新的目标实例并将它们回来给体系,体系运用替换目标来进行布局和显现。
NSTextElement
NSTextElement
是文档的构建块,每个 NSTextElement
代表内容的一部分,并包括描绘它在文档中的方位的规模。NSTextElement
具有值语义。它们的特点(包括规模)是不可变的,而且在创立后无法更改。将文档建模为一系列 NSTextElement
而不是字符给了咱们更高的自由度。咱们能够轻松区分给定元素代表的内容类型,是一段文本、附件还是其他一些自界说类型以便咱们能够依据元素的类型决定怎么安置元素。
NSTextContentManager
NSTextContentManager
知道怎么从文本内容生成 NSTextElement
并在整个文档中而且追寻这些元素的运用。NSTextContentManager
供给了将原始数据转化为 NSTextElement
的接口。
NSTextContentManager
和 NSTextElement
都是笼统类型,假如需求运用自界说文档模型能够对它们进行子类化操作。但大多数情况下,你能够直接运用 TextKit 2 供给的默许设置。
NSTextContentStorage & NSTextParagraph
NSTextContentStorage
是一个运用 NSTextStorage
作为存储的内容办理器。
它知道怎么将文本存储的内容划分为阶段元素,这些元素也便是 NSTextParagraph
的实例。
NSTextContentStorage
还知道怎么在文本发生改变时生成更新的阶段元素。
在对文本进行更改时,应该将更新包装在 performEditingTransaction 办法中。 这可确保 TextKit 2 体系的其他部分感知到改变。
NSTextLayoutManager
NSTextLayoutManager
与 TextKit 1 中旧的 NSLayoutManager
相似,但 NSTextLayoutManager
不处理字形。而且 NSTextLayoutManager
能够获取文本元素,并将它们安置到文本容器中,并为这些元素生成布局片段信息 NSTextLayoutFragment
。
NSTextLayoutFragment
NSTextLayoutFragment
包括一个或多个文本元素的布局信息。它们运用值语义而且它们的特点是不可变的。NSTextLayoutFragment
经过三个特点传达布局信息:textLineFragments 数组、layoutFragmentFrame 和 renderingSurfaceBounds 办法。假如你想自界说或更改布局,能够从这几个办法下手。
如下的代码示例,咱们能够增加在文字布局外层增加一个气泡的布景:
class BubbleLayoutFragment: NSTextLayoutFragment {
// 回来烘托布局规模
override var renderingSurfaceBounds: CGRect {
return bubbleRect.union(super.renderingSurfaceBounds)
}
// 气泡制作的完结
override func draw(at renderingOrigin: CGPoint, in ctx: CGContext) {
ctx.saveGState()
let bubblePath = createBubblePath(with: ctx)
ctx.addPath(bubblePath)
ctx.setFillColor(bubbleColor.cgColor)
ctx.fillPath()
ctx.restoreGState()
super.draw(at: renderingOrigin, in: ctx)
}
}
那么咱们能够从哪个机遇给体系咱们自界说的 NSTextLayoutFragment
呢?答案是在 NSTextLayoutManagerDelegate
协议中。
代码示例:
func textLayoutManager(_ textLayoutManager: NSTextLayoutManager,
textLayoutFragmentFor location: NSTextLocation,
in textElement: NSTextElement) -> NSTextLayoutFragment {
// Something Code
return NSTextLayoutFragment(textElement: textElement, range: textElement.elementRange)
}
}
以上的部分便是 TextKit 2 在安全性方面的作业。
高功能
高功能是任何文本引擎面对的最大挑战之一。TextKit 2 在日常日子的场景中速度十分快,从快速烘托只有几行的标签到以交互翻滚数百兆字节的文档布局中都能够体现。关于这些场景,当你用可变速率翻滚阅览这些十分大的文档时,不接连的文本布局关于功能来说肯定是必不可少的。
接连和非接连布局之间的区别是什么呢?用2张图来阐明一下
在可视的规模之外,布局仍然存在,这个叫做接连的布局。
只在可视的规模之内的布局,打破文档布局结构,这个叫做不接连的布局。
在 TextKit 2 中的布局总是不接连的。仅对屏幕上可见的文本部分履行布局以及额外的过度翻滚区域来提高功能,然后发生更流畅的翻滚体验。相比之下,非接连布局在 TextKit 1 中是可选的。运用 TextKit 1,你只能翻开或封闭非接连布局。在 TextKit 2 中你能够在视口布局之前、期间和之后收到回调做出对文字部分排版和布局。为了获得最佳功能,你的代码应专心于处理视口区域内的布局信息,尽或许去避免为视口外的元素请求布局信息。
NSTextViewportLayoutController
NSTextViewportLayoutController
是可见规模布局的控制器目标,NSTextViewportLayoutController
在视口布局过程中对其 Delegate 调用三个重要办法:TextViewportLayoutControllerWillLayout
、textViewportControllerConfigureRenderingSurface
和 textViewportLayoutControllerDidLayout
。
你能够在 TextViewportLayoutControllerWillLayout
履行任何准备作业以准备布局,例如铲除视图或图层的内容。
在处理布局中,NSTextViewportLayoutController
为视口中可见的每个布局片段调用 textViewportControllerConfigureRenderingSurface
办法来达到烘托的目的。最终,NSTextViewportLayoutController
在完结对视口中可见的一切布局片段的布局后调用 didLayout 办法,能够在此处履行任何所需的更新。
这便是 TextKit 2 对保持高功能所做的尽力。
体系组件的兼容
TextKit 2 一切新类都能够在 iOS 15 的 UIKit 和 macOS 12 的 AppKit 中运用,假如你想,今日就能够开端运用 TextKit 2 编写新代码。因为 TextKit 1 是内置文本控件不可或缺的一部分,所以体系组件也并非彻底切换到 TextKit 2。下面是一些运用的注意事项:
- 关于 AppKit 开发人员,NSTextView 不会主动运用 TextKit 2。假如要将 TextKit 2 与 NSTextView 一同运用,则需求在创立时动态参加支撑。
- 关于 NSTextField 的组件则默许运用 TextKit 2。
- 关于 UIKit 来说,UITextField 在 iOS 15 中主动运用 TextKit 2。
- 带有 TextKit 2 的 UITextView 在 iOS 15 中则不可用。
更多具体的能够检查 Apple 的开发者文档:developer.apple.com/documentati…
总结
TextKit 2 在今年给咱们带来了大量更新。经过笼统字形的处理,让咱们在处理多言语的情况下的得心应手避免了准确性的问题。经过对文档模型的笼统和核算布局流程的更新,赋予咱们在整个文本烘托的流程中有更高的定制性,高效的完结咱们对文本排版的自界说要求。高功能的秘密则是在只针对视界规模内的区域高效率的制作,避免了视界外区域的排版和烘托的功能损耗,这也是让 TextKit 2 高效的原因。最终期望各位开发者能够利用 TextKit 2 的新特性,创造更多的构思和完结更酷炫效果。
在 Session 视频中带了一个 TextKit 2 的 Demo App,感兴趣的同学能够下载体验一下:docs-assets.developer.apple.com/published/f…
重视咱们
咱们是「老司机技能周报」,一个持续寻求精品 iOS 内容的技能公众号。欢迎重视。
重视有礼,重视【老司机技能周报】,回复「2021」,领取 2017/2018/2019/2020 内参
支撑作者
在这里给大家推荐一下 《WWDC21 内参》 这个专栏,一共有 102 篇关于 WWDC21 的内容,本文的内容也来源于此。假如对其余内容感兴趣,欢迎戳链接阅览更多 ~
WWDC 内参 系列是由老司机牵头组织的精品原创内容系列。 现已做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经历、苹果文档和视频内容做二次创作。