大多数浏览器和
Developer App 均支持流媒体播放。
-
为 Apple Watch 创建复杂功能
当您在 Watch app 中添加复杂功能时,人们可以直接从其表盘访问全部最新信息。 我们将向您展示如何从头开始创建和构建复杂功能,并向您介绍多种复杂功能。了解如何构建时间表,使用族和模板,以及探索最佳实践,以打造透彻的复杂功能体验。
资源
相关视频
WWDC20
-
下载
(你好 WWDC2020)
你好 欢迎来到 WWDC
(为 Apple Watch 创建复杂功能) 大家好 我是 Michael Kent 我是一名 ClockKit 工程师 在本视频中 我们将了解 Apple Watch 的复杂功能 以及如何将它们添加至你的 app 复杂功能是把你的内容 展现在用户眼前的绝佳方式 只需快速一瞥钟面 用户就能看见它们 在 watchOS 7 上 我们让创造优秀的复杂功能体验 变得比以往更容易 这就是为什么复杂功能在 Watch 上 与 app 体验一样重要的原因 它们是将你的内容 展现在用户眼前的最容易之法 要如何将它们添加至你的 app? 我们会来讨论时间线 即复杂功能的支撑结构 你需要进行的所有琐碎工作 你要如何将数据置入复杂功能 你要如何利用新的 watchOS 7 性能 让你的 app 提供多于一种的复杂功能 然后我们还会查看一个示例 我们先从时间线开始说起吧 时间线在复杂功能里扮演着主要角色 它们代表随时间推移的复杂功能数据 因为时间线提供了所有复杂功能的数据 ClockKit 可以向你的 app 请求一次所有的必要信息 来将你的复杂功能 驱动至你所指定的一个日期
当然 如果你的 app 获得了新数据 而你需要给我们更多条目 你可以请求 ClockKit 延长你的时间线 或彻底使之失效 我和一些团队成员正在努力开发一款 app 来追踪夏威夷周围的鲸鱼出没 在这个 app 里 你可以看到毛伊岛周围 在几个观察站看到的鲸鱼出没 来看看可能来自这个 app 的 时间线示例吧 在这里 我有一个观鲸之旅的日程表 这是一天之内不同地点的三项观鲸活动 现在这个则是该日程表以时间线的形式 可能显示在 ClockKit 里的样子 你会注意到几处不同 首先 每个活动都只和一个时间点 而非一个时间范围相关联 这是因为你的复杂功能 在到达下一个活动的日期前 只会显示一个条目 第二点 你会看到每个活动的关联时间 都比实际开始时间还要早 这是为了事先通知用户 以便如果他们感兴趣的话 能来得及搭乘观光船 不是所有数据都会这样运作 比方说 气温预测 就应该要在其真正的预测时间记录条目 最上方的是我想在当前时间 要这个复杂功能显示的数据 随着一天的推进 你可以看到信息跟着时间而更新 直到今天再没有任何观光活动为止
现在就来了解一下你究竟要如何 指定你的复杂功能显示些什么
复杂功能家族即是将它们 归类进不同视觉分组的方法 图形家族早在 watchOS 5 就被推出了 不过当时没有 Graphic Extra Large 这是 watchOS 7 的新功能 这些家族允许更多以视觉为主的数据呈现 不同的表盘使用不同的复杂功能家族 以下是一些示例
图文表盘使用 Graphic Corner 和 Graphic Bezel 以及 Graphic Circular 它也和 Graphic Rectangular 一起 出现在图文模块表盘上
这就是 Graphic Extra Large 现在超大表盘支持它 模块表盘上有 Modular Small 和 Modular Large 动态表盘上则有 Utilitarian Small Flat 实用表盘上有 Utilitarian Small 和 Large 最后 如果你的复杂功能不支持 Graphic Extra Large 家族的话 超大表盘则会使用 Extra Large 这个家族会一直用在 Series 3 手表的超大表盘上 在理想情况下 你应该 让自己的复杂功能尽可能地支持 更多复杂功能家族 这样的话 你的所有用户 都能够在任何自己喜爱的表盘上 使用你的复杂功能 复杂功能模板代表家族内的不同视觉布局 这是一个示例 上面只显示了某些不同家族的 一些复杂功能模板 虽然它们和特定家族相关联 但是每个模板实则继承自一个通用基类 即 CLKComplicationTemplate
你可以从这么多选择里进行挑选 以便最好地显示你的数据 你可以在开发者网站上的 说明文档里 找到更多可用模板 现在我们来看一下 这个提供时间线的代码示例 我要说明的第一个类型是 CLKComplicationTimelineEntry 你通过给我们一个这些的列表 来填充你复杂功能的时间线 每一个都代表了你的复杂功能 在特定时间点里 应该是什么样子 它只有两个属性 第一是日期 这是该条目应该变得可见的日子 第二是 complicationTemplate 这是包含你要为此条目显示的数据的模板 回到我们之前的时间线示例 在早上 6 时 我们会有一个 填充了日期的时间线条目 即早上 6 时 以及一个代表着马亚拉伊港观光活动的 complicationTemplate 在上午 9 时 日期和模板就会改变 这在余下的一天里都会如此 你与 ClockKit 的主要互动 是通过一个你创建的对象进行的 该对象符合 CLKComplicationDataSource 在这个协议里 唯一需要的方法是 getCurrentTimelineEntry for complication withHandler
要实施这个方法 你只需为给定的复杂功能 调用当前时间线条目的处理程序 我们稍后再讲这个复杂功能是什么
对于某些复杂功能来说 一个当前条目就已足够 比如说棒球比赛的当前分数 或者是 Apple 的当前股价 但是如果你的复杂功能 可以提供包括未来条目的时间线 你还将需要实施这两种方法 getTimelineEndDate for complication withHandler 它可以指定你想为多远的未来提供条目 以及 complication after date limit withHandler 的 getTimelineEntries 这有点拗口 但你只需要做一件事 那就是 尽可能地 为你既定日期后的时间提供条目 在限制内越多适合数据的条目越好 “日期” 代表着我们已经拥有的 最后一个时间线条目 “限制” 则确保我们一次 只能获取我们所需的条目数量 如果你提供更多的话 我们将删除所有超过限制的条目 那么如果你的 app 获得了新数据 而你需要重新加载时间线呢? 对此我们有两种选择 第一种 如果你的数据完全改变 而你提供过的所有条目都无效了 你可以在 CLKComplicationServer 的 共享实例上为复杂功能调用 reloadTimeline
比方说 一场始料未及的暴风雨席卷了毛伊岛 我们所有的赏鲸活动将会被取消 我们需要使我们的 整个时间线都失效来反映这点
但是如果你之前所提供的条目依然有效 而你只是想让我们知道 你可以提供更多条目 那你就可以为复杂功能 调用 extendTimeline
ClockKit 只会为用户表盘上的 当前的复杂功能跟踪时间线 我们通过 activeComplications 属性 让你知道这点
你永远都不会直接创建 CLKComplication 对象 你总是从这里得到它们 或者传入你所实施的数据源方法 现在就来聊聊 创建这些复杂功能模板需要什么吧
复杂功能有一些限制 手表屏幕已经够小了 复杂功能比它还小 你也许想要在不同的模板 或者复杂功能家族里显示相同的字符串 而它们全都有着大不相同的布局限制 为交付最好的体验 我们有个叫做数据提供者的概念 它们允许你在许多不同的地点 和情境里表达同一信息 信息由 ClockKit 为你进行格式化 因为我们会替你处理 复杂功能的具体布局详情 而我们需要足够的信息才能灵活行动 我来向你展示我说的情况吧 我们会先从一些文本开始 手表的核心是时间 所以显示日期是你经常要做的一件事 这个写长日期的方法不错 但是在绝大多数的复杂功能情境里 你最终会写出这样的东西 这样的文本不是很清楚 我们要怎样让它变得更好呢? 你可以使用一个叫做 CLKDateTextProvider 的数据提供器 你声明你想要的内容 在这个场景里是 “9 月 23 日 星期三” 然后我们会尽己所能来显示它 如果空间不足的话 我们会采用较短的版本
最终可能就会删去 比较无用的信息 比如星期几 如有必要 可能会一直删到 只剩下该月的天数
以下是它在代码里的样子 你给它提供一个日期 创建一个 CLKDateTextProvider 以及你偏好的最长日历单位 如果你需要回答问题 比如说 “这个日期距离我当前时间还有多久?” 或者“这个日期距离某个日期还有多久?” 你可以使用 CLKRelativeDateTextProvider 在第一道问题的场景里 一个相对日期文本提供器会自动更新文本 反映出当前的时间 这甚至可以精确到秒 而你无需做任何事 你可以将它用于不同的格式 显示像这样的字符串 从现在到日落的时间 或者你的面团还要多久才能发酵完成
在后一个例子中 你可以提供一个结束日期 你可以指定“计时器”风格 并提供你想要显示的单位 这些是其他的文本提供器 比如时间文本提供器 它和日期文本提供器的作用非常相似 但是它显示的是时间而非天数 时间间隔文本提供器 则是用来显示一个范围内的时间 比如上午 7 时 30 分至上午 9 时 最后的简单文本提供器 则会显示你想要的任何字符串 你也可以给它提供较短的版本 好让该字符串 在受限的空间里自行选用 图像提供器和文本提供器很相似 它们所含的数据 都能用在几种不同的情境里
最大的分别是这些情境 比较专注于表盘的颜色 许多表盘允许用户自定义这个属性 而复杂功能必须与它一致匹配
某些表盘将单一颜色 应用到整个复杂功能的图像上 而其他表盘可能允许 由背景和前景所组成的多色图像 你可以在底部这里看到一些例子 比如计时器、日出和秒表
CLKImageProvider 是实现这一切的对象 图形复杂功能家族允许显示全彩图像 因此它们的模板要求的是 CLKFullColorImageProviders 可是在某些情境里 这些图形复杂功能是被着色的 如果你只提供一个全彩图像 我们会帮你降低饱和度以应用这种着色 如果你想否决这种行为 CLKFullColorImageProvider 会允许你设置一个 CLKImageProvider 以作为后备 你可以在这里看到 为相同的图像 提供 CLKImageProvider 之后 这个复杂功能变亮了 它现在拥有了 和左上角其他复杂功能一样的白色 欲了解更多关于适应复杂功能着色的信息 请查看这个 2019 年的精彩讲座 (探索图形复杂功能的着色) 量表提供器是用来概括必要数据 以显示图形型量表或进度的方法 它们能适应不同的复杂功能布局 就像这里所看到的角落和中心一样
你可以自定义量表的颜色或渐变度 以及填充系数 你只需要花很少的功夫即可
如果你想要一个 能够实时更新其填充系数的量表 你可以使用 CLKTimeIntervalGaugeProvider 它能让你指定开始和结束日期 然后它会自动更新以显示当前时间的进度
你现在可以在复杂功能里使用 SwiftUI 了 这是 watchOS 7 的新功能 所有使用 CLKFullColor ImageProviders 的复杂功能模板 都拥有 SwiftUI 视图选项
这样你就可以轻易地重用 app 里其他部分的部件 并且你的复杂功能更容易脱颖而出 你也可以更容易地创建 独特的复杂功能以吸引你的用户 (在 SwiftUI 里构建复杂功能 WWDC20) 若想进一步了解 在复杂功能里使用 SwiftUI 的详情 我们有一个很好的视频 详细地说明了这些
如果说你的 app 充满了 对用户有用且相关的信息 而你想让他们快速方便地访问它们? 这也是 watchOS 7 的新性能 你现在可以从单一 app 里 提供多种复杂功能了 这是将你的数据呈现在用户面前的好方法 用户只需在手腕上快速一览即可看到 你甚至可以将你的复杂功能 放到表盘上并共享它
多种复杂功能的支持 会在你所实施的 CLKComplicationDataSource 中被声明 这里有两个相关的方法 第一 getComplicationDescriptors withHandler 它会说明你的 app 所支持的复杂功能当前列表
第二个是 handleSharedComplicationDescriptors 当某个包含了你部分复杂功能的 表盘与这个手表共享时 这个方法就会被调用 以提醒你 ClockKit 会开始帮它们请求时间线条目 CLKComplicationDescriptor 是你定义复杂功能的方法 它包含了一个你的 app 里独特的标识符
一个在编辑表盘时显示的 displayName 一个被这个复杂功能所支持的 复杂功能家族列表 以及两个可选的互斥属性 它们允许你包含以后会用到的自定义数据 即 userInfo dictionary 或者 userActivity
我们的观鲸 app 支持着几种不同的复杂功能 有些是显示每个地点看到鲸鱼的次数 一个是快速记录新看到的鲸鱼的复杂功能 还有一个是显示全季数据的复杂功能 来看看它们是怎样运作的吧
在 getComplicationDescriptors withHandler 方法里 你会创建一个 ComplicationDescriptors 的数组 并用它来调用处理程序 在这里 我们会在数据模型里遍历每一个观察站 为它们每一个创建 ComplicationDescriptor
这些将会用来显示每个地点的观鲸信息
接下来 我们要给复杂功能添加一个描述符 以便记录一次看到鲸鱼的事件 最后再加一个描述符以便浏览全季数据 你会注意到这最后一个复杂功能 只支持 graphicRectangular 家族 而其他复杂功能则支持全部家族 由于季节数据复杂功能需要显示很多信息 graphicRectangular 的巨大画布 是唯一适用于这个复杂功能的选择 如果你需要使这个列表失效 你可以在 CLKComplicationServer 上调用 reloadComplicationDescriptors 然后我们还会再调用这个方法 在我们的观鲸示例中 也许我们只显示了 用户所喜爱观鲸站的复杂功能 如果他们更新那些列表 那我们也应该要更新复杂功能
如果你更新了列表并移除了对某个用户 在当前表盘上有的复杂功能的支持 我们会继续请求你提供 该复杂功能的时间线条目 在这种情况下 请尽你所能 确保自己可以继续提供有用的信息
注意一下 这个方法 与 CLKComplicationServer 的 reload TimelineForComplication 方法不同 前者会重新加载你的 app 所提供的复杂功能列表 后者则会重新加载特定的复杂功能时间线
和往常一样 当用户轻点你的复杂功能 我们就会启动你的 app 如果被轻点的复杂功能的标识符 是用 userActivity 创建的 那么它就会在启动时被用上 无论是哪种情况 我们都会把一些条目 传进 userInfo dictionary 比如说 该复杂功能 当前可见的时间线条目日期 以及复杂功能的标识符
当然 这个 dictionary 也会包含 被 complicationDescriptor 所定义的开发者指定条目
我们讨论过如何描述你所支持的复杂功能 但是你又要如何知道 Clockit 在为哪个复杂功能请求时间线条目?
这里是我们先前看过的几个方法 getCurrentTimelineEntry 以及 getTimelineEndDate 每一个都需要 CLKComplication 类型的复杂功能参数
这看起来很像 complicationDescriptor 但是 当 complicationDescriptor 负责定义复杂功能支持什么 这个对象则代表一个复杂功能 在用户表盘上的具体实际实例 所以它不会有一个所支持家族的列表 反而它会拥有一个属性 该属性包含现实复杂功能实例的家族 当然 你使用 userInfo dictionary 或 userActivity 给描述符提供的信息 也会被包含在这里 我们有一个叫做 默认复杂功能标识符的东西 你应不应该用这个东西 来识别你的一个复杂功能呢? 当然可以 但这不是它的主要用途 如果你拥有一个 watchOS 7 之前的复杂功能 而用户在他们的表盘上有它 又或者用户与你的复杂功能共享了表盘 但是选择移除关联数据 那么你就会被问起 有以下标识符的复杂功能 CLKDefaultComplicationIdentifier 即便你并没有在你的 复杂功能描述符列表上明确支持它 这点很重要 你应该要支持这个复杂功能 否则你的用户肯定会好奇 为什么他们会在表盘上看到 一个来自你的 app 并且已经有故障的复杂功能 那么你该怎么办呢?
你可以显示与你的复杂功能 在 watchOS 7 之前所显示的同样的信息 又或者 你可以选择显示 来自你 app 最受欢迎或最相关的信息 又或者 你可以只显示 app 的图标 这样的话 至少用户会知道 那个复杂功能是什么 并大致了解如果轻点它的话会发生什么 现在来看看我们观鲸 app 的更多细节吧 我们之前就看过这个方法 它会为既定的复杂功能创建当前条目
以下是我们还未看过的另一个方法 getLocalizableSampleTemplate for complication withHandler 也是 CLKComplicationDataSource 协议的一部分
它正在请求的模板 会用在表盘编辑里的复杂功能选择过程上 以及用在已配对 iPhone 的 Apple Watch app 上 这个模板应该包含示例数据 因为我们只会为每个复杂功能 发出一次请求并缓存结果
我们已经看到 createTimelineEntry 方法被用过几次了 它挺简单的 它会先创建一个模板 再创建该模板的一个时间线条目 和传入该条目的日期
让我们来更深入了解 createTemplate 方法吧 我们先来抓取一些 我们需要在创建模板时重用的数据 我们从这个复杂功能里提取观察站信息 记住 我们用它创建了描述符 然后我们再创建可以用在不同情况下的 FullColorImageProvider 接着我们创建用来记录观鲸的 SimpleTextProvider
最后我们再创建一个 可以为给定家族返回默认模板的闭包 这是面对突发情况时 我们可以备用的闭包 比如说 当我们收到 默认复杂功能标识符模板的请求时
要确定创建哪个模板 我们先来打开家族和标识符 针对 Graphic Rectangular 中的 SeasonData 复杂功能 我们会创建一个 GraphicRectangularFullView 模板 它有一个 SwiftUI 视图 能够漂亮地显示一张数据直观图表
至于 graphicCircular 中的 “记录看到鲸鱼”的复杂功能 我们则会使用之前创建的 ImageProvider 和 TextProvider 来创建一个 GraphicCircularStackImage 模板 至于其他任何种类的 graphicCircular 复杂功能的话 我们会展示另一个 SwiftUI 视图 它会显示某个地点的鲸鱼出没信息 GraphicExtraLarge 和 GraphicCorner 也很相似 它们各自都会返回一个复杂功能模板 该模板会包含来自站点数据的 相应 text 和 imageProviders 其实还有很多为每个 所支持的复杂功能 正确提供模板的其他例子 但是它们或多或少都很相似 所以我就不一一说明了 当然 如果我们被问到一个复杂功能 而我们认为我们做不了什么 比如说 默认复杂功能标识符 那我们就返回一个默认模板即可 以下是某些那种复杂功能运行时的样子 我们有两种不同的 Graphic Rectangular 复杂功能 两种 Graphic Circular 复杂功能 我们使用 SwiftUI 来绘制这些美丽图表 而这些复杂功能在全彩 和着色情境下好看极了 在 watchOS 7 里 你拥有很多 将数据带到用户面前的表盘上的机会 所以 制作复杂功能吧 让数据以时间线的形式显示 让你的复杂功能切合最新情况 使用 SwiftUI 创建优秀 且自定义的复杂功能内容 最后 被问到的时候 你要确保 可以支持默认复杂功能标识符 如果你还想要了解更多 关于构建优秀复杂功能的信息 你可以查看这些视频 谢谢你的收看
-
-
4:54 - CLKComplicationDataSource - Required Methods
// CLKComplicationDataSource - Required class ComplicationController: NSObject, CLKComplicationDataSource { func getCurrentTimelineEntry( for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) { // Call the handler with the current timeline entry handler(createTimelineEntry(forComplication: complication, date: Date())) } }
-
5:16 - CLKComplicationDataSource - Timeline Support
// CLKComplicationDataSource - Timeline Support extension ComplicationController { func getTimelineEndDate( for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) { handler(timeline(for: complication)?.endDate) } func getTimelineEntries( for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { handler(timeline(for: complication)?.entries(after: date, limit: limit)) } }
-
8:11 - CLKDateTextProvider initialization
let longDate: Date = DateComponents(year: 2020, month: 9, day: 23).date ?? Date() let units: NSCalendar.Unit = [.weekday, .month, .day] let textProvider = CLKDateTextProvider(date: longDate, units: units)
-
8:49 - CLKRelativeDateTextProvider initialization
let timerStart: Date = … let units: NSCalendar.Unit = [.hour, .minute, .second] let textProvider = CLKRelativeDateTextProvider(date: timerStart, style: .timer, units: units)
-
13:16 - CLKComplicationDataSource - Multiple Complication Support
// CLKComplicationDataSource - Multiple Complication Support extension ComplicationController { var descriptors : [CLKComplicationDescriptor] = [] var dataDict = Dictionary<AnyHashable, Any>() for station in data.stations { dataDict = [“name": station.name, “shortName": station.shortName] descriptors.append( CLKComplicationDescriptor( identifier: station.name, displayName: station.name, supportedFamilies: CLKComplicationFamily.allCases, userInfo: dataDict)) } descriptors.append( CLKComplicationDescriptor( identifier: "LogSighting", displayName: "Log Sighting", supportedFamilies: CLKComplicationFamily.allCases)) descriptors.append( CLKComplicationDescriptor( identifier: "SeasonData", displayName: "Season Data", supportedFamilies: [.graphicRectangular])) // Call the handler with the currently supported complication descriptors handler(descriptors) }
-
17:09 - CLKComplicationDataSource - Sample Templates
func getLocalizableSampleTemplate( for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) { let template = createSampleTemplate(forComplication: complication) handler(template) }
-
17:33 - Whale Watch - Entries
func createTimelineEntry( forComplication complication: CLKComplication, date: Date) -> CLKComplicationTimelineEntry? { guard let template = createTemplate(forComplication: complication, date: date) else { return nil } return CLKComplicationTimelineEntry(date: date, complicationTemplate: template) }
-
17:44 - Whale Watch - Templates
func createTemplate( forComplication complication: CLKComplication, date: Date) -> CLKComplicationTemplate? { var station: Station? = nil if let stationName = complication.userInfo?["name"] as? String { station = data.stations.first(where: { $0.name == stationName }) } let image = UIImage(named: "Spout-small")! let spoutFullColorImageProvider = CLKFullColorImageProvider(fullColorImage: image) let logSightingTextProvider = CLKSimpleTextProvider( text: "Log Sighting", shortText: "Log") let defaultTemplate: (CLKComplicationFamily) -> CLKComplicationTemplate = { family -> CLKComplicationTemplate in // Return a default complication template for the given family } switch (complication.family, complication.identifier) { case (.graphicRectangular, "SeasonData"): return CLKComplicationTemplateGraphicRectangularFullView( ChartView( seriesData: data.last7DaysSightings, seriesColor: .turquoise) case (.graphicCircular, "LogSighting"): return CLKComplicationTemplateGraphicCircularStackImage( line1ImageProvider: spoutFullColorImageProvider, line2TextProvider: logSightingTextProvider) case (.graphicCircular, _): guard let station = station else { return defaultTemplate(.graphicCircular) } return CLKComplicationTemplateGraphicCircularView( SightingTypeView(station: station)) case (.graphicCorner, _): guard let station = station else { return defaultTemplate(.graphicCorner) } return CLKComplicationTemplateGraphicCornerTextImage( textProvider: station.timeAndShortLocTextProvider, imageProvider: station.whaleActivityFullColorProvider) case (.graphicExtraLarge, _): guard let station = station else { return defaultTemplate(.graphicExtraLarge) } return CLKComplicationTemplateGraphicExtraLargeCircularStackText( line1TextProvider: station.timeAndLocationTextProvider, line2TextProvider: station.shortLocationTextProvider) default: return defaultTemplate(complication.family) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。