大多数浏览器和
Developer App 均支持流媒体播放。
-
Formatter:让数据人性化
节省你的时间和精力:你的 app 显示的数据,包括日期,时间,大小,名称,目录,Numebers 表格或字符串,了解如何正确格式化数据并提供出色的体验。 我们将向你介绍格式化器 API,以及 SwiftUI 如何与字串符一起使用,并向你展示它们如何帮助你完成繁重的格式化数据工作。帮助你了解最佳做法以及如何避免常见错误。
资源
- Displaying Human-Friendly Content
- Expanding Your App to New Markets
- Unicode Date Field Symbol Table
- Xcode Help: Add language plural variants
相关视频
WWDC22
WWDC21
WWDC20
-
下载
(你好 WWDC 2020) (大家好 欢迎参加 WWDC) (Formatter 让数据人性化) 大家好 我是 Karan Misra 今天我会为大家介绍 如何在我们的 app 中格式化数据 使其变得友好和容易理解 每天我们都会使用各种 app 其中有各种各样的数据 有些 app 比如“天气”有各种不同的测量 其他 app 比如“健康” 包含一些带有趋势的关键统计数据 还有一些比如“备忘录” 可能只是显示简单的日期或时间戳 无论日期的显示有多显著 有一点很重要 那就是使用的单位和格式必须是对的 而这一点至关重要 无论 app 只支持一种语言 还是支持几十种不同的语言 此外 我们更新了支持 formatter 的底层算法和数据集 我们的重点之一是为语言和区域的组合 提供更多支持 为什么呢? 因为我们生活在日益全球化的世界中 并且发现世界各地人们所使用设备的语言 与所在的区域可能传统上并没有关联
例如 生活在阿布扎比的人 可能使用系统语言是法语的 iPhone (更多语言和区域组合) 以往这意味着我们要使用 法国的数字格式 把逗号用作小数点 正如这里家庭 App 中显示的
目前在 iOS 14 以及 Mac OS watchOS 和 tvOS 的最新版本中 格式更加符合区域习惯 在这个例子中 就是阿联酋 毫无疑问 这一改进不局限于单一语言或区域 而是一种通用算法 它可以完善 数百对语言和区域组合的格式
我将为大家演示在 app 中 使用 formatter API 的强大效果 这些 API 可以为你节省多少时间和精力 以及你如何使用这些 API 构建灵活的用户界面 (格式化所有内容) 下面我会介绍几个部分的内容 在讲解每一部分时 我会把重点放在 Apple app 的实际示例 按顺序介绍 从具体到一般 我的目的是证明 我们可以使用各种各样的 API 以及简单事情可以很容易 复杂事情可以成为可能 (日期和时间 测量 姓名 列表 数字 字符串) 首先来看日期和时间
先来看一个简单的例子 在“备忘录” app 中 在每条备忘录上方 你可以看到上次更改的日期和时间 我们看看这是如何做到的
这里我们想要显示一个完整的日期 包括天、月和年以及时间 这个可以使用 dateFormatter 中的 预定义格式来完成 日期使用 medium 格式 时间使用 short 格式 注意 这里的单词"at"是自动添加的 它是 medium 长度格式的一部分 正如我说的 简单事情很容易
下面我来证明更复杂的事情也是可能的 这里是“设置”中的屏幕使用时间用户界面 我们要显示星期以及日和月 但不显示年 没有一种预定义格式能满足这一要求 但是通过指定模板就可以实现
还是屏幕使用时间的例子 在许多其他用户界面中你也会看到 如果你只是想要星期的缩写 你可以使用自定义模板进行请求 可以看到 模板机制真的很强大 因此 我们再详细介绍下模板 (模板) Unicode 发布各种技术报告 包括这个有关日期的报告 事实上这一系列包含七部分 而我很自豪地说 Apple 作为 Unicode 成员 是关键撰稿人之一 这是一个很棒的模板参考 其中有许多解释和示例 之前在查找星期时 我向下滚动 找到了这一部分 这里看到 我可以选择大写"E" 小写“e”或者小写“c” 那我该使用哪一个呢? 你可能还记得 我在图表中使用过这些星期标签 所以它们是独立的 不是更长格式的一部分 因此我为模板选择小写"c"
模板选择好了 下面我们来看它是如何运行的
这里可以看到小写"c"不同长度的输出 注意 这里包含五个 c 的模板 是我们需要的 因为我们想要最简单的形式 此外 需要注意这些长度的解释方式 因语言不同而变化 这里可以看到 五个长度在英语中分别对应不同的输出 而对于阿拉伯语或者日语却不是这样 因此 选择对的模板很重要 考虑所有支持的区域 因为有些区别只是针对某些语言或区域 这也是为什么我们需要选择独立格式 这点非常重要 可能对于英语没什么区别 但是其他语言就不同了 关于模板 另外一件需要注意的事情是 字段的顺序无关紧要 这个模板和这个是一样的 而它和这个又是一样的
模板接收我们感兴趣的数据 而 formatter 则负责把这些数据整合成 所在地区可以理解的内容
因此 美式英语中 你会看到有个逗号 而英式英语中 月在日后面 而不是反过来 最后一个例子 在日语中 添加了月和日对应的文字 以便在日语中生成有意义的日期
最后 需要指出的是 dateFormatter 有一个叫 dateFormat 的属性 任何时候我们 都不应把模板直接设成该属性 因为会导致按表面理解 而且十有八九会生成错误结果 我们应始终从模板开始创建日期格式
还有一些 API 可用于格式化日期和时间
DateComponentsFormatter 帮助你格式化组件 比如持续时间的组件 另外 使用 DateIntervalFormatter 你可以格式化时间范围 比如“健康” app 中的这个例子 它甚至可以避免开始和结束日期中的 重复元素
最后 RelativeDateTimeFormatter 是一个功能强大的工具 通过它 过去或将来的日期可以自动调节 下面我们来看测量 如果你的 app 包含各种测量 那么你需要 MeasurementFormatter 这个“朋友” 接下来我为大家介绍 “天气” app 中的几个例子
首先来看温度 注意 在所有这些例子中 我为测量数据设置的是公制单位 但右边显示的是 针对美国市场本地化之后的数据 它们使用的是英制单位 因此 16 摄氏度的温度显示为 61 华氏度 速度也是一样 这里的速度值转换成了美国的英里每小时 还有气压也是同理 我举这些例子 是因为你的测量数据源 可能采用了公制单位或其他单位 而 MeasurementFormatter 可以为你处理繁重的转换任务 不仅包括单位的转换 也包括语言和区域的转换 我刚刚只是为大家介绍了 三个不同测量数据的例子 MeasurementFormatter 还支持很多很多种数据 如果它不支持你想使用的单位 你还可以自定义单位 更多详细内容 请观看 WWDC 2016 《测量和单位》的精彩视频
下面我们再来看姓名
姓名是 app 中 显示的最个人化的数据之一 当我们在屏幕上看到自己姓名时 都会有强烈的反应 因此设置正确的姓名格式 对于留下好印象绝对很重要 幸运的是 PersonNameComponentsFormatter 以下简称“姓名 Formatter” 可以轻松打造正确格式
我们只需加入组件对象 请求格式化字符串即可
默认情况下 姓名 Formatter 会选择 medium 格式 它包括所有基本的组件 比如姓和名
我们也可以请求 short 格式 取决于用户的使用偏好 可能是昵称或者姓名的缩写
缩写格式一般用于特定用途的情况 比如 这里的字母组合 过去几年中 越来越多的 app 使用了字母组合 下面我们更深入地研究一下
在没有头像或者照片时 字母组合是个不错的选择 这样的用户界面依然让人觉得友好且醒目 姓名 Formatter 设计时 考虑到了字母组合 不过有一点需要注意 并不是所有的姓名都能生成字母组合 有些姓名缩写太长 对应的用户界面可能放不下 Swift 能基于长度检查轻松限制字母组合 Swift 的计数是基于用户可见的字符 而不是 Unicode 代码点 因此对于不同语言间的约束效果很好 当然 字符数不能决定 字符串展示的高度或宽度 因此我们仍需确保字符串能够放得下
当字符数太多或者字符串太大放不下时 我们需要使用备选方案 比如使用通用图标来代替字母组合
最后 需要注意的是 姓名 Formatter 内置许多智能功能 在格式化姓名时 使用好多条信息 比如用户的语言设置 联系人设置比如姓名顺序以及姓名本身
例如 这里 iPhone 显示了一个日语姓名 系统语言是英语 我特意选了这个例子来证明 姓名 Formatter 可以 在所有情况下恰当处理姓名 即便姓名所用的语言 不同于 iPhone 的系统语言
在中文、日语和朝鲜语书就的姓名中 名始终在姓后面 这是这类姓名的唯一适当格式 下面我们介绍如何制作格式正确的列表 每当我们需要显示项目清单 将其格式化为人类可读文本时 我们都可以使用 ListFormatter 接下来我为大家演示它如何节省时间 以及如何通过正确处理细微之处 让我们的 app 看起来美观 API 简直不能再容易了 我们只需指定一个字符串数组 或者其他我们想要合并到列表的对象 之后我们就得到一个串联列表 下面我们看下这样做的微妙益处 这里是键盘设置用户界面 我们来格式化一个非常简单的列表 只有两个元素 语言名称“英语”和“西班牙语” 在英语中 只需要在中间插入单词"and" 即便语言的顺序变了 还是一样 但是在本地化的西班牙语用户界面中 我们可以看到西班牙语的"and" 但是你知道吗? 西班牙语的"and" 是根据语境变化的 注意 当元素顺序改变后 西班牙语的"and"也发生了变化 事实上 在 iOS 14 以及 macOS tvOS 和 watchOS 的最新版本中 我们更新了 ListFormatter 使其符合多种语言的 语法规则 包括西班牙语 这就是我们应该 尽可能使用 formatter API 的 诸多原因之一 根本的执行方式更新和完善后 它们不仅可以抽掉 很多复杂的东西 免费提供本地化 而且无需更改一行代码 我们的 app 行为就能得到改善 接下来我们介绍两种通用数据类型 首先来看数字 这里有个快速测验 屏幕上的数字是什么? 这个问题有点棘手 取决于我们使用的语言和我们所在的区域 我们可能 会把它理解成 32768 或者 32.768 这里计算器上的小数点按钮暴露了答案 我们可以知道它实际是 32.768
而我们可以这样格式化它 超级简单
如果 app 语言设置为阿拉伯语 区域设置为埃及 会怎样呢? 这里可以看到 NumberFormatter 来处理所有繁重的任务 确保所有内容都进行了恰当的本地化
此外还需注意 如果我们需要使用某些符号 而这些符号根据语言或区域而变化时 我们可以使用一些简便的方法 来获取 percentSymbol 和 decimalSeparator 事实上 还有很多未在这里显示的 简便的 API 可以用于获取更多符号
NumberFormatter 支持多种不同的格式 包括百分比格式 同时 我们也希望 它可以处理不同语言和区域的细微变化 例如 我们可以看到 在土耳其语中它正确格式化了数字 把百分比符号放在了数字前面 我就不再介绍 NumberFormatter 更多的内容了 因为真的有太多实用功能了 不过请务必查看说明文档或者 在 Xcode Playground 中尝试使用它 最后 我们来讲字符串 虽然多样的 formatter API 涵盖各种在所有 app 中 常见的格式化数据类型 你的 app 数据 可能没有标准的 formatter 例如 没有标准的 formatter 来格式化照片数量 下面我们用一个例子说明如何格式化 做起来真的非常简单 我们像通常那样在 SwiftUI 中 使用文本元素 指定文本 关于代码 我们只需要写这些
然后我们只需要确保有一个包含 对应字符串的 stringsdict 文件 想要了解更多有关 stringsdict 的内容 甚至是本地化的一般内容 请务必查看这里 URL 中的说明文档 比如说我们的译员 已经把文本翻译成了英语和阿拉伯语 我们来看下结果 当选中一张照片时 注意尽管英语中是数字“1” 但在阿拉伯语中 是大写数字 因为这更自然一些
当选中两张照片时 在英语中还是阿拉伯数字 但是"photo"变成了"photos" 符合英语语法习惯 另一方面 在阿拉伯语中 双数则遵循阿拉伯语语法
选中三张照片时 英语和阿拉伯语的计数相似 数字后面是复数形式的"photo"
一直到数字十都是这样 如果是 11 张照片或更多 阿拉伯语语法规定 使用不同的复数形式 现在你可能 想要学习下一百种语言的语法规则了 反正我想 不过我想告诉大家的是 如果你使用 stringsdict 把字符串以正确的方式进行本地化 并且在 app 中尽可能使用 formatter 你的 app 就会很出色 无论你只支持 一种语言还是一百种语言 而且所有这些都是代码 你只需要写一次 无论你的 app 支持多少种语言 无论是在现在还是将来
最后 为让大家 更好地了解我刚介绍的所有 formatter 以及方便大家探索 我们提供了范例 app 你可以下载并使用 最后祝大家格式化愉快
-
-
2:25 - Dates and times
// Dates and Times // Date with Day/Month/Year and Time let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short dateFormatter.string(from: Date()) // Day of Week + Date + Month let dateFormatter = DateFormatter() dateFormatter.setLocalizedDateFormatFromTemplate ("MMMMdEEEE") dateFormatter.string(from: Date()) // Abbreviated Day of Week let dateFormatter = DateFormatter() dateFormatter.setLocalizedDateFormatFromTemplate ("ccccc") dateFormatter.string(from: Date())
-
5:56 - Date components formatter
// Dates and Times // Date and Time Components let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated let components = DateComponents(hour: 2, minute: 26) formatter.string(from: components) // Date and Time Intervals let formatter = DateIntervalFormatter() formatter.dateTemplate = "dMMM" formatter.string(from: startDate, to: endDate) // Relative Dates and Times let formatter = RelativeDateTimeFormatter() formatter.dateTimeStyle = .named formatter.localizedString(from: DateComponents(day: -1))
-
6:29 - Measurements
// Measurements // Temperature let formatter = MeasurementFormatter() let temperature = Measurement<UnitTemperature> (value: 16, unit: .celsius) formatter.numberFormatter.maximumFractionDigits = 0 formatter.string(from: temperature) // Speed let speed = Measurement<UnitSpeed> (value: 14, unit: .kilometersPerHour) formatter.string(from: speed) // Pressure let pressure = Measurement<UnitPressure> (value: 1.01885, unit: .bars) formatter.string(from: pressure)
-
7:49 - Names
// Names let formatter = PersonNameComponentsFormatter() var nameComponents = PersonNameComponents() nameComponents.familyName = "Iwasaki" nameComponents.givenName = "Akiya" nameComponents.nickname = "Aki-chan" // Full Name formatter.string(from: nameComponents) // Short Name: Respects User Preferences formatter.style = .short formatter.string(from: nameComponents) // Abbreviated Name formatter.style = .abbreviated formatter.string(from: nameComponents)
-
8:31 - Abbreviated name (monogram)
// Abbreviated Name: Monogram formatter.style = .abbreviated let monogram = formatter.string(from: nameComponents) if (monogram.count <= 2) { // Use Monogram } else { // Use Icon }
-
9:23 - Name formatter
// Names let formatter = PersonNameComponentsFormatter() var nameComponents = PersonNameComponents() nameComponents.familyName = "岩崎" nameComponents.givenName = "晃也" nameComponents.nickname = "あきちゃん" // Full Name formatter.string(from: nameComponents) // Short Name: Respects User Preferences formatter.style = .short formatter.string(from: nameComponents) // Abbreviated Name formatter.style = .abbreviated formatter.string(from: nameComponents)
-
10:15 - Lists
// Lists // English Localization let items = [ "English", "French", "Spanish" ] ListFormatter.localizedString(byJoining: items) let items = [ "English", "Spanish" ] ListFormatter.localizedString(byJoining: items) let items = [ "Spanish", "English" ] ListFormatter.localizedString(byJoining: items) // Spanish Localization let items = [ "Inglés", "Español" ] ListFormatter.localizedString(byJoining: items) let items = [ "Español", "Inglés" ] ListFormatter.localizedString(byJoining: items)
-
12:01 - Numbers
// Numbers let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.string(from: 32.768) // French (France) let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.string(from: 32.768) // Arabic (Egypt) formatter.percentSymbol formatter.decimalSeparator
-
12:33 - Numbers formatter
// Numbers let formatter = NumberFormatter() formatter.numberStyle = .percent formatter.string(from: 0.71) // English (US) let formatter = NumberFormatter() formatter.numberStyle = .percent formatter.string(from: 0.71) // Turkish (Turkey)
-
13:24 - Strings
// Strings var body: some View { Text("\(photosCount) Photos Selected") }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。