大多数浏览器和
Developer App 均支持流媒体播放。
-
Foundation 中的新功能
探索 Foundation 的最新更新如何帮助您改进 app 的本地化和国际化支持。了解专为 Swift 设计的新 AttributedString,并学习如何使用 Markdown 将样式应用于您的本地化字符串。探索语法协议引擎,它会自动修复本地化的字符串,从而使它们与语法性别和复数形式匹配。我们还将向您介绍迄今为止的改进和数字格式化,这些功能可简化复杂的要求,同时提高性能。
资源
相关视频
WWDC23
WWDC22
WWDC21
-
下载
你好 我叫托尼 我是Foundation团队的一名工程师 欢迎来到《Foundation的新功能》 Foundation框架为所有app 和框架提供基本功能 它有很多功能 包括 从文件处理到网络和通知的所有内容 今天我想专注于 所有app都需要的东西: 国际化和本地化 在今年发布的版本中 我们在这个API中 取得了一些有史以来最大的改进 我们从低阶开始 重新思考Swift中的 AttributedString是什么 我们为Swift重新构建了格式化程序 使它们更快 更容易使用 并增加了新功能 最后 我们有一个全新的功能 叫做自动语法一致 它大量地减少了 需要提供的本地化字符串的数量 同时使代码更简单 让我们直接进入 AttributedStrings AttributedString是字符 一组范围和字典的组合 AttributedStrings允许你将属性 也就是键值对 与字符串的特定范围产生关联 最常见的属性由SDK定义 但你也可以创建自己的属性 你经常会在支持富文本的API中 找到AttributedStrings 让我们看个例子 这是我正在开发的一个app 叫做Caffé 菜单很简单 我选择我想要的食物、大小和数量 最后 它会给我一张收据 上面列出了我订购的所有东西 在底部 我决定增加一段简短的感谢词 这是一个AttributedString 部分字符串为粗体 部分为斜体 最后一个字带有连结 正如你在此处所看到的 属性可以重叠 从Foundation一开始 我们就有了一个引用类型 叫做NSAttributedString 今年 我们引入了一个新的结构 AttributedString 它充分利用了 Swift为我们提供的所有功能 首先 它是一种值类型 它还具有与Swift String 相同的字符计数行为 作为我们致力于简化 编写包容性软件的承诺的一部分 AttributedString 现在已完全可本地化 最后 它的构建考虑了安全和保障 这既包括使用强类型的 编译时的安全性 也包括使用Codable 解压缩期间的安全性 让我们简单介绍一下你可以使用 新的AttributedString做什么
我们要建立我们的感谢信息 首先 我们使用一个 简单的初始化程序 创建AttributedString 我想在整个字符串上设置一个属性 这就像设置字体属性一样简单 在AttributedString结构中 所有属性都可以 直接使用 并且它们使用正确的类型 例如 此属性是SwiftUI字体 接下来 我们创建另一个 AttributedString 这是对我们网站的引用 因此我们将链接属性设置为URL 这里我在整个字符串上 设置字体和链接 稍后我们会研究 如何更改字符串的一部分 另一个有用的工具是 AttributeContainer 这是一个你可以在 没有字符串的情况下 单独保存属性和值的地方 在这里 我创建了一个容器 并设置了一些属性 取决于我的信息的重要性 最后 我将这些属性 合并到两个AttributedStrings中 如前面所述 AttributedString是 字符、范围和字典的组合 AttributedString 本身不是这些事物的 任何一个集合 相反 为了访问这些属性 它具有我们所谓的视图 两个最重要的视图是字符 提供对字符串的访问 和执行 提供对属性的访问 这些视图是Swift集合 这代表你熟悉的数组类型的函数 也可以在这里使用 让我们再看一个例子 假设我们的设计师叫我们 在感谢信息中增加一点活力 把所有标点符号都变成有趣的橘色 要做到这一点 首先我需要找到 AttributedString中 标点符号的位置 像其他Swift集合一样 AttributedString视图 使用索引 索引只是集合中 某个元素的位置 为了通过索引进行叠代 我使用标准库索引函数 接下来 我使用函数 isPunctuation来检查 这个字符是否是需要更改的字符 最后 我使用AttributedString的 另一个功能 切片 将一个属性仅应用于 整个字符串的一个子范围 范围从这个索引开始 一直持续到 下一个索引 即一个字符 现在标点符号是橘色的 让我们看另一个视图 执行 执行是特定属性的 起始位置、长度和值 我们可以从计算信息中的 所有执行开始 这将叠代字符串中 每个连续范围的属性值 在这个字符串中有四次执行 每次执行的每个属性都有一个值或无 字符和执行的范围可以互换 因此你可以找到属性的字符串 反之亦然 在这里 我在字符视图的下标中 使用属性的范围 将结果转换为 独立字符串
通过关注特定属性来查看执行 通常是最有用的 在这里 我们使用关键路径链接来合并 链接属性 结果集合的每个元素都会给我们 链接属性的值 而不考虑 可能在字符串中设置的其他属性 如果我们只看连结 我们有三个执行:第一次 它没有设置;第二次 被设置为一个值;第三次 句子中的最后一个句号 这里不再设置 叠代执行为我们提供了一个值 和范围的元组 由于这些值是类型安全的 我们可以使用一个API 比如方案 存在于URL上 而不用强制转换或担心类型错误 我在这里检查 AttributedString中的 每个连结是否都是https 另一种有用的技术是查找 子字符串并使用该范围 来编辑字符或属性 比方说 我想将“访问”一词替换为 更具有怀旧氛围的东西 首先 我查找子字符串的范围 接下来 我使用该范围 仅在该子范围上设置属性和字符 结果是一个有六次执行的 AttributedString 就像这样 接下来 我们来谈谈本地化 AttributedString 是完全可本地化的 我们还在Objective-C中 为NSAttributedString 增加了本地化支持 AttributedStrings位于 app的字符串文件中 就像一个普通的常规字符串 在Swift中 我们现在支持 使用字符串插值 来本地化字符串 和AttributedString格式 就像SwiftUI的文本视图一样 这里有个简单的例子 这个函数返回一个本地化的字符串 该字符串是用用户的文档名订制的 不是使用像%@或%d 这样的格式说明符 来调用格式函数 你现在可以直接传入值 相同的方法也适用于 AttributedString
Xcode可以使用编译程序 从这些新的初始化器中 生成字符串文件 要打开它 进入Build设置 查找本地化设置 然后打开使用编译程序 提取Swift字符串 你可能想知道 本地化的AttributedString 是如何获取其属性的 我很高兴地告诉你 我们已经为 AttributedString 增加了对Markdown的支持 这是在SwiftUI文本中使用本地化 AttributedString的例子 我从一个普通的字符串开始 通过在文本周围增加两个星号 我强烈强调它 SwiftUI将其显示为粗体 我可以使用下划线来强调文本 SwiftUI会显示为斜体 我们也支持连结 这是本地化人员为不同语言 提供自定义URL的好机会 我们还支持其他内联样式 例如删除线和代码语音 最后 让我们谈谈归档 AttributedStrings 首先 我们需要能够与 NSAttributedString 引用类型相互转换 AttributedStrings 可以是数据模型的一部分 这代表我们要能够 对它们进行编码和译码 最后 我们想要在Markdown中 指定自定义属性的方法 这些操作都是相互关联的 让我们从转换开始
我们都写过很多使用 NSAttributedString的代码 所以我们简化了 从结构到类类型的转换 这个视图有 NSAttributedString属性 要进行转换 只需将我们的结构 AttributedString 传递给 NSAttributedString初始化器 对于SDK一部分的属性 这就是我需要做的 现在让我们看看编码和译码 这是一个包含来自 我们Caffé app收据的结构体 同样 我使用了 SwiftUI、UIKit、AppKit和 Foundation提供的属性 这代表AttributedString的默认 Codable实现就是我所需要的 我只是将Codable一致性 增加到收据上 就完成了 让我们更进一步 增加对自定义属性 编码的支持 我们将从属性本身开始讨论 属性由两部分组成:键和值 键是一种符合新的 AttributedStringKey 协议的类型 这定义了它需要什么类型的值 以及用于归档的名称 此键还可以符合其他协议 以自定义值的编码或译码方式 假设我们想要定义一个 AttributedString的范围 以具有一些额外的颜色 这种彩虹效应分为三个层次: 简单、有趣或极端 我们将使用枚举来表示该值 并将名称设置为彩虹 定义类型和名称是该协议的 唯一要求 现在 假设我们要将此属性 设为Codable 以便它成为编码的 AttributedString的一部分 我所要做的就是增加Codable一致性 就像这样 最后 假设我们希望彩虹级别 成为我们本地化字符串的一部分 这代表它可以应用于字符串的右边 无论在哪里 在任何语言中 我们只需要再选择一份 符合协议的文件 当我们说一个属性 是Markdown可解码的 那我们就可以 直接从Markdown解码它 并将它插入到AttributedString中 所需要的只是该值是Codable 接下来 我们来看看Markdown的 自定义属性语法 在第一个例子中 我们引用了一个连结 它使用方括号作为连结文本 括号表示链接目标 即URL 在第二个例子中 我们有一个对图像的引用 它以一个感叹号开头 然后使用方括号表示图像描述 并使用括号表示图像来源 前两个例子在Markdown中很常见 第三个例子展示了我们的 自定义属性的语法 它以插入符号开头 然后使用方括号表示文本 并使用括号表示其属性 属性用JSON5表示 JSON5与JSON兼容 允许使用不带引号的键 注释和一些其他功能 对于像这样的人性化字符串来说 这是一个很好的匹配 我们还在Foundation中 为其他JSON API增加了JSON5支持 因为自定义属性使用JSON 任何可以用 JSONDecoder解码的东西都会自动 与新的自定义Markdown语法兼容 这里我们有一个属性 两个属性、一个字符串 和一个数字 以及一个具有多个属性的属性 这里只有一个额外的部分 就是我们如何 把Markdown中的这些名称 连接到Swift类型 这部分称为属性范围 作用域是一组属性键 从JSON或Markdown解码时 作用域很有用 因为它们告诉我们希望 找到哪些属性、它们的名称 以及如何解码它们 我们为Foundation UIKit、AppKit 和SwiftUI分别定义了一个作用域 你也可以定义自己的属性作用域 让我们为Caffé App 定义一个作用域 我们将作用域嵌套在 AttributeScopes中 并使其符合AttributeScope协议 然后我们需要做的 就是用“let”列出 作用域内的属性 到目前为止 我们只有 RainbowAttribute 接下来 我们将在我们的内部 包含SwiftUI作用域 除了我们自己的属性之外 这还允许所有这些属性 作用域递归嵌套 因此这也包括Foundation属性 为我们的新范围 定义一个属性是很有用的 这允许我们在函数中使用 键路径语法 该函数将作用域作为参数 最后 我们现在可以从自定义 Markdown 加载我们本地化的彩色 AttributedString 你还将发现用于归档和转换 NSAttributedString的 作用域函数 这允许在每个步骤中自定义行为 这是Caffé app的第一个屏幕 你可以在标题中 看到我们自定义的彩虹属性 在本地化字符串从Markdown转换为 AttributedString后 app会找到属性 并将有趣的效果应用于该字符串范围
由于该属性来自 我们的本地化字符串文件 因此它适用于Caffé支持的 所有语言 例如西班牙语 不过 我们才刚刚开始 我们还有一个全新的格式化程序API 格式化程序是另一个长期存在的 Foundation功能 负责获取数据 如数字、日期、时间等 并将其转换为 本地化的和用户可表示的字符串 格式化程序由相当多的配置数据支持 因此缓存和重用它们 是一种常见的模式 但是 app由许多不同的代码组成 在所有格式化程序之间共享程序 可能并不总是有意义的 此外 由于人们 阅读日期和时间的方式 有很多种 加上我们作为app作者 希望以符合我们设计的方式 呈现这些数据 因此存在很多边缘情况 今年 我们通过从头开始重新思考 我们的格式化程序API 来提高性能和可用性 简而言之 我们的新API专注于格式 让我们看一下来自地震 这个示范app的这段代码 在这里我们可以看到缓存模式的作用 它有两个步骤 首先 创建并配置一个格式化程序 接下来 给格式化程序我们的日期 并获得一个字符串 怎样还能更简单? 好 让我们先删除 创建我们自己的 日期格式化程序的要求 很容易忘记这需要缓存 这将导致表中的每个单元格重新创建 相同的格式化程序 接下来是格式化步骤 我们不需要将日期传送给格式化程序 而是使用日期本身 现在只有一行代码了 你只需指定所需的格式即可 让我们进一步讨论这个数字格式 它的代码并不多 但它隐藏了一些复杂性 并且有一些需要注意的陷阱 如果参数不是浮点数 你会得到完全错误的输出 读者必须了解格式化 浮点数的特殊语法 以及一组只是字符串常量的修饰符 我们认为这段代码更容易理解 维护和阅读 它使用一般的Swift函数 来准确指定 我们希望的数字格式 你还可以获得自动完成和类型安全 我们已将这种新方法应用于 Foundation中的 所有十个格式化程序 我们清理并简化了界面 进行了更改以帮助避免常见的陷阱 并在此过程中增加了一系列新功能 让我们详细了解两种 最流行的格式化类型: 日期和数字 日期格式是关于使用日历 和时区将绝对时间点转换为 人们可以理解的日期 更重要的是 它考虑了人类 对他们喜欢的 日期格式的所有偏好 我们将这些偏好称为语言环境 让我们来看看格式化日期 所需的少量代码 首先 我将使用Date.now 获取当前时间点 接下来 我调用格式化函数 就是这样 当然 正如我们刚刚在示范中看到的 日期格式有很多选项 所以让我们把它展开一点
格式化函数可以配置为 只显示日期或时间 这两个观点都有几个选项可供选择 这个新格式化API的一个重要目标是 在创建正确格式时提供 尽可能多的编译时间帮助 使用魔法字符串值格式化是出了名的 容易产生错误 格式在正常情况下看起来是正确的 但在边缘情况下会产生完全错误的值 比如在年底 这也是我们的默认格式 这是询问日期和时间 样式的简短版本 就像这样 对于无参数和简单样式版本 我们都为你选择默认格式 但是 如果你想真正自定义日期 只需从这里增加你关心的字段 在此示范中 我通过将字段 附加到样式来构建格式 我只想要年、日和月 其他可能包括时、分 秒等 输出格式将根据 用户的语言环境自动调整 这些字段也是可配置的 在本例中 我将月份更改为宽格式 这代表打印完整的月份名称 使用此API 也可以轻松格式化日期的一部分 这里我只想要工作日 日期也可以格式化为不同的样式 这里我选择使用ISO8601格式 和ISO8601 但只用 年、月、日 并用破折号隔开 通过这些示范 格式化模式变得清晰 我们从要格式化的值开始 我们调用格式化函数 参数是样式 每种类型可能有不只一种样式 例如日期 既有日期时间又有ISO8601 该样式可用于默认配置 或自定义配置 此格式化API通过指定字段列表 来运作 其中一些字段有额外的选项 你提供的字段顺序并不重要 每个字段只是告诉格式化程序 在最终输出中应该 包含哪些值 我们为API的最短版本 选择了一个合理的默认值 即那些没有参数 或只有样式名称的版本 一旦开始向其中增加字段 输出就会 变成自定义 并只反映你选择显示的内容 有点像UI中的占位符文本
还有一个新的API 用于格式化 两个彼此相关的日期 这里有些例子
首先 在一个范围内格式化两个日期 你可以只使用带有两个日期的 一般Swift范围语法 格式化范围允许配置显示 日期和时间 就像我们对单个日期所做的一样 你可以将此范围格式化为 持续时间或组件 或相对于当前的单个日期 格式化的另一个新功能 是属性输出 这使你可以找到格式化程序 将格式化值的特定部分重新排列 以适应用户偏好后放置的位置 当然 这使用了我们的新结构 AttributedString 将样式应用于格式化输出会出现在 各种地方 在watchOS上 许多复杂的 操作都是格式化字符串 由于Apple Watch是一种个人设备 因此考虑到用户的偏好很重要 但它也是我们想要应用 某种设计语言的地方 例如为日期的一部分 赋予用户选择的颜色 在SwiftUI中设置这个 是非常有趣的 让我们在这个示范中一起看一下 在这里 我有我的Caffé 配套app的起点 它会显示你的下一杯免费咖啡的时间 我有一个SwiftUI视图 它只显示一个格式化的日期
我在格式上设置了语言环境 这样我就可以在这里 用我的SwiftUI预览来控制它
这是一个很好的开始 但我想对它做更多自定义 让我们首先更具体地 对我的app自定义 我只关心分 时和工作日 好 看起来还不错 现在让我们增加一点颜色 首先 我们将返回类型更改为 AttributedString 并请求属性输出
接下来 我们将使用属性容器 这些可以保存属性 而不附加到 字符串中的任何特定字符 我们将为工作日属性 创建一个日期格式的输出 它设置在包含工作日的 字符串范围内
接下来 我们将为要设置的颜色属性 创建一个容器
最后 我们将使用 AttributedString函数 将与第一个容器中的属性匹配的属性 替换为AttributedString中 第二个容器中的值
因为AttributedString 是一种值类型 而替换 是一个变异函数 所以我们需要将 “let”更改为“var”
看起来很棒 更好的是 它适用于所有语言环境 让我们在预览中增加更多内容 以进行仔细检查
你可以在这里看到 无论工作日 在这些语言环境的 格式化日期中的哪个位置 它都是橘色的 让我们继续了解更多 新的格式化程序API 现在我们已经了解 如何将日期转换为字符串 接下来我们讨论 如何将字符串转换为日期 日期现在有一个 接受策略参数的初始化器 该策略用于告诉解析器 在输入中期望哪些字段 对于日期 格式也是一种策略 这对于往返日期很有用 例如在文本字段中 既能显示输出 又允许用户输入新日期的 下面是一个往返的例子 你会注意到解析可能抛出问题 这是因为 根据输入 解析可能会失败 某些策略具有更高级的解析选项 在这里 我们解析一个固定格式 当从服务器接受到 日期格式时 这个格式很有用 要使用它 请使用格式字符串初始化策略 不过 我们不使用魔法字符串值 而是 使用字符串插值 在本例中 我们期望格式为 年-月-日的字符串 每个插值都由字段清楚地标识 并且每个插值都准确地 指定了预期的格式 其中一个非常好的地方 是自动完成体验 如果我想使用不同的日期格式 自动完成会向我显示有效选项 以及有关每个选项含义的文件 不 更多的是猜测 你应该使用多少个Y字符 来解析一年 让我们来看看数字 数字格式化是关于将整数或浮点值 转换为人类可以阅读的内容 与所有格式一样 它考虑了数字 显示方式的偏好 这包括从使用的数字类型 到用于对数字进行分组的 字符的所有内容 与日期格式一样 无需额外参数 即可轻松获得出色的输出
有许多受支持的选项和输出 我们在这里显示百分比 科学记数法和货币 最后 让我们将几种格式放在一起 列表格式化现在只是格式化一个数组 此成员样式参数指定数组中 每个元素的格式样式 这些是数字 所以我想使用百分比 输出对于每个用户的语言环境 都是正确的 到目前为止 我们一直专注于直接格式化值 SwiftUI还支持将格式样式 附加到TextField 由于格式样式具有关于 它们所格式化的值种类的类型信息 我们可以使用可读 但安全的语法来计算我收据上的 小费百分比 让我们再看看我们的Caffé app 看看有多少地方显示了格式 我们对成分使用列表格式 我们对价格使用货币格式 我们对数量使用数字格式 并在订单按钮中本地化计数 我们不能忘记你在 角落里看到的日期格式 你会发现到处都是格式化输出 我们认为 这个新的API也会让你的app 变得简单 甚至有趣 还有很多资源可以 帮助你处理本地化字符串 和格式化程序 我们还有两个关于这个主题的课程 《本地化你的SwiftUI app》 和《简化你的本地化字符串》 接下来 我们来讨论一个叫做 自动语法一致性的新功能 西班牙语等语言的本地化人员 表达自然翻译的能力有限 有时会导致尴尬的对话 这些语言需要通过转换 以实现不同词性之间的 性别和复数一致性 有时甚至需要了解 用户的首选称呼 英语也有这个特点 名词的 单复数形式不同 我讲了很多语言术语 让我们来看一个例子 在我的Caffé app中 我可以选择食物 大小和数量 我选择了一份小沙拉
现在我的朋友说她也钥 所以我将计数增加到二 在英语中 “沙拉”这个词 必须改变以匹配数字二 这就是所谓的一致性 也就是说 这句话中的 单词必须相互匹配 在英语中 用复数形式固定单词 是一种常见的约定 现在让我们将我们的app 切换到西班牙语 并订购一份ensalada pequeña 或一份小沙拉
当我为我的朋友点餐时 这个点餐按钮 需要和英语一样有复数形式 但有一点变化 在西班牙语中 形容词pequeña 和名词ensalada都必须 与计数dos一致 因此 该按钮不是 ensalada pequeña 而是ensaladas pequeñas 接下来 我要点饮料 对于这句话 按钮不仅需要 正确的复数形式 还需要 在这些词的语法性别一致 果汁 jugo 是男性化的 形容词pequeño也必须匹配 为了像这样正确本地化文本 我们最终会遇到组合爆炸 每种食物、大小和数量的组合 都需要不同的本地化字符串 在代码中 它通常最终看起来像这样 我们需要切换每个项目 然后切换每个大小 依此类推 还有一个stringsdict文件 它可以将这些字符串中的 每一个正确复数以进行计数 现在 通过利用键盘建议的 相同技术 我们创建了一个新的API 可以轻松处理所有这些情况 和更多情况 我们将此功能称为自动语法一致 因为系统会自动修复 本地化的字符串 使其具有正确的语法 现在代码变得简单多了 你可以将数量、大小和食物 组合在一个字符串中 自动语法一致 将使用称为屈折变化的过程 为你修复字符串 让我们分析一下 为了进行屈折变化 我们需要知道 字符串的哪一部分需要被修正 幸运的是 我们在Swift中 有一个类型可以做到这一点 AttributedString 和Markdown中的自定义属性 在此字符串中 我使用该语法的屈折变化属性 来包装食物、大小和计数 该属性的值为真 当我们导出该项目的本地化时 我们将获得一个字符串文件 其中包含我们的注释字符串 以及源代码中的其他本地化字符串 例如食物的名称和大小 这是拉丁美洲西班牙语的字符串 本地化器使用了参数重新排序语法 %1、%3、%2 因为像“小”或“大” 这样的形容词 放在西班牙语中的名词之前 他们保留自定义属性语法 来改变字符串的这个区域 并提供食物和大小的翻译 自动语法引擎负责修正 其余部分 某些语言不仅要本地化 文本的单词之间的一致性 而且在该文本与读者之间的一致性 自动语法一致 也可以帮助解决这个问题 例如 让我们看一下 备忘录的欢迎屏幕 在英语中 我们说“欢迎使用备忘录” 在西班牙语中 我们说“Te damos la bienvenid a aNotas” 或“我们欢迎你使用备忘录” 我们希望在西班牙语方面 与在英语方面有相同的体验 但是 在西班牙语中 “bienvenido”一词必须 与用户首选的称呼词相匹配 这个数与可能是几种选择中的一种 这个选择会改变文本 使用正确的称呼 会带来更加个性化和包容性的体验 在今年的版本中 我们让使用西班牙语的人 可以指定他们的称呼 在语言和地区设置中 有一个新的称呼选项 当你选择它时 你可以选择你的偏好 也可以选择与所有app共享 在这里 你可以在备忘录中 看到新的欢迎屏幕 用于表示女性化称呼 这里是一个男性化的称呼 如果我们不知道或用户不想指定 我们保留原始字符串作为替代 我们之前看到的相同的 屈折变化属性也用于 引用用户的本地化字符串中 在我们的“欢迎来到”案例中 我们 将屈折变化属性 应用于单词bienvenido 英文字符串不需要更改 我还可以增加一个屈折变化替代选像 这是引擎在没有 关于用户首选项的信息时 使用的替换字符串 今年 我们支持西班牙语 和英语的自动一致性 我们已在整个操作系统的 多个地方采用 例如备忘录中的欢迎屏幕 你也可以在你的app中采用它 所需的代码更改大多只是删除大量 逻辑来选择不同的字符串 关于如何屈折变化的指令 是本地化字符串本身的一部分 允许本地化人员更好地 控制字符串 在他们的语言中的显示方式 让我们来看看我们的 Caffé app中的 自动语法一致 让我们来看看Caffé app的 英语版本 我先从点披萨开始 我想要一个大的 数量一 请注意按钮上的文本是如何 从零个大披萨变成一个大披萨的 这是自动完成的 如果我选择二 它会再次修正 我只要一个 在此屏幕的底部 订单按钮已更改为一个项目 喝点东西如何? 小杯就好 只要一杯 请注意按钮如何从“项目” 改为复数的“项目” 该字符串已自动更改 让我们来看看
这是我们的收据 它列出了我们的比萨和果汁 以及格式化的价格 底部是我们的AttributedString 带有自定义字体和网站链接 让我们回到Xcode并查看源代码 我将从食物细节视图开始
这是显示大小选择屏幕的视图 让我们增加一个新的尺寸 这样我们就可以看到如何只需要我们 为西班牙语增加一个本地化字符串 而不是为每种食物、每种大小 和每种计数增加一个字符串 这一行显示了列表 列表来自我们的模型对象 我们去那里看看
尺寸枚举已经有小和大 我将为我们最饥饿的客户 增加一个新尺寸 称为“特大” 为此 我将增加一个新案例 以及一个本地化字符串
现在我只需要为特大的尺寸增加价格 对于这个示范 我只是将它们放在初始化程序中
让我们再看看我们的视图
预览中是我们的新尺寸 我们的源代码已经包含了 一个英文字符串 我现在只需要一个西班牙语版本 为了生成新字符串 我将使用编译程序 为“特大”找到新的本地化字符串 为此 我选择了产品导出本地化 并保存了西班牙语字符串
现在让我们为西班牙语增加翻译
我可以过滤我们的新字符串 并输入我们的西班牙语单词
接下来 我导入这些本地化语言 放入我的app中
现在我要再次执行我们的app 但用西班牙语版本 为此 我选择了产品方案编辑方案 在选项中 我可以选择 我想要测试的语言
然后执行 你可以从我们的标题中 看出我们现在以西班牙语执行 我们再点一次吧 从沙拉开始 请注意 当我更改数量时 订单按钮已修正 我们新的特大尺寸是正确的复数 两份沙拉 另外 它与“ensalada”的 语法性别相匹配 所有这些都只有一个字符串 今年Foundation中有很多 很棒的新功能 它们已经准备好 让你今天在你的app中试用 AttributedString提供了一个快速 易于使用且Swift优先的界面 用于将键值对增加到字符串的范围内 你可以在文本中使用SwiftUI 并开始在本地化字符串中 使用Markdown 我们新的格式化器API 将重点放在格式上 简化你的代码并提高性能 在app中呈现数据的所有地方 使用格式 最后 自动语法一致 会智能地修正本地化的字符串 以便它们匹配语法性别、计数 和用户自己的称谓 我希望你会喜欢这些新功能 我们期待在你的app找到它们 谢谢你 [音乐]
-
-
2:50 - Attributed String Basics
func attributedStringBasics(important: Bool) { var thanks = AttributedString("Thank you!") thanks.font = .body.bold() var website = AttributedString("Please visit our website.") website.font = .body.italic() website.link = URL(string: "http://www.example.com") var container = AttributeContainer() if important { container.foregroundColor = .red container.underlineColor = .primary } else { container.foregroundColor = .primary } thanks.mergeAttributes(container) website.mergeAttributes(container) print(thanks) print(website) }
-
4:24 - Attributed String Characters
func attributedStringCharacters() { var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let characterView = message.characters for i in characterView.indices where characterView[i].isPunctuation { message[i..<characterView.index(after: i)].foregroundColor = .orange } print(message) }
-
5:12 - Attributed String Runs (Part 1)
func attributedStringRuns() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let runCount = message.runs.count // runCount is 4 print(runCount) let firstRun = message.runs.first! let firstString = String(message.characters[firstRun.range]) // firstString is "Thank you!" print(firstString) }
-
5:49 - Attributed String Runs (Part 2)
func attributedStringRuns2() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let linkRunCount = message.runs[\.link].count // linkRunCount is 3 print(linkRunCount) var insecureLinks: [URL] = [] for (value, range) in message.runs[\.link] { if let v = value, v.scheme != "https" { insecureLinks.append(v) } } // insecureLinks is [http://www.example.com] print(insecureLinks) }
-
6:36 - Attributed String Mutation
func attributedStringMutation() { var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") if let range = message.range(of: "visit") { message[range].font = .body.italic().bold() message.characters.replaceSubrange(range, with: "surf") } print(message) }
-
7:29 - Localized Strings
func prompt(for document: String) -> String { String(localized: "Would you like to save the document “\(document)”?") } func attributedPrompt(for document: String) -> AttributedString { AttributedString(localized: "Would you like to save the document “\(document)”?") }
-
9:34 - Codable Attributed Strings
struct FoodItem: Codable { // Placeholder type to demonstrate concept var name: String } struct Receipt: Codable { var items: [FoodItem] var thankYouMessage: AttributedString } func codableBasics() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let receipt = Receipt(items: [FoodItem(name: "Juice")], thankYouMessage: message) let encoded = try! JSONEncoder().encode(receipt) let decodedReceipt = try! JSONDecoder().decode(Receipt.self, from: encoded) print("\(decodedReceipt.thankYouMessage)") }
-
10:42 - Markdown Decodable Attribute
enum RainbowAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { enum Value : String, Codable { case plain case fun case extreme } public static var name = "rainbow" }
-
11:30 - Custom Markdown Syntax
This text contains [a link](http://www.example.com). This text contains ![an image](http://www.example.com/my_image.gif). This text contains ^[an attribute](rainbow: 'extreme').
-
12:27 - Custom Markdown Attributes
This text contains ^[an attribute](rainbow: 'extreme'). This text contains ^[two attributes](rainbow: 'extreme', otherValue: 42). This text contains ^[an attribute with 2 properties](someStuff: {key: true, key2: false}).
-
13:15 - Attribute Scopes
extension AttributeScopes { struct CaffeAppAttributes : AttributeScope { let rainbow: RainbowAttribute let swiftUI: SwiftUIAttributes } var caffeApp: CaffeAppAttributes.Type { CaffeAppAttributes.self } } func customAttributesFromMarkdown() { let header = AttributedString(localized: "^[Fast & Delicious](rainbow: 'extreme') Food", including: \.caffeApp) print(header) }
-
17:28 - Formatting Dates
func formattingDates() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted() // example: "6/7/2021, 9:42 AM" print(formatted) let onlyDate = date.formatted(date: .numeric, time: .omitted) // example: "6/7/2021" print(onlyDate) let onlyTime = date.formatted(date: .omitted, time: .shortened) // example: "9:42 AM" print(onlyTime) }
-
18:16 - Formatting Dates With Styles
func formattingDatesWithStyles() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted(.dateTime) // example: "6/7/2021, 9:42 AM" print(formatted) }
-
18:36 - Formatting Dates - More Examples
func formattingDatesMoreExamples() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted(.dateTime.year().day().month()) // example: "Jun 7, 2021" print(formatted) let formattedWide = date.formatted(.dateTime.year().day().month(.wide)) // example: "June 7, 2021" print(formattedWide) let formattedWeekday = date.formatted(.dateTime.weekday(.wide)) // example: "Monday" print(formattedWeekday) let logFormat = date.formatted(.iso8601) // example: "20210607T164200Z" print(logFormat) let fileNameFormat = date.formatted(.iso8601.year().month().day().dateSeparator(.dash)) // example: "2021-06-07" print(fileNameFormat) }
-
20:30 - Formatting Intervals
func formattingIntervals() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let now = Date.now // Note on time calculations: This represents the absolute point in time 5000 seconds from now. For calculations that are in terms of hours, days, etc., please use Calendar API. let later = now + TimeInterval(5000) let range = (now..<later).formatted() // example: "6/7/21, 9:42 – 11:05 AM" print(range) let noDate = (now..<later).formatted(date: .omitted, time: .complete) // example: "9:42:00 AM PDT – 11:05:20 AM PDT" print(noDate) let timeDuration = (now..<later).formatted(.timeDuration) // example: "1:23:20" print(timeDuration) let components = (now..<later).formatted(.components(style: .wide)) // example: "1 hour, 23 minutes, 20 seconds" print(components) let relative = later.formatted(.relative(presentation: .named, unitsStyle: .wide)) // example: "in 1 hour" print(relative) }
-
21:39 - Demo - SwiftUI and AttributedString
import SwiftUI struct ContentView: View { @State var date = Date.now @Environment(\.locale) var locale var dateString : AttributedString { var str = date.formatted(.dateTime .minute() .hour() .weekday() .locale(locale) .attributed) let weekday = AttributeContainer .dateField(.weekday) let color = AttributeContainer .foregroundColor(.orange) str.replaceAttributes(weekday, with: color) return str } var body: some View { VStack { Text("Next free coffee") Text(dateString).font(.title2) } .multilineTextAlignment(.center) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, Locale(identifier: "en_US")) ContentView() .environment(\.locale, Locale(identifier: "he_IL")) ContentView() .environment(\.locale, Locale(identifier: "es_ES")) } }
-
23:53 - Parsing Dates
func parsingDates() { let date = Date.now let format = Date.FormatStyle().year().day().month() let formatted = date.formatted(format) // example: "Jun 7, 2021" print(formatted) if let date = try? Date(formatted, strategy: format) { // example: 2021-06-07 07:00:00 +0000 print(date) } }
-
24:23 - Parsing Dates - Strategies
func parsingDatesStrategies() { let strategy = Date.ParseStrategy( format: "\(year: .defaultDigits)-\(month: .twoDigits)-\(day: .twoDigits)", timeZone: TimeZone.current) if let date = try? Date("2021-06-07", strategy: strategy) { // date is 2021-06-07 07:00:00 +0000 print(date) } }
-
25:30 - Formatting Numbers
func formattingNumbers() { // Note: This will use your current locale. Example output is for en_US locale. let value = 12345 let formatted = value.formatted() // formatted is "12,345" print(formatted) }
-
25:36 - Formatting Numbers With Styles
func formattingNumbersWithStyles() { // Note: This will use your current locale. Example output is for en_US locale. let percent = 25 let percentFormatted = percent.formatted(.percent) // percentFormatted is "25%" print(percentFormatted) let scientific = 42e9 let scientificFormatted = scientific.formatted(.number.notation(.scientific)) // scientificFormatted is "4.2E10" print(scientificFormatted) let price = 29 let priceFormatted = price.formatted(.currency(code: "usd")) // priceFormatted is "$29.00" print(priceFormatted) }
-
25:47 - Formatting Lists
func formattingLists() { // Note: This will use your current locale. Example output is for en_US locale. let list = [25, 50, 75].formatted(.list(memberStyle: .percent, type: .or)) // list is "25%, 50%, or 75%" print(list) }
-
26:05 - Receipt Tip View
struct ReceiptTipView: View { @State var tip = 0.15 var body: some View { HStack { Text("Tip") Spacer() TextField("Amount", value: $tip, format: .percent) } } }
-
29:41 - Automatic Grammar Agreement
func addToOrderEnglish() { // Note: This will use your current locale. Example output is for en_US locale. let quantity = 2 let size = "large" let food = "salad" let message = AttributedString(localized: "Add ^[\(quantity) \(size) \(food)](inflect: true) to your order") print(message) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。