WatchOS开发教程之六: 表盘功能开发

WatchOS表盘开发流程, 工作原理, 表盘类型和表盘模板类型总结。

Contact



表盘开发简介(Complications)

Complications是 WatchOS 2.0引入的,它是表盘上展示的小元素,可以快速访问常用数据。当你的应用支持Complications功能开发后, 便可以在表盘展示你应用的指定的数据,且支持直接从表盘唤醒你的 App。系统提供了一些内置的Complications, 比如天气、日历、活动以及更多类型的数据提供内置复杂功能。

这里所指的表盘开发, 是对表盘元素(Complications)的开发, 而不是对整个表盘(Complication)的开发, 因为整个表盘的所属权是 WatchOS。当然, Complications的大小和位置也是由 WatchOS决定,并基于所选表盘类型的可用空间。因为不同类型的表盘, Complications的模板类型、可用空间、大小也不尽相同。比如, 下图是某种表盘,包含的Complications有五个位置, 两种不同的模板类型。

表盘开发的好处

Complications功能不是必须的,但苹果官方强烈推荐 App支持此功能, 即使只是作为 App的启动器也好。好处如下:

  • 1.当用户查看手表时, 它为你的 App提供了一个展示重要信息的机会。
  • 2.它会将 App暂停在内存中。这样,当用户点击Complications时,系统可以快速唤醒你的 App。
  • 3.它为你的 App的后台任务提供了更大的预算。

表盘和模板的类型

要实现Complications功能,需要将ClockKit框架导入到 WatchKit Extension中。ClockKit框架定义了用于实现Complications功能的类, 以及用于提供 Apple Watch所需数据的类。比如ClockKit中的CLKComplication类, 它的实例就是一个表盘。

表盘系列(Complication Family)

表盘的实例只有一个属性就是family, 它代表了当前表盘的表盘系列(Complication Family), 或者理解为表盘的类型。Apple Watch支持多种Complication Family,也定义了表盘的大小和特征。下图展示了Complication Family以及它们在特定表盘上的显示方式。苹果官方也是鼓励 App支持所有可用Complication Family

再具体些, 源码中Complication Family一共有7种系列。具体如下:

public enum CLKComplicationFamily : Int {
    case modularSmall
    case modularLarge

    @available(watchOS 3.0, *)
    case utilitarianSmallFlat /* subset of UtilitarianSmall */
    
    case utilitarianLarge
    case circularSmall
    @available(watchOS 3.0, *)
    case extraLarge
}

表盘模板(ComplicationTemplate)

在给定某个表盘系列(Complication Family)中,都有多种不同的表盘模板(ComplicationTemplate),你可以决定使用哪种模板来显示的数据。这些模板可以在可用空间中显示文本、图像或两者的组合, 只需要你提供数据就可以展示了。

到目前 WatchOS 4.3, 共有28种表盘模板, WatchOS 5.0又加入了15种表盘模板,我做了一个表盘模板汇总。下面按照表盘系列对所有模板进行了一一例举, 你可以很清楚的看到各个模板的展示方式。

CLKComplicationFamilyModularSmall

① CLKComplicationTemplateModularSmallSimpleText

② CLKComplicationTemplateModularSmallSimpleImage

③ CLKComplicationTemplateModularSmallRingText

④ CLKComplicationTemplateModularSmallRingImage

⑤ CLKComplicationTemplateModularSmallStackText

⑥ CLKComplicationTemplateModularSmallStackImage

⑦ CLKComplicationTemplateModularSmallColumnsText

CLKComplicationFamilyModularLarge

① CLKComplicationTemplateModularLargeStandardBody

② CLKComplicationTemplateModularLargeTallBody

③ CLKComplicationTemplateModularLargeTable

④ CLKComplicationTemplateModularLargeColumns

CLKComplicationFamilyUtilitarianSmall

① CLKComplicationTemplateUtilitarianSmallSquare

② CLKComplicationTemplateUtilitarianSmallRingText

③ CLKComplicationTemplateUtilitarianSmallRingImage

CLKComplicationFamilyUtilitarianSmallFlat

① CLKComplicationTemplateUtilitarianSmallFlat

CLKComplicationFamilyUtilitarianLarge

① CLKComplicationTemplateUtilitarianLargeFlat

CLKComplicationFamilyCircularSmall

① CLKComplicationTemplateCircularSmallSimpleText

② CLKComplicationTemplateCircularSmallSimpleImage

③ CLKComplicationTemplateCircularSmallRingText

④ CLKComplicationTemplateCircularSmallRingImage

⑤ CLKComplicationTemplateCircularSmallStackText

CLKComplicationFamilyExtraLarge(watchOS 3.0)

① CLKComplicationTemplateExtraLargeSimpleText

② CLKComplicationTemplateExtraLargeSimpleImage

③ CLKComplicationTemplateExtraLargeRingText

④ CLKComplicationTemplateExtraLargeRingImage

⑤ CLKComplicationTemplateExtraLargeStackText

⑥ CLKComplicationTemplateExtraLargeStackImage

⑦ CLKComplicationTemplateExtraLargeColumnsText

CLKComplicationFamilyGraphicCorner(watchOS 5.0)

① CLKComplicationTemplateGraphicCornerGaugeText

② CLKComplicationTemplateGraphicCornerGaugeImage

③ CLKComplicationTemplateGraphicCornerTextImage

④ CLKComplicationTemplateGraphicCornerStackText

⑤ CLKComplicationTemplateGraphicCornerCircularImage

CLKComplicationFamilyGraphicBezel(watchOS 5.0)

① CLKComplicationTemplateGraphicBezelCircularText

CLKComplicationFamilyGraphicCircular(watchOS 5.0)

① CLKComplicationTemplateGraphicCircularImage

② CLKComplicationTemplateGraphicCircularOpenGaugeRangeText

③ CLKComplicationTemplateGraphicCircularOpenGaugeSimpleText

④ CLKComplicationTemplateGraphicCircularOpenGaugeImage

⑤ CLKComplicationTemplateGraphicCircularClosedGaugeText

⑥ CLKComplicationTemplateGraphicCircularClosedGaugeImage

CLKComplicationFamilyGraphicRectangular(watchOS 5.0)

① CLKComplicationTemplateGraphicRectangularLargeImage

② CLKComplicationTemplateGraphicRectangularStandardBody

③ CLKComplicationTemplateGraphicRectangularTextGauge

时间轴(TimeLine)

表盘数据源对象, 它是一个CLKComplicationDataSource协议的遵循者, 实现了协议中的一些方法。此协议中的方法将与ClockKit产生交互, 可以返回的过去、现在和将来的条目(TimeLineEntry)用于构建表盘元素(Complications)数据的时间轴

每个时间轴条目(TimeLineEntry)都包含一个 NSDate对象和一个包含要显示的数据的表盘模板。当指定的日期和精确时间到达时,ClockKit会将相应模板中的数据渲染到Complications中。随着时间的推移,ClockKit会根据时间轴中的条目更新你的Complications

构建数据时间轴的另一个好处是,它允许用户在时间旅行(TimeTravel)期间查看更多数据。如果启用了TimeTravel,用户可以使用Digital Crown查看或预览向Complications提供的任何数据。

工作原理

由于与表盘的交互很快发生并持续很短的时间,因此ClockKit必须提前发现表盘元素(Complications),以确保它们能够及时显示。为了最大限度地降低功耗,ClockKit会要求你提供尽可能多的数据,然后缓存数据并在需要时呈现数据。

ClockKit需要来自表盘元素(Complications)的数据时,它会运行你的 WatchKit Extension并调用表盘数据源对象的方法以获得它所需的内容。表盘数据源对象遵循CLKComplicationDataSource协议,是你提供的对象, 系统默认是工程中的ComplicationController对象。可以使用此协议中的方法将数据返回到ClockKit,并提供要显示的过去、现在、将来的数据。表盘将展示这些提供的表盘数据。

在更新表盘数据的时候, 有问题需要注意。如果表盘数据没有更新,请不要调用表盘数据更新的方法, 如reloadTimelineForComplication:extendTimelineForComplication:方法。请注意,后台任务和表盘数据传输都是预算编制的。如果超出预算,则在恢复预算之前无法更新表盘数据。

表盘开发流程

要支持 App的表盘开发功能,请执行以下操作:

  • 1.配置 WatchKit Extension以告诉ClockKit支持表盘功能。
  • 2.确定需要在表盘中展示的数据内容。
  • 3.选择你的 App在每个系列中支持的模板。
  • 4.实现表盘数据源对象(遵循CLKComplicationDataSource协议), 以此向ClockKit提供数据。

配置 WatchKit Extension

在创建新的 Watch App时,可以要求项目支持表盘功能开发。Xcode就会自动创建Complications所需的资源。Xcode提供了一个数据源类ComplicationController,并配置该项目以使用该类。如果在创建Watch App时未启用表盘功能,也可以稍后启用该功能。

设计表盘元素(Complications)

在创建Complications之前,需要先确定Complications打算展示的内容。在确定打算展示的内容时,请考虑以下因素:
1.数据能够放入可用的表盘模板中吗?数据空间有限, 可能只有少量文本字符或小图像的空间。您可以使用可用空间将信息传达给用户吗?
2.否已使用通知向用户及时传达信息?如果使用通知向用户提供更新,则Complications可能并不比通知的方式更加显眼。
3.你可以提前提供多少数据?如果您的应用程序的数据经常更改,则可能难为Complications提供足够的数据。更糟糕的是,如果过于频繁地刷新Complications数据,则可能会超出后台执行或传输的预算。
4.表盘在活跃状态下, 具有Complications功能的 App可以为后台任务提供更大的预算,但每小时的后台执行时间仍然有限。或者,可以在用户的 ​​iPhone上生成Complications数据并将其传输到 Watch App,但每天只能进行50次表盘传输。具体可以参考另外一篇文章: 表盘数据传输

如果上述这些内容你都考虑好了, 那么你就可以制定表盘数据和选择表盘模板了。苹果官方依然还是很建议支持Complications功能的, 具体原因文章开头表明过, 故不再赘述。

表盘数据源配置(ComplicationDataSource)

先来看看关于ComplicationDataSource协议的一些方法:

public func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Swift.Void)

上面的方法, 定义了 TimeTravel 的方向, 过去还是未来, 或者两者都是。

optional public func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Swift.Void)

optional public func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Swift.Void)

上面的第一个方法, 定义提供时间轴条目(TimeLineEntry)的开始时间, 如果不支持过去可以设置为当前时间。若未实现此方法,则ClockKit不会在当前条目之前检索时间轴条目。

上面的第二个方法, 定义提供时间轴条目(TimeLineEntry)的结束时间, 如果不支持过去可以设置为当前时间。若未实现此方法,则ClockKit不会在当前条目之后检索时间轴条目。

public func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Swift.Void)

上面的方法, 定义当前想要展示在表盘元素上的时间轴条目数据。若支持过去的时间轴条目,则从此方法返回的条目必须在getTimelineEntries(for:before:limit:withHandler :)方法提供的所有条目之后。

optional public func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Swift.Void)

optional public func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Swift.Void)

上面的第一个方法, 定义过去的时间轴条目。返回的条目必须从过去开始,且结束日期不能迟于给定参数date。 条目间必须相隔超过一分钟, 如果两个条目相隔不到一分钟,则ClockKit会丢弃其中一个条目。

上面的第二个方法, 定义未来的时间轴条目。返回的条目必须在给定参数date之后开始。条目间必须相隔超过一分钟, 如果两个条目相隔不到一分钟,则ClockKit会丢弃其中一个条目。

optional public func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Swift.Void)

上面的方法, 获取本地化的表盘模板,该模板充当PlaceHolder的作用。当应用安装完成后, 系统会根据支持的表盘系列(ComplicationFamily)调用此方法一次,并缓存结果。

更新表盘数据

ClockKit提供了几种在运行时更新并发症数据的方法:

  • 1.WatchKit Extension运行时显式更新表盘数据。
  • 2.安排后台应用程序刷新任务以更新表盘数据。
  • 3.从 iOS App进行表盘数据传输来更新。
  • 4.使用推送通知更新表盘数据。

每当应用为Complications提供新数据时,请调用CLKComplicationServer对象的reloadTimelineForComplication:extendTimelineForComplication:方法来更新时间轴。前者方法删除并替换整个时间轴,而后者方法将数据添加到现有时间轴的末尾。

如果数据在可预测的时间发生变化,请考虑安排后台应用刷新任务以更新表盘数据。触发后台任务时,收集新数据(例如,使用NSURLSession后台传输)。只要您拥有更新的数据,请调用数据源reload或extend方法来更新时间轴,并安排下一个后台应用程序刷新任务。

或者,您可以在 iOS App中执行更复杂或消耗过高的数据收集任务,然后将该数据传输到手表。使用WatchConnectivity框架通过transferCurrentComplicationUserInfo:方法将更新发送到手表。手表收到数据,就会调用会话代理的session:didReceiveUserInfo:方法。在此方法中,使用提供的用户信息字典更新表盘数据,然后调用数据源reload或extend方法来更新时间轴。

相关资料

WatchOS开发教程之一: Watch App架构及生命周期
WatchOS开发教程之二: 布局适配和系统Icon设计尺寸
WatchOS开发教程之三: 导航方式和控件详解
WatchOS开发教程之四: Watch与 iPhone的通信和数据共享
WatchOS开发教程之五: 通知功能开发
WatchOS开发教程之六: 表盘功能开发 WatchOS 开发教程源码:Watch-App-Sampler
Complication Essentials
Complications Guidelines Complication Images Guidelines


欢迎指正, wangyanchang21.