-
TextKit 最佳做法
利用 TextKit 的各项功能,为用户带来显示及编辑文本的最佳体验。有效使用 TextKit,让您的 app 发挥出最佳性能。了解相关概念,以完成更复杂的处理、布局及呈现。
资源
- TextEdit Sample Code
- Attributed String Programming Guide
- Ruler and Paragraph Style Programming Topics
- Text Attribute Programming Topics
- Text System User Interface Layer Programming Guide
- Text Layout Programming Guide
- Text System Storage Layer Overview
- Cocoa Text Architecture Guide
- Text Programming Guide for iOS
- 演示幻灯片 (PDF)
相关视频
WWDC20
WWDC18
-
搜索此视频…
大家好 欢迎来到 221 号讨论会 “TextKit Best Practices(最佳实践)” 我是 Donna Tom 一名 TextKit 工程师 我的同事 Emily Van Haren 今天也会和我一起介绍 她来自创作工具组 我们非常激动能够 和大家分享 运用 TextKit 的一些最佳实践 让我们开始吧 首先 我们将回顾一下 运用 TextKit 的一些关键概念 然后 我们会通过一些 例子来说明如何 把这些关键概念运用到 你的 App 中
最后 我们将以一些 在正确性 性能 以及安全领域的最佳实践结束
现在让我们从关键概念开始吧 为了确保我们进度相同 我们将从最基础的内容开始
什么是 TextKit 你的第一反应可能是 去在 Xcode 中打开一个 新的 Playground 并且输入 “import TextKit” 除非你已经试过这样做 并且发现没有用 这是因为 TextKit 与其他你可能已经用过的框架相比 有些不同 你不必导入任何东西 就可以使用它
UIKit 和 AppKit 中的 文本控件都是建立在 TextKit 上面的 所以 如果你曾使用过 label textField 或者 textView 你其实已经使用过 TextKit 了
TextKit 还集成了 强大的底层技术 比如 Core Text Core Graphics 和 Foundation 来让你的 App 能够轻松且完美地显示文本
每次你使用这些 内置控件之一的时候 你都是在 使用 TextKit 的功能去 显示或编辑文本 以一种完全 国际化 可本地化 的方式 并且没有 直接运用这些底层 技术或者理解 复杂的脚本 还有很多 你还能顺带获得很多额外的东西 比如你在这里看到的 这些显示特性 至于编辑 你也会 获得所有 OS 所支持的 技术服务 比如辅助功能 拼写检查等等 你可以充分利用 所有这些出色的功能 而无需编写 一行代码 这真的很棒
有了这么多触手可及的 功能供你选择 你怎么决定 使用哪一个控件呢 那我们就来讨论一下 如何为你的情况选择 合适的控件 你可能会有 不同的选择 取决于 你是在使用 UIKit 还是 AppKit 所以让我们分别讨论一下 好的 让我们从 UIKit 开始 首先 你会考虑你是否需要文本输入 如果你不需要文本输入 那接下来你考虑你是否 需要选择或滚动 如果你不需要这些 那你应该使用 UILabel UILabels 适用于 少量的文本 比如几个字 或者几行字
如果你有比这更多的文本 或者你需要 选择或者滚动的功能 那你需要使用 禁用编辑的 UITextView 现在我们回到顶端 如果你需要文本输入 那么 考虑你是否需要安全的 文本输入 这会像密码栏一样 其中文本被隐藏起来 复制被禁用 如果你需要这样 你就应该使用 UITextField 因为它是 唯一支持安全文本 输入的控件 否则 想想你 想输入的文本有多少
如果你想要像表单栏输入那样 只需要一行的 那就使用 UITextField UITextField 仅支持 一行文本输入 如果你需要比这更多的文本 你可以使用 UITextView 对于 AppKit 来说 同样需要这样的决策过程 这个过程和 UIKit 的很像 但是有些小小的不同 你也要从考虑是否 需要文本输入开始 AppKit 没有标签控件 所以 如果你需要展示文本
那就使用 NSTextField 你可以禁用编辑和选择 来获得和标签一样的效果 现在回到顶端 如果你需要文本输入 问问自己是否需要 安全文本输入 如果需要 那你可以使用 NSSecureTextField 否则 我们会问 我们最喜欢的问题 你想要多少文本 NSTextView 对展示 大量文本进行了优化 所以 如果你有大量文本要处理 你应该使用 NSTextView 否则 你可以使用 NSTextField 与 UIKit 不同 NSTextField 支持多行文字 但是它更适用于 短一些的字符串 因此如果你有大量的文本的话 仍然应该使用 NSTextView 好 你们中的和 TextKit 打过几次交道的人 可能已经注意到 这个流程图缺少了一个选项 那就是字符串绘制优化
你可以通过在你的 NSString 或者 NSAttributedString 下 直接调用 draw(at: CGPoint) 或 draw(in: CGRect) 来进行字符串绘制 你们中的一些人可能会为了 避免在 Kit 层级上使用过多的 视图对象所带来的性能收益 来使用它
因此 如果你想要用这个方法 请务必记住下面的几点提示 你应该用它来处理 少量的静态文本 并且你应该限制 调用 draw() 方法的频率 如果你频繁地调用字符串 绘制方法 你获得的性能表现可能不如 使用标签或文本栏 因为这些控件能提供 更好的缓存 特别是 使用自动布局的情况下 如果你正在绘制一个 有很多自定义属性的 属性文本字符串 这也会 减慢你的字符串绘制 因为文本系统 需要在渲染之前 验证所有的属性 因此 为了最佳性能表现 你应该在绘制之前 剥离额外的属性 只保留那些 需要去决定的视觉表现属性 比如字体或颜色 最后你要知道 如果使用字符串绘制 你会失去所有这些 文本控件提供的额外功能 因此只要可能的话 你应该一直使用文本控件 所以现在你知道 仅仅通过 TextKit 的内置控件 你可以做些什么了 但是如果你需要的 比这些控件所提供的更多 你需要文本堆栈中 找到正确的定制点
与 Cocoa 很像 TextKit 是基于 MVC 设计模式的 文本系统可以 被分为三个阶段 直接与 MVC 对应 分别是存储 显示和布局 我们现在来仔细看看 构成了各个阶段 的 TextKit 对象 我们从存储开始 它对应的是 Model(模型) NSTextStorage 存储着你的 字符串数据和属性 它是 NSMutableAttributedString 的 一个子类 因此你可以用 你已经熟悉的 使用 attributedString 的方式 来使用它 我的同事 Emily 将会在稍后 展示一些强大的方法 来自定义 NSTextStorage 期待一下吧
NSTextContainer 会为 你的文本将要被展示的几何区域 建立模型 默认情况下 它是一个矩形 但你也可以自定义 文本布局的走向或者形状 就像这里展示的一样 要了解更多详细的 关于存储对象的信息 看看这些以往优秀的 WWDC 讨论会内容和文件 这些能在讨论会结束时的 更多信息链接中找到 接下来是显示阶段 它对应着视图 我们已经讨论了 一些显示阶段的内容 在我们讨论选择 正确控件的时候 因此为获得更多的信息 你可以再一次查看 这些文件和资源 这些也可以 在讨论会的结尾的 更多信息链接中获得 最后 我们讨论下布局阶段 它对应着控件 NSLayoutManager 是这个阶段中 唯一的组成部分 它是一头野兽
这是个褒义词 因为它的功能实在是太棒了 因此它是整个运作的大脑 它协调所有阶段中的 变化 并且控制着布局过程 所以这里简要介绍一下 布局过程是怎样进行的 文字布局发生在 系统修复文本存储的属性之后 来消除不一致 比如确定字符串中 所有的字符 都使用支持 显示该字符的字体 所以在这个例子中 Times New Roman 被指定到 整个字符串 但是这个字体并不支持 显示日文汉字或者表情符号
因此在属性修正后 你的文本存储 看起来会像这样 日文字符被 分配了适当的日文字体 表情符号被 分配了的表情符号字体 好的 一旦属性被修正了 布局过程就开始了 我们可以认为布局 分为两步 字形生成和字形布局
一旦它们被布局好了 就可以显示了 但是 等一下 什么是字形 让我们回顾一下 字形是一种代表一个 或多个字符的 视觉符号 正如你在这里看到的那样 字符与字形之间的映射 并不总是一对一的
字符串 “ffi” 有 三个字符 但是它可以 由连起来的单个 字形表示 其实反过来也可以 这里有一个 “ñ” 这是一个单独的字符 它可以由多个字形表示 一个 “n” 一个波浪号
回到我们的图中 我们的 NSLayoutManager 执行字形生成和字形布局 在字形生成这一步中 布局管理器(Layout Manager)会 获取字符并确定 需要绘制的字形 在字形布局这一步 布局管理器会定位好这些字形 以在你的视图中显示 从以往 WWDC 讨论会和文档中 还可以学到很多 关于布局管理器的知识 你可以在 没错 讨论会结尾的更多 信息链接中 好的 现在你了解了 文本系统的阶段 并且你知道了 TextKit 构成每个阶段的组件
所以现在让我们来看看 如何为这些组件选择正确的配置 来创造不同的效果 这是你的标准配置 当你从 “界面构建器(界面构建器)” 中拖放文本视图时 你会自动获取 每个组件中的一个 像这里一样 大多数时候 这对你来说就足够了 如果你想要一个多页或一个 多栏布局 你可以 使用多对文本容器和 文本视图 每页或每列一对 你可以将所有这些都绑定到 相同的文本存储中的 相同的布局管理器上 这样它们就能在 后台存储中 共享 布局信息了 如果你想要让每个视图 呈现不同的视图 你也可以做到 只要使用多个布局管理器 就可以了 再次说明 因为文本 共享相同的后台存储 更新该文本将更新 所有视图 我们现在不会讨论过多 关于这些配置的细节 因为之前已经有一个 讨论会介绍过了 你可以去看看 WWDC 2010 讨论会中的 “Advanced Cocoa Text Tips and Tricks” 你可以在讨论会结尾的 更多信息链接中找到它 好的
我们已经看过了内置 文本控件 我们已经看过了 TextKit 的组件 我们已经讲过了如何 配置这些组件 以实现不同的效果 在你了解了这些知识以后 现在你已经可以做很多事情了 但是如果你想要更多 你需要自己扩展 并自定义 TextKit 的某些部分
所以现在我们会谈一谈 选择正确的方式 来做到这一点
选择正确的方式 就像构建文本工具箱一样 这就像因为需要一把锤子 所以你去了商店一样 当你到了商店 你发现有大量的锤子供选择 但你想选择 能够完成这项工作的锤子 最理想的是能满足你需求的 最便宜的锤子 这些是我们可以 得到的锤子 委托就是一种 标准的羊角锤可以用来 完成各种任务 委托们有很多 不同的自定义钩子 大多数时候它 就能帮你完成任务 通知就像是球头锤 它的末端是一个球 而不是羊角 所以它更专业化 适合某些特定的任务 但它并不像委托那样 是一个全能的锤子
大锤非常强大 你可以将它用于 任何你需要锤子的任务 但在很多时候 使用它会有些大材小用 接下来 我想 邀请 Emily 向我们展示如何 使用这些不同种类的锤子 Emily 谢谢你 Donna
因此 作为开发人员 我们有一系列控件 各种配置 以及大量的自定义选项供我们选择 以实现我们 所需的功能 所以我们的工具箱 储备充足 那么我们要 如何选择工具呢 我们一起来看看 一些 App 示例 它们套用了 TextKit 的强大功能
这样的 App 示例并不难找 因为几乎我们使用的每个 App 都需要显示或 编辑文本
我们首先看看 两个我们都熟悉的 App 然后一步一步 构建我们 自己的 App 我们要看的第一个 App 是 iOS 上的 “Apple News” 这是一个漂亮的 App 在个性化以及甄选的 文章中显示文本 下面这个例子 是在 “Spotlight” 标签页中 “Featured” 栏目中的一篇文章
现在 App 的顶部显示了 关于这篇文章的一些细节
我们如何才能用 TextKit 重现这个外观和风格呢
所以让我们考虑一下 早些时候 Donna 向我们展示的流程图 以便选择最适合 本示例的控件 我们有不少文本控件 可供选择 但由于我们想要 显示的文本很少 所以我们将在每一行都使用标签 我们可以看到 在检查器面板中 有很多定制选项 我们继续 将文本更改为 “Spotlight” 并把字体 改为 “Body” 我们还要勾选 “Dynamic Type” 这可以让 启用辅助功能设置的用户 能以适合它们需要的 字体大小和样式 来查看文本 现在我们可以在 界面构建器中 自定义这个标签 但我们也可以 在 Swift 代码中 看到所有这些属性
我们可以在运行时 动态设置文本和 排版属性 现在回到界面构建器 我们将继续添加两个标签
现在一切都很好 但我们还需要做一件事
所以回头看 Apple News 我们可以看到右边的文字 实际上显示 以两种不同的颜色 一部分是黑色的 一部分是白色的
现在我们可以实现这一点 用两个单独的标签 但如果我们 只想使用一个标签 在界面构建器中 是无法实现的 那么我们怎么做到这一点呢 我们可以利用 NSAttributedString 强大而 灵活的功能
attributedString 是 一串字符 它可以将属性 应用于一定范围内的字符 你可以轻松地获取一些属性 比如默认字体 和文本颜色一样 但我们可以 用我们自己的值重写 这些属性 在这种情况下 我们将把 字符串的部分文本颜色 设置为白色 要具体查看属性字符串 我们将使用 在 NSMutableAttributedString 中的 addAttribute() 方法 来讲我们想要的范围内的文本 设置成白色
这一次 我们将 在我们的标签上设置 属性文本属性 在运行时 这看起来很酷
UILabels 对这类文本来说 是很棒的选择 如果我们看看屏幕底部 我们会看到 一个标题 这也是文本 但它有比较大 占了很多行 使这个文本不同的另一点是 它是可以 被选中的 那么这次我们应该使用 哪种控件呢
文本栏和文本视图 都支持选择 但文本栏通常 仅用于一行 因此 在这种情况下 由于我们的 标题可以跨越多行 因此我们将使用
文本视图 当我们将文本视图 放到 Storyboard 上时 我们可以看到 我们默认会得到 大量的测试文本 我们继续来 检查器面板中 更改文本
我们还要改变字体 使它看起来更 像 “Apple News” 我们希望禁用 编辑功能 因为 标题不应该被编辑 UITextView 默认支持滚动 因为它们是 UIScrollView 的子类
但是 如果我们希望我们的文本视图 在自动布局中良好运行 我们应该禁用滚动 因此 这将允许 我们的文本视图的边界调整大小 以适应文本 最后 这个白色背景 真的需要删去 所以我们要把它 设置为透明
界面构建器使得 自定这个文本视图非常容易 但是和之前的标签一样 我们可以在代码中 设置所有这些 因此 在 Swift 中我们可以 在运行时动态设置 文本和格式属性
我们现在看过了 Apple News 并选择了正确的控件 现在我们要看看另一个 我们都熟悉的 App 并选择正确的配置 那就是“文本编辑” “文本编辑” 是 macOS 上的一个 App 用于处理显示和编辑 富文本内容
但大多数人不知道 “文本编辑” 实际上是 NSTextView 套上了一个壳子
我想花一点时间 感叹一下 TextKit 给我们带来了 多少额外的好处 所以这是检查器栏 我们通过勾选 界面构建器中的复选框 就可以轻松获得 正下方是一个标尺视图 我们也可以轻松获得它 只要简单地启用它即可 而下面的所有内容 其实只是一个文本视图 更准确地说 是一个文本视图 加上文本容器 布局管理器 还有文本存储 这是 NSTextView 和 UITextView 的 标准配置 但相似之处也就这些 例如 表格 只在 NSTextView 中受支持 再次感叹一下 我们免费 获得的强大功能 TextKit 提供了一个表格编辑器 为我们完成所有 繁冗的工作
当我们使用“文本编辑”时 我们经常编辑大量的文本
有时候我们会粘贴很多 示例文本 来看看我们是否 也免费获得了拼写检查器 但我们真的想看到的是 当我们使用格式菜单 选择“按页面换行”时 最终看起来会 有点像一个页面 我们可以看到文本容器 已被调整大小 以匹配一张纸的尺寸 现在 如果我们向下滚动 我们可以看到文本会 从第一页跳到第二页 标准配置 并不真的支持 这样的布局
显然 这个布局使用了 两个文本视图和文本容器 但它们仍然 由相同的布局管理器和文本存储管理 这使得文本 可以从一个页面跳到 另一个页面 现在 如果你想了解更多 关于文本编辑是如何工作的信息 你其实可以在指南和示例代码库中 找到它的源代码 所以我们选择了正确的控件 我们选择了正确的配置 但有时我们 实际上需要使用锤子控件 以达到我们想要的效果
但是 我们如何决定 使用哪个锤子呢 我们将尝试为任务 挑选合适的锤子 在我们接下来一起 构建一个日记 App 的过程中
我们从把今天的日期 放到窗口上开始
我们在 AppKit 中没有 UILabels 但是我们可以使文本栏 像标签一样工作
我们所需要做的就是禁用编辑 现在 对于窗口的日记内容部分 我们将使用 textView
所以在检查器中 我们可以确保 文本视图是可编辑和可选的 并支持富文本和撤销 我们还要 在窗口底部添加几个 文本栏 来显示 写了多少个单词 现在 当我们运行我们的 App 时 我们希望 底部的字数统计随之变化 所以让我们来为这个任务 找到合适的锤子 我们可以选择委托通知或子类 但在这种情况下 我们将 使用小锤子 我们将监听 来自文本存储的通知
我们可以从文本存储中 获取单词的数量
当我们收到通知时 我们可以更新 文本栏的 字符串值属性 当我们开始输入时 我们可以 看到字数改变 如果我们想强调部分文本 我们可以使用键盘快捷键 或菜单来应用 格式 比如粗体 但是如果我们能够支持 现代文本排版工具会更好 比如 Markdown 它使用命令字符 来指定格式 如果我们在一段内容前后 分别添加两个星号 我们希望结果是粗体的 但我们应该使用
哪种锤子呢 我们想知道什么时候 发生变化 并且我们想知道 变化发生在哪里 但通知并没有真正 给我们提供很多关于 这种改变的信息
所以我们将使用更大的锤子 并实现 textStorage() 委托 具体是 _ didProcessEditing: 方法 我们可以从现有的字体中 创建一个新的粗体字体 而且 我们可以将该字体 直接添加到我们的 textStorage 用于我们想要加粗的范围 现在 当我们插入最后一个星号时 我们可以将其加粗 我们现在对这个 Markdown 功能非常满意 那么如果我们尝试插入 代码片段呢
在 Markdown 中是这样的
如果我们添加最后一个反引号 我们希望它看起来像一个代码块
它应该有一个背景和 一个表示 Swift Code 的标头
这实际上是一项复杂的任务 所以我们需要两把大锤
第一个是 NSTextStorage 子类
当我们子类化 NSTextStorage 时 我们需要实现四个必要的方法 我们通过对一个可变字符串的 局部实例进行操作来做到这一点 我们需要注意 replaceCharacters() 方法 我们可以将 NSTextBlock 添加 到我们的段落样式中 然后我们将 该段落样式添加到 我们文本存储中 代码块的范围内 现在 NSTextBlock 本身 不会自己做任何自定义绘制 所以我们也需要对它
进行子类化 我们的 NSTextBlock 子类需要 在顶部有一些 额外的留白 还需要一个浅灰色背景 我们将重写 drawBackground() 并使用字符串绘制来绘制 标头 Swift Code 实际上这些我们想 让文本块看起来 像一个代码片段所需要做的 现在回到我们的 customTextStorage 我们可以创建 新代码块的实例
而不是使用纯文本块 最后 我们还要 告诉我们的 textView 使用 我们的 customTextStorage 所以我们要在布局管理器中 把原来的文本存储替换掉 现在这变成了一个真正的 所见即所得的 Markdown 编辑器了 现在在大多数 Markdown 编辑器上 都有的一个功能是 并排视图 左侧是编辑区域 右侧是渲染版本
我们可以通过使用 两个文本视图来实现
我们停用右侧视图的编辑功能 现在我们有两个文本视图 虽然我们希望它们显示 相同的内容 但我们还希望 右边视图能够看起来不太一样
所以我们需要一个像这样的配置 两个视图的文本存储器相同 而所有其他都不同
为此 我们 将用左侧的文本存储 把右侧的替换掉
现在让我们看看 这是什么样子 现在这真的很酷 如果我们在左侧添加任何字符 它们将立即显示在右侧 现在通常右手边 并没有真正显示 Markdown 字符 但由于这是一个 共享文本存储 这意味着我们 必须隐藏字符 在布局过程中 因为我们需要这样做 所以我们实际上只有一个选择 那就是实现 NSLayoutManager 委托上的 shouldGenerateGlyphs() 方法
这将允许我们介入 字形生成过程 因此 我们可以 获取即将布局的字形 并且如果它们表示 Markdown 命令字符 我们可以将 .null 属性应用于该字形 现在 这将让字形 在布局过程中被 彻底消除 而不会更改 底层的文本存储 然后 我们将使用新的字形 并告诉布局管理器 我们想要用这些新的属性 来呈现这些字形
这真的很酷 所以左侧显示了 可编辑版本 包含所有 Markdown 字符 而右侧没有显示 任何 Markdown 字符 全部使用相同的文本存储 现在构建一个并排的 Markdown 编辑器 并不是 我们每天都在做的事情 但是很开心看到 TextKit 的可自定义度是多么高 以及它是如何与现实的例子结合的 如果你想了解更多 关于如何使用和自定义 TextKit 的信息 请查看我们出色的 编程指南 然后 我要把现场交给 Donna
谢谢 Emily 这些例子真的 非常酷 我真的希望你能够 采用她展示的一些技术 并在你自己的 App 中使用它们 但现在让我们稍微转换一下 并讨论一些 使用文本的最佳做法
关于正确性 如果你的文本没有 按照你期望的方式呈现 它可能与属性字符串上的 不完整或不正确的 属性有关
所以我们来看看一个例子 在实践中看到这一点
假设我们有一个 UITextView 它带有一些属性文本 写着 “Don't hate.” 这段文本的格式是 Comic Sans 字体 24 号大小 我们希望以编程的方式 把它设置成粗体 因为如果有 任何比 Comic Sans 令人讨厌的字体 那一定是 Comic Sans Bold 所以乍一看 编写这样的代码似乎是合理的
现在 我们有我们的原始字体 我们将使用一个 fontDescriptor 来创建原始字体 的粗体版本
然后 我们要 使用原始文本 初始化可变属性字符串 我们要将我们的 新字体或新的粗体字体应用于 单词 “Don't” 这是前五个字符 然后我们将设置 我们的 UITextView 的属性文本属性 来使用这个新的 属性字符串 但是我们这样做时 我们会看到 应用于该单词的新的粗体字体 确实像我们预期的那样 但字符串其余的部分 不知道为什么丢失了原始字体 现在 那些不喜欢 Comic Sans 的人可能会为此感到高兴 但结果是错误的 所以这是一个悲伤的故事
那么为什么会这样呢 为了回答这个问题 让我们 仔细看看我们是如何 初始化我们的 属性字符串的
所以请注意 我们要使用 纯文本字符串来初始化它 并且我们使用的 初始化程序没有属性信息 当你创建一个新的 属性字符串 并且你没有 提供任何属性信息时 新的属性字符串 会使用默认的属性 默认字体是 Helvetica 12 号 为了回顾发生了什么 我们开始 从这个原始的属性字符串 将字体 Comic Sans 24 应用于 整个范围 然后我们创建了这个新的 属性字符串 并使用默认属性 进行了初始化 并且我们将粗字体应用于 新字符串上的 “Don't” 结果就是这里这个 不正确的结果 其中 “Don't” 以 Comic Sans 粗体 24 号显示 但字符串的其余部分 处于默认字体 Helvetica 12 有两种不同的方法 可以让它们正确地显示 一种方法是 避免将纯文本 和属性文本混合在一起
因此 通过使用原始字符串 来初始化我们的新属性字符串 我们要把 那些原始属性保留下来 然后我们再应用我们的新属性 而不会看到这个 重设的默认效果 但是 避免混合纯文本和属性文本 并不总是可行的 因此 如果你必须将其混合 则可以在从纯文本字符串中 创建新的属性字符串时 明确提供属性 如果我们确保 从原始文本中应用相同的属性 我们就会得到 正确的结果 但是你应该知道 重设效果会发生 在任何具有默认值 而不仅仅是字体的属性的情况下
如你所见 有很多属性都具有默认值 所以我想特别提醒一下 这里的 “Paragraph style(段落风格)” 因为它是一个隐蔽的 重置点 为了明白为什么 我们将重新回顾一下前面的例子 但不是改变字体 我们将改变段落风格 来屏蔽 “hate” 这个词 因为没有人喜欢仇恨 所以我们希望我们的文本看起来像这样 但是当我们运行这段代码时 我们将得到 一个这样的结果 所有文本的格式都是 Helvetica 12 使用默认的段落风格 默认的换行模式 和默认的自动断词 又一次 对于那些 讨厌 Comic Sans 的人来说 真是太棒了 因为它已经被完全地 从字符串中移除了 但它=这是错误的 但是这次错误的原因 与上次不同 为了更好地理解这不同 让我们回顾一下 属性修复发生在 布局之前 这时候 系统会修复不一致的属性 在我们的属性字符串中 我们有一个 包含多个段落样式的单个段落 这显然非常不一致 因此 当系统修复 此字符串的属性时 它将采用 它找到的第一段样式 并将其应用于整个段落
这就会导致 我们的属性字符串 以默认段落样式显示 关键是要明确你的属性 特别是在混合纯文本和 属性文本时 所以通过这样做 你就能避免它出现 默认属性的重置效果 对于 AppKit 开发人员来说 如果你正在为 App 更新 深色模式 这一点尤其重要 因此通过使用 带有动态颜色的显式属性 如 NSColor.textColor 可以确保你的文本 会根据环境用正确的 颜色绘制 继续 下一个话题是性能 如果你使用大量文本 一个提高 App 性能的 好方法是 使用非连续布局 为了明白这意味着什么 让我们重新回顾 布局过程
我们说过布局过程 包括字形生成 和字形布局
所以对于连续布局 布局管理器 在文本存储开头就开始 执行字形生成和 字形布局了 它以从头到尾的顺序进行
所以如果有人使用你的 App 滚动到文本视图中间的某个点 那么布局管理器 必须为红色矩形 所指示区域之前的所有字形 生成并布局字形 并且请注意 这还包括你从 屏幕顶部滚动回到 文本存储起点之间 所有你看不见的文本
所以如果你有很多文本 那么这个可怜的用户 可能需要等待你的 App 一段时间 让它完成布局 但幸运的是 我们可以通过使用非连续布局 来避免这种情况
正如其名称所暗示的那样 使用非连续布局 布局管理器无需 从文本存储开始 顺序地进行字形 生成和布局 所以现在当这个使用你 App 的人 滚动到文本视图的中间时 布局管理器可以 立即为该中间部分 执行字形生成和布局 所以如果你的文本存储中 有很多文本 使用非连续布局可以 为你带来巨大的性能提升 那么你如何启用 这个功能呢 非连续布局是 NSLayoutManager 的一个属性 对于 NSTextView 你可以使用布局管理器 来访问文本 然后在其中设置该属性 对于 UITextView 你通常不需要做任何事情 因为这是默认开启的 但只需要记住一件重要的事情
由于 UITextView 是 UIScrollView 的子类 因此非连续布局 需要启用滚动 才能生效
这是因为当你禁用滚动时 询求你的文本视图 所包含的内容大小 需要布局 所有的文本 所以你根本 不会获得 非连续布局的 性能优势 这把我们引导了一个 很重要的问题上 当你使用非连续布局时 你应该避免一次 请求全部或者大多数文本的布局 因为这样做 从根本上就 违背了我们的初衷 所以如果你只有一个文本容器 不要要求 整个容器的布局 并且不要要求 包含文本结尾的大范围字符 或字形的布局 我们并没有深入 研究文本性能的主题 因为 我在去年的 WWDC 2017 上针对这一点 进行了一次非常棒的演讲 那就是框架效率的不完整性 你可以在讨论会结束时 通过更多信息链接 访问该视频 好的
现在是时候谈论 每个人最喜欢的话题 安全 你可能已经注意到 最近有一些事件发生在互联网上 某些人利用我们软件中的错误 来给使用我们产品的人们制造麻烦
作为回应 我们正在继续设计 抵御这类攻击的技术 但今天我想谈谈 我们如何共同努力 为这些攻击 提供更强大的防御 所以你可能已经听说过 纵深防御的概念 如果你不熟悉 这些术语的话 纵深防御 就是创建多层保护 来抵御威胁 这个概念已经存在了几个世纪了 你可以在中世纪城堡的设计中 看到它 城堡周围的地上没有树木 所以你可以看到袭击者来了 还有一个护城河 可以让城堡更加难以接近 并且可以防止敌人在 下面挖隧道 而墙壁是另一道防线 它们很高 所以很难攀爬 并且在墙壁和顶部 有很多垛口 让防御者能在 有防御的地方 向攻击者开火
这些保护中单独 任何一种都可能不足以 抵御攻击 但它们组合在一起 就提供了强有力的防御 就像城堡一样 我们在这里为攻击 提供了多层防御 但没没有人会阻止 你在你的 App 或框架中 采取自己的 防御措施 通过这样做 你可以添加另一层保护 并提高产品安全性 这是共赢 那么让我们来谈谈 你可以在这里做什么 而我希望你考虑的是 在你的 App 或框架中 设置文本输入限制 现在我想强调 这可能并不总是 有意义的 因此举例来说 如果你的 App 是 Emily 之前展示的那种 日记 App 的创作工具 那么对该文本的长度设置限制 并没有任何意义 所以如果没有意义 你就不应该这样做 但相比之下 如果你的手机 App 有一个 用于为帐户分配昵称的 文本域 那么你可能会想 在这里应该设定怎样的限制 才合理 设置这些限制是个好主意 因为所有文本输入 都有潜在的风险
当你允许文本输入时 你就允许了复制和粘贴 你不知道其中会被 粘贴哪种文本
它可能是任何东西 它可能是一个 带有恶意字符组合的字符串 或者它可能只是 一个非常 非常长的字符串
即使像这样的长字符串 本身可能不是恶意的 它也可能会导致你的 App 冻结或卡住 如果你有一个文本域 是为了一行输入设计的 但有人想将“战争与和平”的 全部内容粘贴进去 这大约有 310 万个 英文字符 这是否合理
可能并不
所以这是一个很好的例子 它可以让你能强制增加 你自己的限制
以下是 设定这些限制的推荐方法 你希望在输入字符串 在文本域上设置之前进行验证 对于 UITextFields 你可以通过 使用 UITextFieldDelegate 来完成此操作 对于 NSTextFields 你应该使用自定义的 NSFormatter 来实现你的验证逻辑 而且我们还有一些额外的 安全增强功能即将推出 因此请在发布说明中注意它们 如果你有任何问题 请在本周的实验室中 联系我们 好的 时间要到了 所以让我们回顾一下 你知道如何选择正确的控件 自定义点 和自定义方法 并了解在正确性 性能和安全性方面 遵循的最佳实践 使用这些知识和 TextKit 去创伟大的东西吧
等等 这里是超级重要的 更多信息链接 你可以在这里找到 我们今天引用的 所有过去的优秀讨论会和文档 请在星期四和星期五来 我们实验室参观 谢谢 希望你们可以享受讨论会的 其余部分 [ 掌声 ]
-