watchOS应用开发指南(翻译)

作者: Tolecen 分类: iOS开发,交互体验,方法技巧 发布时间: 2018-03-08 21:46

概述

Watch应用程序开发

借助Apple Watch,用户现在可以以一种既个性化又不引起注目的方式访问信息。用户只要抬起手腕,就可以接受和响应通知,从复杂功能(complication)中获取一些基本甚至更多的信息。 开发Watch应用主要意义在于以最直接,最便捷的方式给用户提供重要的,有用的信息(见下图)。

带有功能栏的Apple Watch

您为Watch创建的项目会由两个相关的软件包(Bundle)组成:一个是Watch的应用程序包和一个WatchKit扩展包。Watch的应用程序包括storyboards和其他的一些跟界面相关的资源文件。WatchKit扩展包位于Watch应用程序包内,包含管里这些界面和响应用户交互的代码。(这两个包被统称为Watch应用程序。) 您的Watch应用程序是随着iOS应用程序包一起分发的,iOS会将您的Watch应用程序复制到用户的Apple Watch里面,然后你的Watch应用程序就可以在Apple Watch上运行了。

Watch应用程序是watchOS项目的核心,它提供应用程序的主界面,但这并不是用户看到的唯一的东西。Watch应用程序也会提供自定义的通知和复杂功能(complication)界面。这些界面以不同的方式展示你的应用内容,但都作为Watch应用程序本身的一部分进行打包。具体来说,用于管理通知和复杂功能(complication)界面的代码位于WatchKit扩展中, 而storyboards场景位于Watch应用程序包中。尽管通知和复杂功能(complication)是可选的,但是通知和复杂功能(complication)是与用户进行交流的重要方式,而且通常是用户使用最多的界面。

Watch应用程序

Watch应用程序是用户从Apple Watch主屏幕上启动的应用程序。Watch应用程序呈现你的应用程序的完整用户界面,其中包括多个自定义内容界面和对复杂用户交互的支持。使用Watch应用程序来展现你在Apple Watch上想展现的所有内容。

创建Watch应用程序涉及为你的内容选择一个导航模型和为你想展示的内容设计页面。有关Watch应用程序核心架构的信息,请参阅Watch应用程序的架构部分的讲解。有关如何设计Watch应用程序界面的信息,请参阅UI基础知识部分讲解。

复杂功能(Complications)

复杂功能是表盘上可见的小元素,它可以将重要信息传递给用户。术语复杂功能(Complication)来源于手表制造,在手表制造的过程中,功能的添加增加了手表结构的复杂性。用户可以自定义特定复杂功能展示在表盘上面。不同的表盘可以放置的功能栏的数目是不一样的,大多数都支持至少两个或者三个。

通知

Apple Watch使用一组不同的界面来展示本地和远程通知。当通知第一次到达时,Apple Watch会显示一个最简页面叫做short look,它是一个方便查看通知内容的版本。如果用户抬起手腕,short look界面就会变成展示更多详细内容的界面叫做long look。

你可以自定义Watch应用程序的long look界面,在界面中你可以混合自定义的图形,动态内容和一些其他信息。提供自定义界面,可以让你将你的品牌信息和另一些用户熟悉的元素进行整合。你甚至可以针对不同类型的通知提供不同的界面,将通知的最重要部分放在界面的显著位置。

在watchOS中, 你可以直接从WatchKit扩展中,使用User Notification系统库来安排和处理通知。这个系统库支持创建基于时间的和基于位置的本地通知。你也可以使用它来配置应用程序的可操作的通知(actionable notifications)和处理收到的本地和远程通知。

有关watchOS如何处理通知,以及有关如何将通知界面添加到Watch应用程序,请参阅通知要点部分。有关如何安排和处理本地和远程通知的详细信息,请参阅Local and Remote Notification Programming Guide

watchOS上的用户界面

Apple Watch因为本身的特殊性,在设计应用程序,通知和复杂功能界面时需要采用不同的方法。你的界面需要快速显示信息和提供更便捷的页面切换和交互。创建这种界面意味着你不应该简单的把你的iOS应用的行为移植到Apple Watch上。相反,你应该用Apple Watch的一些好的用户体验来给你的iOS应用程序整体体验加分。

作为创建出色用户体验的一部分,你应该了解,Watch应用程序,通知和复杂功能都有其独特的作用。复杂功能能让你直接从表盘上获取信息,但是由于表盘上展示复杂功能的空间有限,所以你一定要慎重选择你想要展示的信息。通知功能能让用户在某个事件发生时及时得到提示,它给了你一种方法,让你和你的应用交互,即使你的应用没有在运行。Watch应用程序提供了更丰富的用户体验,让你展示更多内容以及和用户交互,但是这些互动必须快速,直观,以保持用户持续参与。

有关如何为Apple Watch设计有效界面,请参阅Apple Watch人机界面指南(Apple Watch Human Interface Guidelines)

配置你的Xcode项目

Watch应用程序的分发是伴随着你的iOS应用的。你可以为一个已经存在的iOS项目添加一个Watch应用程序,或者创建一个新的包含Watch应用程序的iOS项目。这两种方式,Xcode都会自动为Watch应用程序生成两个目标(targets)(Watch应用程序包和WatchKit扩展包)和相应的初始资源。(两个软件包都会包括资源文件。)这些软件包都会作为你的iOS应用程序的一部分被发布到App Store。

Xcode自动创建的Watch应用程序目标和WatchKit扩展程序目标,包含了你开始创建Watch应用程序,复杂功能和自定义通知界面所需要的一切。另外,iOS模拟器给你提供了一个运行时环境,用于测试所有界面的外观和行为。为了测试,模拟器支持Watch应用程序与iOS应用程序一起运行,并且可以同时调试两个进程。这对测试Watch应用程序和iOS应用程序之间通信特别有用。

为一个已经存在的项目添加一个Watch应用程序

  1. 用Xcode打开你的iOS项目。
  2. 选择 File > New > Target, 然后点击watchOS选项栏。
  3. 选择WatchKit App,然后点击下一步。
  4. 在下一步中,输入目标名称。
  5. 如果你打算实现自定义的通知或者复杂功能,选择相应的选择框。包含通知功能的选框默认是选中的。推荐你保持选中这个选项,即使你目前还没有计划实现通知。选中该选框会在项目中添加一个额外的文件来调试你的通知界面。如果你不选中的话,以后你需要实现通知功能时,你必须手动创建该文件。
  6. 点击完成。

创建一个新的包含Watch应用程序的项目

  1. 打开Xcode, 选择 File > New > Project。
  2. 然后点击watchOS选项栏。
  3. 选择iOS App with WatchKit App,然后点击下一步。
  4. 输入项目名称。
  5. 如果你打算实现自定义的通知或者复杂功能,选择相应的选择框。包含通知功能的选框默认是选中的。推荐你保持选中这个选项,即使你目前还没有计划实现通知。选中该选框会在项目中添加一个额外的文件来调试你的通知界面。如果你不选中的话,以后你需要实现通知功能时,你必须手动创建该文件。
  6. 点击完成。

Xcode配置你的Watch应用程序目标和WatchKit扩展程序目标,并将所需要的文件添加到你的iOS项目中去。这两个新的目标的bundle IDs都是根据iOS应用程序的bundle ID自动创建的。Watch应用程序和WatchKit扩展程序的bundle IDs的根目录必须与iOS应用程序的bundle ID相匹配。

注意
如果你改变了你的iOS应用程序的bundle ID,你也必须相应的更新其它bundle IDs。

应用程序目标结构

添加Watch应用程序到你的Xcode项目,将会配置两个新的可执行文件,并更新你的项目的构建(build)依赖项。构建你的iOS应用程序将会构建所有三个可执行文件(iOS应用程序,Watch应用程序和WatchKit扩展程序),并把它们一起打包到iOS应用程序包中。

注意
Xcode还创建专门用于构建和调试Watch应用程序界面(Watch应用程序主界面,通知和复杂功能)的构建方案(build schemes)。使用这些构建方案来运行和调试你的Watch应用程序的各个部分。

下图说明了你的iOS应用程序和watchOS应用程序可执行文件的结构。iOS应用程序可执行文件包含Watch应用程序可执行文件,而Watch应用程序可执行文件又包含WatchKit扩展程序的可执行文件。当用户在手机上安装了iOS应用程序后,如果该用户的手机有已经配对Apple Watch的话,系统会自动安装Watch应用程序(包括WatchKit扩展程序)到Apple Watch上。iOS会自动处理安装过程,无需其它操作。

注意
用户可以关闭自动安装Watch应用程序,或者手动删除已经安装的Watch应用程序。因此,你的iOS应用程序不能依赖安装在已配对Apple Watch上的Watch应用程序。使用在Watch Connectivity框架里面的WCSession类中的watchAppInstalled属性可以判断对应的Watch应用程序是否被安装。

Target structure in watchOS

构建,运行和调试你的Watch应用程序

Xcode提供一个构建方案来运行和调试你的Watch应用程序。在模拟器或者真机上,使用这个方案来加载和运行你的应用程序。如果你在添加Watch应用程序到你的项目时,要求Xcode配置了自定义的通知界面或者复杂功能的话,那么还会有用于运行这些界面的构建方案。如果你最初没有要求Xcode配置这些界面,那么之后如果你需要这些界面的话,你就必须手动配置这些构建方案来运行和调试。

为通知或者复杂功能配置自定义构建方案

  1. 选择你的Watch应用程序的构建方法。
  2. Scheme菜单中选择Edit Scheme
    Edit Scheme
  3. 复制这个已经存在的Watch应用程序方案,并给新的方案一个合适的名字。举个例子,给它一个名称,如“Notification – My Watch app”,以表明该方案是专门用于运行和调试你的通知的。
  4. 在方案编辑器的左栏中,选择Run
  5. Info选项卡中,为新方案选择适当的Watch Interface
    Duplicate Scheme
  6. 如果你为通知界面创建一个构建方案的话,请在测试期间指定一个JSON文件作为通知载体(payload)。
  7. 关闭方案编辑器以保存你的修改。

指定远程通知测试和调试的载体

在iOS模拟器上调试远程通知界面时,你可以指定希望系统在测试过程中将其传递到界面JSON载体(payload)数据。你可以在一个.apns为后缀名文件中指定载体数据。把这个文件添加到项目中,然后在方案编辑器中选择这个文件。你也可以在你的项目中创建多个载体文件,并修改构建方案的载体文件或者创建多个构建方案以方便你的测试。

注意
如果你在创建Watch应用程序的目标时选择通知功能选项时,Xcode会给你提供初始的PushNotificationPayload.apns文件来指定测试数据。(这个文件位于WatchKit扩展中。) 你也可以稍后手动创建载体文件。

PushNotificationPayload.apns文件包含模拟远程通知所需要的大部分字段,并且你也可以根据需要添加更多字段。下面是项目默认初始的载体数据。

A simulated remote notification payload

大部分JSON数据都被打包成一个字典,并在运行时传递给你的代码。因为iOS模拟器没有权限访问你的iOS应用程序的注册操作,因此请使用载体文件来指定要在界面中显示的操作按钮。WatchKit Simulator Actions字段包含一个字典数组,每个字典都代表要添加到界面的操作按钮。每个字典都可以包含以下字段:

  • title—(必须) 这个字段的值是操作按钮的标题。
  • identifier—(必须) 这个字段的值是标识用户所选操作的字符串。当用户点击这个按钮时,系统会调用notification center的代理方法userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:。系统会把这个字段的值赋给actionIdentifier 属性,该属性位于传给方法UNNotificationResponse 对象中。
  • behavior—(可选) 该字段唯一有效的值是字符串textInput。如果这个字段出现,则生成的按钮将触发文本输入。
  • destructive—(可选) 这个字段的值是10。其中1表示按钮将执行破坏性动作,按钮会以警告性的方式呈现。而0表示按钮将以正常的方式呈现。
  • background—(可选) 这个字段的值是10。其中1表示按钮将触发在后台启动应用。而0表示正常从前台启动应用。

为了使用JSON载体来测试你的通知界面,请使用有效的载体文件来配置构建方案。 你可以为不同的通知载体文件创建不同的构建方案,也可以在测试之前修改现有构建方案的载体文件。

有关通知载体中的字段和字段值的更多信息,请参阅Local and Remote Notification Programming Guide.

Watch应用程序的架构

当用户从主屏幕选择你的Watch应用程序时,watchOS会启动你的应用程序到前台。你的应用程序会一直保持在前台直到用户离开它。即使你的应用程序不在前台,它任然有可能周期性的运行。说具体就是,当你的应用程序是当前的表盘复杂功能(complication)或者在程序坞(dock)里时,以及你的应用程序正在处理通知时,它都可以在后台运行。此外,程序坞上的应用程序和是当前表盘复杂功能的应用程序会保留在内存中,这使得系统可以更快的启动它们。

一个Watch应用程序包括两个软件包:Watch应用程序包和WatchKit扩展程序包。如下图,展示了这两个软件包的关系。Watch应用程序包包含你的应用程序的storyboards,而WatchKit扩展程序包包含应用程序的代码和一些其它资源。WatchKit扩展程序与iOS应用程序之间的通信也可以通过Watch Connectivity框架来进行。

注意
复杂功能完全只存在与WatchKit扩展程序中。复杂功能的用户界面没有在Watch应用程序中定义, 而是由实现CLKComplicationDataSource协议的对象定义的。当watchOS需要更新你的复杂功能时,它会启动你的WatchKit扩展程序,但是Watch应用程序界面并未加载。

Relationship between the Watch app interface, the WatchKit extension, and the iOS app

实现扩展代理(Extension Delegate)

WatchKit扩展程序有一个扩展对象(WKExtension)和一个与之相关的代理对象来管理对你的应用程序至关重要的行为。WKExtension对象是一个共享对象,当系统显示你的主Watch界面或你的自定义的通知界面时,你可以获取到这个对象。当你的Watch复杂功能更新时,你也可以获取到WKExtnsion这个扩展对象。这个扩展对象有一个代理对象,这个代理对象遵从WKExtensionDelegate协议。

在启动时,WatchKit扩展程序会自动实例化一个扩展代理对象。它从WatchKit扩展的Info.plist文件的WKExtensionDelegateClassName字段获取扩展代理类的名称。Xcode会自动为每个WatchKit扩展程序目标提供一个扩展代理类,并把这个类的名字来写到Info.plist中的WKExtensionDelegateClassName字段。你所需要做的就是将你的代码添加到Xcode提供的源文件中。

管理Watch应用程序的生命周期

WatchKit扩展程序会在应用程序转换到不同状态时,通知扩展代理对象。你也可以使用这些通知来实现你的应用程序需要的生命周期行为。下图显示了Watch应用程序启动时发生的状态转换,以及为每个状态转换时会调用的WKExtensionDelegate协议的方法。在启动时,使用这些方法来准备你的应用程序的数据结构,检查对应iOS应用程序的数据,以及开始请求最新的数据。

The life cycle of a Watch app

A. applicationDidFinishLaunching 方法被调用。

B. applicationDidBecomeActive 或者 applicationWillResignActive 方法被调用。

C. applicationWillEnterForeground 或 applicationDidEnterBackground 方法被调用。

重要
只有在Watch应用程序运行时,WatchKit扩展程序才会将生命周期事件传递给扩展代理对象。当你的WatchKit扩展程序启动用于通知界面或复杂功能时,WatchKit扩展程序是不会传递这些事件的。

在启动时,Watch应用程序通常会从inactive状态直接转换到active状态。当用户退出应用程序时,它将转换回inactive状态,并在某个时候过渡到background状态,然后变成suspended状态。如果应用程序在程序坞中或者是当前表盘复杂功能时,应用程序将保持在suspended状态。否则应用程序将默默的转换到Not running状态。

生命周期事件的传递可能与向应用程序界面控制器(interface controller)传递的激活(activation)和停用事件(deactivation)混在一起,并且不保证传递顺序。换句话说,界面控制器的willActivate方法,既可能会在扩展代理的applicationDidBecomeActive方法被调用之前被调用,也可能在之后。所以无论顺序如何,你的应用程序都必须做出合适的响应。

有关应用程序状态的更多信息,请参阅WKExtensionDelegate Protocol Reference中管理状态转换部分。

管理场景(Scenes)

和iOS应用程序一样,Watch应用程序由一个或多个场景组成,其中每个场景代表一个完整的内容页面。storyboard存在于Watch应用程序包中,它里面的每个场景都被一个存在WatchKit扩展程序中的界面控制器对象管理。界面控制器是WKInterfaceController类的子类。watchOS中的界面控制器与iOS中的视图控制器具有相同的用途:它在展现和管理屏幕上的内容,并响应用户与该内容的交互。与iOS视图控制器不同的是,watchOS界面控制器不管理界面的实际视图。那些视图是有watchOS管理的。

主Watch应用程序一般包括多个场景,每个场景展示不同类型的信息。由于系统每次只能展示一个场景,因此应用程序会根据用户的操作来展示新的场景。应用程序的导航(navigation)风格决定了场景呈现方式。应用程序也可以根据需要以模态方式呈现场景。有关如何呈现新场景的更多信息,请参阅界面导航

注意
虽然WatchKit扩展程序也使用界面控制器来管理自定义的通知界面,但这些控制器的行为和主Watch应用程序界面对应的控制器行为有所不同。有关如何实现自定义的通知界面的信息,请参阅通知要点

界面控制器的生命周期

在启动时,watchOS会自动加载适合当前交互的故事版场景。如果用户正在浏览通知,watchOS会选择加载正确的通知场景。否则,watchOS会加载应用程序初始场景。加载完场景后,watchOS会要求WatchKit扩展程序来创建对应的界面控制器对象,你可以使用控制器来准备场景呈现给用户。下图显示了具体步骤的顺序。你将会从UI基础知识部分学习到更多关于界面控制器如何工作的信息。

Launching a Watch app

使用界面控制器的initawakeWithContext:方法开加载你所需要的任何数据,设置界面对象的值和准备呈现你的界面给用用户。不要使用willActivate来初始化你的界面控制器,而是使用willActivate方法来做在你的界面出现之前的最后更新。WatchKit扩展程序还会调用didAppear方法来让你知道你的界面已经在屏幕上了。

当一个界面已经出现在屏幕上时,用户与界面的交互就会被界面控制器定义的响应方法处理。当用户与表(tables),按钮(buttons),开关(switches),滑块(sliders)和其它控件交互时,WatchKit扩展程序会调用与之对应的响应方法。使用这些响应方法来更新你的界面或者执行其他相关的任务。为了可以在其他时间执行任务,你需要安排后台刷新任务来更新你的应用程序。有关安排后台任务的更多信息,请参阅 WKExtensionDelegate Protocol Reference

注意
复杂功能和通知界面都不支持响应方法。点击你的应用程序的复杂功能总是启动应用程序,而可操作的通知会触发相应的扩展代理方法。有关可操作的通知的更多信息,请参阅配置可操作通知

WatchKit扩展程序只有在用户与Apple Watch上的你的应用程序交互时,才会继续运行。通常情况下与Apple Watch的交互是简短的,所以界面控制器应该是轻量级的,不要执行长时间运行的任务。当用户明确退出你的应用程序或者停止与Apple Watch交互时,watchOS(注:原文是:iOS,这里我觉得应该是watchOS)将停用当前的界面控制器,并最终暂停WatchKit扩展程序的运行,如下图所示。

The life cycle of an interface controller

当用户重启配对的iPhone时,Watch应用程序仍然可以运行,但是直到用户解锁之后才能与iPhone通信。

后台任务

后台任务是一种能让你的应用程序界面保持最新状态的方法。从系统接收后台任务的对象是执行特定类型操作的信号。任务对象定义了要执行任务的类型,并包含了完成这个任务所需要的数据。系统会通过调用应用程序扩展代理的handleBackgroundTasks:方法,将后台任务对象传递给你的应用程序。

watchOS支持一下类型的后台任务:

  • 后台应用程序刷新任务(Background App Refresh Tasks)。使用WKApplicationRefreshBackgroundTask对象来处理应用程序状态的一般的更新。举个例子,你可能使用这种类型的任务来检查公司的服务器,或者开始下载新的内容。你可以从你的应用程序的WKExtension对象中明确安排这种类型的后台任务。
  • 后台快照刷新任务(Background Snapshot Refresh Tasks)。使用WKSnapshotRefreshBackgroundTask对象来更新你的应用程序界面,准备拍摄快照。当任务完成时,系统自动拍摄快照。系统会定期安排后台快照刷新任务来更新你的应用程序快照。当你的应用程序界面改变时,你也可以从应用程序的WKExtension对象明确安排此类型的任务。
  • 后台Watch连接任务(Background Watch Connectivity Tasks)。使用WKWatchConnectivityRefreshBackgroundTask对象来接收从iOS应用程序使用Watch Connectivity框架发来的数据。当你的Watch应用程序接收到来自对应的iOS应用程序的数据时,系统会自动创建此类型的任务。你不需要自己安排此类型的任务。
  • 后台NSURLSession任务(Background NSURLSession Tasks)。使用WKURLSessionRefreshBackgroundTask对象来接收你之前使用NSURLSession请求的数据。当后台传输需要授权或后台传输完成(成功或失败)时,此类型任务会被触发。你不需要自己安排此类型的任务。

有关后台任务的更多信息,请参阅WKExtensionDelegate协议中的后台刷新任务部分。

快照和程序坞

程序坞是一种用户可以快速启动应用程序或浏览应用程序内容的方式。点击Apple Watch侧边按钮显示程序坞,它可以最多容纳10个应用程序。当用户滚动浏览应用程序时,他们会看到每个应用程序内容的快照-也就是说,他们看到应用程序界面的静态图像。(系统也会在应用程序启动时显示这张静态图像。)如果用户在程序坞停留一会,watchOS会唤醒正在显示的应用程序,并用该应用程序界面最新视图替换当前快照。

始终让你的应用程序的快照保持最新,以确保用户所看到的信息是最新的,不是陈旧的内容。Apple Watch会定期发送WKSnapshotRefreshBackgroundTask对象给在程序坞中的应用程序的扩展代理。使用这个任务对象来更新你的应用程序的内容和你的应用程序当前的界面控制器。当你的应用程序的界面发生有意义的变化时,你可以通过调用WKExtension对象的scheduleSnapshotRefreshWithPreferredDate:userInfo:scheduledCompletion:方法来请求watchOS更新快照。

有关处理后台任务的更多信息,请参阅后台任务。有关快照的更多信息,请参阅WKExtensionDelegate协议参考

在iOS模拟器中调试激活和停用代码

在测试中,你可以锁定和解锁模拟器,来验证你的激活和停用代码是否按预期工作。使用菜单Hardware > Lock命令来锁定模拟器。这模拟了用户放下手腕后,Apple Watch屏幕关闭的情况。作为响应,watchOS会调用当前界面控制器的didDeactivate方法。当你接着解锁模拟器时,watchOS会调用当前界面控制器的willActivate的方法。

共享数据

Watch应用程序和WatchKit扩展程序经常需要访问相同文件,例如,当代码设置自定义的字体或播放多媒体文件时。有以下三个常见用例:

  • 在设计时,在每个包(Bundle)中放置一个单独的自定义字体副本。 然后每个进程访问它们自己的字体副本。
  • 将随你的应用程序一起发布的多媒体文件放入到WatchKit扩展中。 你的WatchKit扩展程序位于Watch应用程序包中,因此你的Watch应用程序可以访问扩展程序包目录中的任何文件。在你的扩展程序代码中,使用NSBundle类的方法来查找扩展程序包中的文件。
  • 将你从网络上下载(或从iOS应用程序传输)的多媒体文件放在共享组容器中(shared group container)。 共享组容器为你的Watch应用程序和WatchKit扩展程序提供了一个公共的存储空间。在你的扩展代码中,为容器内的所有文件创建URL,并使用它们来设置多媒体界面。

另外,你的WatchKit扩展程序可以使用Watch Connectivity框架或者应用程序首选项(app ppreferences)与其对应的iOS应用程序进行通信。

Watch Connectivity框架提供双向的通信通道,用于WatchKit扩展程序和iOS应用程序之间发送文件和数据字典。该框架还支持在后台发送数据,以便在其他应用程序启动时等待数据。

另外,iOS会自动将iOS应用程序首选项的只读副本发送给Apple Watch。你的WatchKit扩展程序可以使用NSUserDefaults对象来读取这些首选项,但不能直接更改默认的数据库。

如果你的Watch应用程序需要更改默认数据库中存储的值时,使用Watch Connectivity框架将需要更改的值发送回你的iOS应用程序,然后在iOS应用程序中修改。

在运行期间,Watch应用程序和WatchKit扩展程序之间共享文件

在运行期间,Watch应用程序界面和WatchKit扩展程序之间使用共享应用程序组来共享多媒体文件。一个应用程序组创建一个可以让多进程访问的安全容器。通常,每个进程都在自己的沙盒(sandbox)环境下运行,但是一个应用程序组允许让两个进程共享一个公共目录。

设置共享的应用程序组

  1. 在Xcode中打开项目的Capabilities选项卡。
  2. 启用应用程序组功能,。这会将权利文件(entitlement file)(如果需要)添加到你所选的目标中,并将com.apple.security.application-groups权利添加到该文件。

注意
Watch应用程序目标和WatchKit扩展程序目标必须启用同样的应用程序组。

有关更多信息,请参阅 Configuring App Groups in App Distribution Guide

访问容器目录

  1. 使用NSFileManager类的containerURLForSecurityApplicationGroupIdentifier:方法来检索容器目录的基本URL。
  2. 使用提供的URL来检索目录内容,或为目录中的文件创建新的URL。

有关使用文件和目录的更多信息,请参阅File System Programming Guide

注意
你不需要使用文件协调器来管理共享容器。虽然容器是由Watch应用程序和WatchKit扩展程序共享的,但Watch应用程序只会从共享容器读取数据,而WatchKit扩展会对共享容器进行读写操作。所以在你使用数据之前,只需确保WatchKit扩展程序已经完成所有数据的写入。

与你的配对iOS应用程序通信

使用Watch Connectivity框架在你的WatchKit扩展程序和iOS应用程序之间进行通信。该框架提供了两个进程之间的双向通信,并允许你在前台或者后台传输数据和文件。

选择正确的通信方式

Watch Connectivity框架提供了在你的iOS应用程序和WatchKit扩展程序之间进行通信的几种方式。每种方式都用于不同的目的。大多数的方式都是在后台单向传输数据,是提供更新的便捷方式。前台传输可以让你的应用程序立即发送消息并等待回复。下表列出了所有的通信方法和对应的使用指南。

方法 用户
updateApplicationContext:error: 向对方应用程序发送少量的状态信息。接收方的程序应该使用接收到的信息来同步其行为或更新其界面。使用此方法发送的数据被认为是不稳定的。每个连续的调用都会替换前一个调用发送的字典数据。数据会在适当的时候,在后台传输,以最大限度地降低功耗。数据传输可能发生在任何时候,包括两个应用程序都没有在运行。这个方法支持后台传输。
transferUserInfo:
transferCurrentComplicationUserInfo:
在后台发送字典数据。接收进程不需要为了传输的数据而运行。数据在后台传输,并排队等待传送。使用transferCurrentComplicationUserInfo:方法将来自iOS应用程序的关于复杂功能相关(complication-related)的数据发送到Watch应用程序。该方法向WatchKit扩展程序发送高优先级的消息,根据需要将其唤醒以传送数据并更新复杂功能的时间线。但请注意,你的复杂功能每日更新的次数是有限的。调用remainingComplicationUserInfoTransfers来确定剩余的更新数量。如果此方法返回0,则系统将使用transferUserInfo:方法来传输数据。数据传输可能发生在任何时候,包括两个应用程序都没有在运行。这个方法支持后台传输。
transferFile:metadata: 在后台传输文件。接收的应用程序必须将文件转移到新的位置,如果它打算保留它。在方法session:didReceiveFile:返回后,未被移走的文件将会被删除。数据传输可能发生在任何时候,包括两个应用程序都没有在运行。这个方法支持后台传输。
sendMessage:replyHandler:errorHandler:
sendMessageData:replyHandler:errorHandler:
立即发送数据到对应的应用程序。如果你不想要响应处理,你可以为replyHandler参数指定nil。在调用此方法之前,对应的应用程序必须是可访问的。iOS应用程序始终被认为是可访问的,并且从Watch应用程序中调用此方法会根据需要在后台唤醒iOS应用程序。Watch应用程序仅在安装并运行时才被视为可访问的。数据会立即传输,消息按照发送的顺序排队传输。这些方法必须在前台启动传输。

对于大多数类型的传输,你提供一个要发送的NSDictionary对象数据。字典中的字段和值都必须是属性列表类型(property list types),因为数据必须先被序列化,然后无线发送。(如果你需要包含不是属性列表类型的数据,将它们打包到NSData对象中,或者在发送之前把它们写入文件。)另外,你发送的字典必须是紧凑的,只包含需要的数据。保持字典数据小一些,确保它们被快速传输,并且不会在两个设备上消耗太多功率。

有关如何使用Watch Connectivity框架传输数据,请参阅Watch Connectivity框架参考

在应用程序之间进行通信的准则

以下是将Watch Connectivity支持添加到你的应用程序时,需要考虑的一些指导准则:

  • 使用后台应用程序刷新将新数据推送到你的Watch应用程序。 如果你的iOS应用程序定期醒来处理任务,在这段时间来准备数据并使用updateApplicationContext:error:方法将其发送到Watch应用程序。
  • 在代理方法中,移走接收到的文件。 代理方法session:didReceiveFile:调用结束后,系统将删除传输到设备的文件。删除文件可以防止不必要的占用空间,但也意味着如果要保留文件,则必须明确地将文件移动到新的位置。否则,接收到的文件可能在你的异步代码运行之前就被删除了。
  • 只传输你需要的数据。 所有的传输涉及无线发送数据到对应的应用程序。不要通过发送不需要的数据来浪费电力。
  • 将应用程序上下文模式用于发生更改的瞬态数据。 updateApplicationContext:error:方法旨在作为Watch应用程序和iOS应用程序之间传递状态信息快照的一种方式。第二次调用该方法会替换到之前调用的数据。如果你想确保所有数据的都能送达(不只是最近的副本),请使用transferUserInfo:方法。
  • 当发生错误时,提供优雅的回退机制。 如果数据空间不足,数据格式不正确,或存在通信出错,都可能导致错误发生。在你的处理代码中,检查错误并采取适当的措施。
  • 请记住,后台传输可能无法立即传输。 文件和上下文数据会被尽可能快的传输,但传输不是即时的。传输大文件或大量数据时,也需要相当长的时间才能完成。

管理你的数据

当你设计的Watch应用程序时,请考虑以下数据管理方案是否会影响你的实现:

  • 数据放置。 WatchKit扩展程序必须在管理你的数据方面发挥积极的作用。WactchKit扩展程序的目录和iOS应用程序的具有相同的基本结构。将用户数据和其他重要信息放在Documents目录中。尽可能将文件放在Caches目录中,以便在可用磁盘空间不足时可以被系统删除。
  • 数据备份。 Apple Watch不会自动备份WatchKit扩展程序保存的文件。如果你需要备份Watch应用程序的数据,你必须明确地将数据传回你的iOS应用程序,并将其保存在那里。
  • iCloud交互。 从watchOS 3开始,WatchKit扩展程序可以直接使用CloudKit和其他的iCloud技术。

利用iOS技术

watchOS可以使用iOS应用程序中的许多技术,但是,即使是同样的技术,你也可能无法像在iPhone在那样使用该技术。以下是决定何时以及如何使用某项特定技术的一些准则。

  • 注意,某些技术的使用许可必须由用户在iPhone上接收。 用户必须授予特定系统技术的权限,例如Core Location。在你的WatchKit扩展程序中使用这些技术之一会在用户的iPhone上触发相应的提示。 Apple Watch还会显示自己的提示,要求你的用户在iPhone上查看权限请求。有关需要用户权限的技术的信息,请参阅App Programming Guide for iOS中的Supporting User Privacy部分。
  • 不要使用后台执行模式的技术。 一般来说,Watch应用程序被认为是前台应用程序;它们只在用户与某个界面进行交互时才运行。因此,响应的WatchKit扩展程序无法利用大多数的后台执行模式来执行任务。但是,也有一些例外:
    • 使用NSURLSession对象来启动基于网络的数据传输。
    • 使用WKAudioFilePlayer类或WKAudioFileQueuePlayer类在后台播放扩展的音频文件。使用这些对象需要你的WatchKit应用程序目标(而不是你的WatchKit扩展程序目标)中启用音频,AirPlay接图片背景模式(Picture background mode)。启用此模式会将UIBackgroundModes字段添加到应用程序的Info.plist文件中。
    • 使用HKWorkoutSession对象来启动和停止锻炼(workouts)。使用此对象需要你的WatchKit扩展目标(而不是你的WatchKit应用程序目标)中启用锻炼处理后台模式功能(Workout Processing background mode capability)。启用此模式会将UIBackgroundModes字段添加到Info.plist文件中。
  • 避免长时间运行的任务。 在用户停止与相应的Watch应用程序交互之后,WatchKit扩展程序将会暂停运行。因为与Watch应用程序的交互通常是短暂的,所以WatchKit扩展程序很可能在需要长时间运行的任务结束前,就停止运行了。

对的需要长时间运行任务的最佳方案是让它在你的iOS应用程序中执行。例如,不要在你的WatchKit扩展程序中启动定位服务,而是应该在你的iOS应用程序中启动定位服务,然后将更新的数据传输到WatchKit扩展程序。你的iOS应用程序可以收集所需的数据,并使用Watch Connectivity框架将其发送到WatchKit扩展程序。有关如何处理iOS应用程序与WatchKit扩展程序之间的通信的信息,请参阅与你的配对iOS应用程序通信

执行基于网络的操作

WatchKit扩展程序可以使用NSURLSession对象直接与网络进行通信。watchOS中的NSURLSession类支持对应iOS的所有传输选项,包括后台传输。有关如何使用这个类的信息,请参阅URL Session Programming Guide

当你的Watch应用程序正在获取数据时,请记住,你的应用程序随时可能停止运行。如果你使用一个默认的请求会话(request session),你的应用程序可能在收到数据之前,就退出了。所以我们应该使用后台会话(background session)。后台会话可以让系统管理文件传输,即使你的应用程序已经停止运行了。系统会在下载完成后,触发一个后台任务,让你可以处理下载的信息。有关更多信息,请参阅WKURLSessionRefreshBackgroundTask Class Reference

如何你的Watch应用程序和iOS应用程序使用同一个服务器,则每个进程分别请求数据通常会更简单,而不是尝试从一个应用程序同步到另一个应用程序。如果你要确保两个应用程序同步,请使用updateApplicationContext:error:方法来传递基本信息,比如记录ID或标题字符串,以便每个应用程序都知道其他人已经获取的内容。

有关使用NSURLSession对象执行网络请求的信息,请参阅URL Session Programming Guide

请求异步任务的后台断言(Requesting Background Assertions for Asynchronous Tasks)

由于Watch应用程序的生命周期比较短,因此在使用异步任务时需要格外小心。否则系统可能在任务完成前就暂停或终止了你的应用程序。在实现诸如didReceiveNotification:withCompletion或applicationDidEnterBackground方法时尤其要注意。在这两种情况下,你只有短暂的处理响应时间,系统可能会在方法返回后,很快就暂停你的应用程序。

当执行关键任务(例如保存数据),可以通过调用NSProcessInfo类的performExpiringActivityWithReason:usingBlock:方法来请求后台断言以完成任务。当你被授予后台断言时,你的应用程序将继续在后台运行,直到提供的block返回。但是请记住,系统可以随时撤销断言。如果发生这种情况,系统会再次调用你的block,并将true传递给expiring参数。

下面的代码显示了如何使用performExpiringActivityWithReason:usingBlock:方法请求后台断言。本示例使用了信号量的等待方法来暂停提供的block的执行。这让应用程序继续在后台运行,直到调用信号量的信号方法。如果你在block中调用异步API,则必须在异步完成后调用信号量的信号方法。另一方面,如果你只执行同步任务,则可以完成删除信号量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let semaphore = DispatchSemaphore(value: 0)
ProcessInfo.processInfo.performExpiringActivity(withReason: "Debug String") { expiring in
    // This block is executed on an anonymous background queue.
    // Note: this block can be called multiple times. If you are given
    // a background task assertion, the block will be called with
    // expiring == false. Then, if the app suddenly needs to terminate, the system can revoke
    // the assertion by calling this block a second time with expiring == true.
    
    if expiring {
        
        // Your app has not been given a background task assertion,
        // or the current background task assertion has been revoked.
        
        // You have a brief bit of time to perform any clean-up activities.
        
        // Clean up any running tasks.
        // Cancel any long synchronous tasks (if possible).
        // Unblock the thread (if blocked).
        
        semaphore.signal()
        
    } else {
        
        // Your app has been given a background task assertion.
        
        // The background task assertion lasts until this method returns, or until
        // the assertion is revoked.
        
        // perform long synchronous tasks here
        // or kick off asynchronous task and block this thread.
        // Be sure to unblock the thread when the asynchronous task is complete.
        
        semaphore.wait(timeout: .now() + .seconds(60))
    }
}

将数据安全地存储在钥匙串中

为了安全地在Apple Watch存储信息,请使用钥匙串接口。将数据项的kSecAttrAccessible属性设置为kSecAttrAccessibleWhenUnlocked,可以让Apple Watch在用户的手腕上并解锁时可以访问该项。当用户取下Apple Watch时,钥匙串再次被锁定,以阻止访问此属性的任何项目。

下面代码显示如何使用kSecAttrAccessibleWhenUnlocked字段将数据项安全地保存到用户的钥匙串中。该示例展示如何保存密码信息,但你可以使用该字段保存其他类型的数据。

kSecAttrSynchronizable字段在watchOS上是不可用的。

1
2
3
4
5
6
7
8
9
10
11
12
NSString* secret = @"SomeSecretInformation";
NSData* secretData = [secret dataUsingEncoding:NSUnicodeStringEncoding];
if (secretData) {
   NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:
            kSecClassGenericPassword, kSecClass,
      kSecAttrAccessibleWhenUnlocked, kSecAttrAccessible,
                        @"myservice", kSecAttrService,
                     @"account name", kSecAttrAccount,
                          secretData, kSecValueData, nil];
 
   SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
}
1
2
3
4
5
6
7
8
9
10
let secret = "SomeSecretInformation"
if let secretData = secret.dataUsingEncoding(NSUnicodeStringEncoding) {
    let attributes: [NSString: NSObject] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
        kSecAttrService: "myservice",
        kSecAttrAccount: "account name",
        kSecValueData: secretData ]
    SecItemAdd(attributes, nil)
}

有关如何加密保存信息到用户的钥匙串中的更多信息,请参阅Keychain Services Programming Guide

支持Handoff

Apple Watch支持使用Handoff,来创建活动。使用WKInterfaceController中的updateUserActivity:userInfo:webpageURL:方法来创建活动并将其发布给其它设备。

Apple Watch不处理被其它设备创建的活动。

打开URLs

应用程序可以使用共享WKExtension对象的openSystemURL:方法来启动电话呼叫或SMS信息。当你打开使用telsms方案(scheme)的URL时,Apple Watch会将该URL重定向到相应的系统应用来处理。你还可以使用openSystemURL:方法打开与PKPass对象关联的URL,从而打开Passbook URL。

有关如何使用telsms URL方案创建URL的信息。请参阅Apple URL Scheme Reference。有关使用PassKit的更多信息,请参阅Wallet Developer Guide

远程控制事件和正在播放信息

Apple Watch使用远程控制事件系统来管理用户在配对的iPhone上的音频或视频的播放。每当正在播放信息显示在iPhone的控制中心,正在播放的应用程序也会出现在Apple Watch的程序坞(dock)的最后一个应用程序。用户也可以将正在播放的应用程序永久的添加到程序坞中。

正在播放应用程序的传输控件可以为正在播放的内容的iOS应用程序生成远程控制事件。注册处理MPRemoteCommandCenter对象命令的iOS应用程序,当它是正在播放的应用程序时,会自动收到这些事件。你不需要做任何额外的工作来支持来自Apple Watch的远程控制事件。

对于喜欢(like),不喜欢(dislike),或添加书签命令,Apple Watch使用localizedShortTitle而不是MPFeedbackCommand对象的ocalizedTitle字符串。

Apple Watch的正在播放应用程序会自动显示当前正在播放的iOS应用程序提供的所有正在播放的信息。iOS应用程序使用MPNowPlayingInfoCenter对象提供此信息。当你的应用程序播放它的内容时,它会更新nowPlayingInfo字典的值。Apple Watch会自动检索并显示这些信息。另外,点击正在播放应用程序中的曲目标题即可启动iOS应用程序的Watch应用程序。

有关如何实现对远程控制事件的支持以及如何在iOS应用程序中播放信息的更多内容,请参阅Remote Control Events

正在播放应用程序还会显示支持扩展音频内容的Watch应用程序播放的曲目的信息。应用程序从当前正在播放的WKAudioFileAssetWKAudioFilePlayerItem对象的属性来获取要显示的信息。

有关从Watch应用程序中播放扩展音频的更多信息,请参阅Playing Extended Audio Content

在iOS应用程序和watchOS应用程序之间共享代码

你可以在iOS应用程序和Watch应用程序之间共享代码,而不能共享框架。由于应用程序运行在不同架构的独立平台上,所以必须为每个平台分别编译源文件。如果你仍想使用框架来管理共享源文件,则必须为每个平台创建单独的框架目标,并将共享的源文件添加到每个框架。

如果你已经有一个iOS框架,你可以复制框架并修改它以支持watchOS。

复制并配置watchOS的框架目标

  1. 打开Xcode的项目编辑器窗格。(窗格通常是关闭的。)
  2. 按住Control键单击目标会出现一个带有重复(Duplicate)命令菜单,然后点击重复命令。
  3. 更改目标的名称,以便之后可以很容易的识别它。
  4. 在构建设置(Build Settings)中,更改一下值:
    • 将Supported Platforms设置为watchOS.
    • 将Base SDK设置为最新的watchOS版本.
    • 更改Product Name设置,使其与你的iOS框架的名称匹配。你希望两个框架都用相同的名字来构建。
  5. 将框架添加到WatchKit扩展程序的linked frameworks中去。

注意
为WatchKit扩展创建单独的框架目标后,必须分别为iOS和watchOS管理该框架的内容。如果你将文件添加到iOS版本的框架中,请记住将它们也添加到watchOS版本中。

Watch应用程序

UI基础知识

实现你的应用程序是从定义你的故事板场景开始的。每个场景都是你的应用程序用户界面的一部分。你可以为不同的Apple Watch尺寸定制场景。

装配你的故事板场景

Watch应用程序和iOS应用程序使用不同的布局类型。在为你的Watch应用程序界面装配场景时,Xcode会帮你排列它们,将它们垂直放置在不同的行上。在运行时,Apple Watch会根据可用空间将这些元素放在一起。

虽然Xcode会处理界面的整体布局,但watchOS还是提供了一些方法来微调场景中物体的位置。大多数物体的大小和位置都可以使用属性检查器(Attribute Inspector)进行设置。对齐选项允许你在元素堆栈的当前位置设置物体的水平和垂直对齐方式。尺寸选项可以让你指定物体的固定宽度,或使其能够在可用的空间内调整其自身尺寸。

组对象(Group objects)使你可以更灵活的控制布局。组作为其他元素的容器,让你能够水平或垂直排列一组物体。你可以在其他组中嵌套组,并使用每个组的间距和inset值来更改其包含物体的大小和位置。组没有默认的可视化展示,但可以根据需要设置背景颜色或图片。

下图显示了如何在故事板文件中排列不同的元素。前三个元素是标签,在界面控制器的边界内有不同的对齐方式。标签下面是一个包含水平排列的两个图片的组对象。该界面还包含一个分隔符和一个垂直堆放在组对象下的按钮。

Interface objects in Xcode

在Xcode创建你的界面时,让对象调整自己的大小以尽可能地适应可用空间。应用程序界面应该能运行在两种不同大小的Apple
Watch上。让系统调整对象大小以适应可用空间,可以最大限度地减少为每个设备编写自定义代码的数量。

适应不同的显示尺寸

Xcode支持为不同大小的Apple Watch自定义界面。 默认情况下,您在故事板编辑器中所做的更改会因影响所有尺寸的Apple Watch,但您可以根据需要为不同设备自定义故事板场景。 例如,您可以为不同的设备尺寸,对元素的间距和布局进行微调或者指定不同的图像。

要为特定设备大小自定义,请使用“属性”检查器中的加号按钮(+)覆盖给定设备的属性值。 点击一个加号按钮为该属性添加一个新的设备专用条目。 您对该属性版本所做的更改仅影响所选设备。 下图显示了Apple Watch 42mm对文本缩放的处理方式。

Customizing attributes for different devices

用户不应该注意到你的应用程序在不同大小的Apple Watch上的界面有显着差异,因此尽量减少针对不同设备尺寸所做的自定义设置。 只要有可能,限制界面更改与布局相关的行为,如间距和边距。 尽管可以在不同的布局中完全从界面中删除界面对象,但不推荐这样做。 尝试在所有尺寸的Apple Watch上使用完全相同的界面对象。

要查看应用于不同设备大小的自定义设置,请使用故事板编辑器底部的控件在设备大小之间切换。 故事板编辑器默认显示任何设备大小。 在任何设备大小模式下,任何更改都会影响所有尺寸的Apple Watch。 如果将显示模式更改为特定的设备大小,则在该模式下所做的更改仅影响当前设备大小的设备。

在运行时更新界面

在运行时,界面控制器可以对相应的故事板场景中的对象进行以下修改:

  • 设置或更新数据值。
  • 改变支持这种修改的对象的外观。
  • 更改对象的大小。
  • 更改对象的透明度。
  • 显示或隐藏一个对象。

您不能将新对象添加到您的界面或更改已经存在的对象的顺序。 尽管无法删除对象,但可以隐藏它们,这会暂时将它们从布局中删除。 当物体被隐藏时,其他物体将填充该物体先前所占用的空间。 要在不填充空间的情况下隐藏物体,只需要将物体的Alpha值设置为0。有关在场景中隐藏对象的更多信息,请参阅隐藏界面对象

设置你的应用程序的关键颜色

每个Watch应用都有一个关联的关键颜色,应用于以下的UI元素:

  • 状态栏中的标题字符串
  • 短视通知中的应用名称

应用程序的关键颜色存储在应用程序故事板的全局色调(Global Tint)属性中。 要访问此属性,请选择您的故事板并显示“文件”检查器(File Inspector)。 从弹出菜单中选择几种预先存在的颜色中的一种,或使用颜色选择器来指定自定义颜色。

国际化你的界面

Watch应用程序附带的故事板默认启用基本国际化。此功能会导致故事板中的任何字符串自动添加到项目的Localizable.strings文件中。只需将这些文件中的字符串翻译为每种目标语言,然后将其包含在你的应用中。在运行时,创建故事板场景,Xcode选择相应的本地化字符串。

安排你的界面,使包含文本的标签和控件有足够的扩展空间。不要将多个按钮放在同一行上,而要将它们垂直排列,以便每个按钮都有足够的空间显示其标题。

对于需要用代码指定的文本和图像,请使用与iOS和MacOS应用程序相同的国际化技术:

当在WatchKit扩展程序中使用时,NSLocale对象返回Apple Watch上配置的区域设置信息。使用该类来获取用户的首选语言和其他语言,以及其它语言环境相关的信息。

有关国际化你的应用程序的更多信息,请参阅Internationalization and Localization Guide

界面导航

对于具有多个内容页面的Watch应用程序,您必须选择一种在不同页面之间进行导航的技术。 Watch应用程序界面支持两种相互排斥的导航风格:

  • 基于页面。此样式适用于具有简单数据模型的应用程序,其中每个页面上的数据与任何其他页面上的数据都没有密切关系。基于页面的接口包含两个或多个独立的界面控制器,在任何给定的时间只显示其中的一个。在运行时,用户通过在屏幕上向左或向右滑动来在界面控制器之间导航。屏幕底部的点指示器控件指示用户当前在页面中的位置。
  • 层级结构。此样式适用于master-detail类型的界面,用于呈现可导航的一组页面,或适用于那些可能未来需要添加新页面的应用程序。层级界面始终以单个界面控制器为根控制器。在该界面控制器中,您可以提供一些控制,当点击时,将新的界面控制器推到屏幕上。

虽然您不能在应用程序中混合基于页面的和层级导航样式,但可以使用模态呈现方式来补充这些基本导航样式。模态呈现方式可让您中断当前的用户工作流程以请求输入或显示信息。您可以从基于页面的应用程序和层级结构的应用程序中以模态方式呈现界面控制器。模态呈现方式表示本身可以由单个页面或以基于页面的布局排列的多个页面组成。

实现基于页面的界面

您可以在应用程序的故事板中通过创建从一个界面控制器到下一个界面控制器的next-page segue来配置页面界面。

在界面控制器之间创建segue

  1. 在故事板中,为界面中的每个页面添加界面控制器。
  2. 按住Control键点按您的应用的主界面控制器,然后将Segue线拖到另一个界面控制器场景中。第二个界面控制器会突出显示,表明可以创建一个segue。
  3. 释放鼠标按钮。
    4.从关系segue面板中选择next page
  4. 使用相同的技术,为每个界面控制器创建segue。您创建segues的顺序定义了界面中页面的顺序。

您在故事板文件中创建的segues定义了启动应用程序时加载的基于页面的界面。您可以通过调用reloadRootControllersWithNames:contexts:方法更改要显示的一组页面,并为故事板中定义的界面控制器传递一组标识符。例如,您可能会在主界面控制器的init方法中调用该方法来强制watchOS加载一组不同的页面。

在基于页面的界面中的所有界面控制器都是在界面显示之前创建和初始化的,但一次只能显示一个界面控制器。通常情况下,watchOS最初会按顺序显示第一个界面控制器。要更改最初显示的界面控制器,请在其initawakeWithContext:方法中调用becomeCurrentPage方法。

当用户浏览页面时,watchOS会激活和停止相应的界面控制器。使用willActivatedidAppearwillDisappeardidDeactivate方法来确定界面控制器什么时候被显示。

实现层级界面

在层级界面中,您可以使用segues或通过调用当前界面控制器的pushControllerWithName:context:方法来告诉watchOS何时转换到新页面。在故事板中,您可以通过界面中的按钮,组或表格行来创建push segues,指向另一个界面控制器。如果您希望用代码来启动push导航的话,请在界面控制器中调用pushControllerWithName:context:方法。

使用代码将新的界面控制器推入到屏幕时,建议您在pushControllerWithName:context:方法的context参数中传递数据对象。这个context对象会在界面控制器出现在屏幕之前,就将状态信息传递给该界面控制器了。使用这个对象来告诉新的界面控制器显示哪些数据。

注意
对于在Interface Builder中定义的segues,请重写界面控制器的contextForSegueWithIdentifier:方法(或其相关方法之一)为新屏幕内容提供context。

被推入的界面控制器在屏幕的左上角显示一个人字形以指示用户可以向后导航。当用户点击屏幕的左上角或执行左侧滑动时,watchOS会自动关闭最顶层的界面控制器。您还可以通过调用其popController方法以代码方式关闭界面控制器。你不能关闭你的应用程序的主界面控制器。

层级界面中的表(tables)也可以支持项目分页。项目分页启用后,用户可以轻松滚动浏览一系列详细信息视图。用户从表中选择一个项目,应用程序显示该项目的详细视图。用户可以上下滚动浏览表中的其他项目。有关更多信息,请参阅WKInterfaceTable Class Reference中的支持项目分页部分。

以模态方式呈现界面控制器

模态界面是暂时中断当前导航流程以提示用户或显示信息的一种方式。无论您的应用程序使用哪种导航方式,您都可以从任何界面控制器呈现模态界面。要以模态方式显示界面控制器,请执行以下操作之一:

在创建模态segue时,将连接点连接到要显示的界面控制器。当使用segue来呈现多个界面控制器时,首先使用next-page segue将需要模态显示的界面控制器连接在一起,就像将它们连接在一起以获得基于页面的界面一样。你的模态segue应该连接到这组控制器中的第一个界面控制器。如果连接到组中间的某个界面控制器,则不显示该界面控制器前面的界面控制器了。

模态界面的左上角显示界面控制器的标题字符串。当用户点击该字符串时,watchOS关闭模式界面。设置标题字符串以反映解除模态界面的含义。例如,在显示信息时,可以将字符串设置为“完成”或“关闭”。如果您没有为界面控制器指定标题,则watchOS默认显示“取消”字符串。

界面对象

您的WatchKit扩展程序使用界面对象来操作Watch应用程序的UI。 界面对象是WKInterfaceObject类的一个实例,或者更具体地说是它的一个子类的实例。 界面对象不是视图。 它们是您Watch应用程序呈现的实际视图的代理对象。 WatchKit框架为大多数(但不是全部)可添加到故事板场景的视觉元素提供界面对象。

注意
一个界面对象和Apple Watch上的相应视图之间的通信是单向的,信息从WatchKit扩展程序到Watch应用程序。 换句话说,您可以在界面对象上设置值,但不能获取其属性的当前值。 相反,您必须根据需要在WatchKit扩展程序中维护关于界面配置信息。

创建一个界面对象

将对象添加到故事板场景并通过界面控制器引用它,这时候你就间接创建界面对象。在向故事板添加元素之后,在界面控制器中为其创建一个outlet。在界面控制器初始化期间,watchOS会自动为所有连接的outlets创建界面对象。你永远不会自己创建界面对象。

要为界面对象创建outlet,请在界面控制器中创建类似于以下内容的声明:

1
2
3
4
// Objective-C
@interface MyHelloWorldController()
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* label;
@end
1
2
3
class MySwiftInterfaceController {
    @IBOutlet weak var label: WKInterfaceLabel!
}

将每个outlet连接到故事板中的相应项目。创建属性声明并将它们连接到一个项目的快速方法是使用Xcode中的助理编辑器(assistant editor)。要创建一个outlet,请显示助手编辑器并将其从界面对象拖放到类的界面定义中。 (在Swift中,拖动到您的类定义。)Xcode提示您在使用IBOutlet关键字创建适当配置的属性之前指定outlet的名称。

在设计时配置您的界面

在设计时,使用Xcode配置每个故事板场景中的界面对象。 许多布局相关的属性只能在设计时进行配置。 例如,您可以使用WKInterfaceLabel对象更改标签的文本,颜色和字体,但不能更改行数或最小比例因子。 这些属性必须在Xcode中配置,如下图所示。

Configuring a label object

有关如何配置界面对象的更多信息,请参阅WatchKit Framework Reference中的界面对象的类部分。

在运行时更改您的界面

在WatchKit扩展的代码中,通过调用任何引用的界面对象的方法来更新应用的用户界面。界面控制器只能在活动的时候改变其界面对象的配置,包括初始化时间。在您的initawakeWithContext:willActivate方法中,调用方法将数据值分配给用户界面中的标签,图像和其他对象。您也可以在您的界面控制器的其它响应方法中更新它们。

在初始化的时候,让系统在做任何事情之前初始化你的界面控制器类是很重要的。 WKInterfaceController及其子类的初始化方法是watchOS创建应用程序界面对象的地方。因此,为界面控制器编写的任何初始化代码都必须首先调用父类的实现。下面代码是一个界面控制器的init方法的例子,它包含一个WKInterfaceLabel对象的outlet(称为label)。

1
2
3
4
5
6
7
8
9
10
// Objective-C
- (instancetype)init {
    // Always call super first.
    self = [super init];
    if (self){
        // It is now safe to access interface objects.
        [self.label setText:@“Hello New World”];
    }
    return self;
}
1
2
3
4
5
6
7
8
// Swift
override init() {
    // Initialize properties here.
    super.init()
    
    // It is now safe to access interface objects.
    label.setText("Hello New World")
}

WatchKit框架可以优化在应用界面对象上设置值的任何尝试。 每当您在同一个运行循环迭代中为一个或多个界面对象设置值时,新值将被合并,并在一个批处理中传送到Watch应用程序。合并更改意味着只有对象的给定属性的最后更改才会发送到设备。更重要的是,将相同的属性设置为相同的值会生成日志消息,以帮助您追踪重复的呼叫。

有关用于配置界面对象的方法的信息,请参阅WatchKit Framework Reference中相应的类描述。

响应用户交互

当点击按钮或其他控件的值改变时,watchOS会在您的界面控制器中调用相关的响应方法。 每种类型的界面对象都有其响应方法所需的格式,如下表所示。 将响应方法的名称更改为适合您应用程序的内容。

Object Objective-C Swift
Button – (IBAction)buttonAction @IBAction func buttonAction()
Picker – (IBAction)pickerAction:(NSInteger)index @IBAction func pickerAction(index: Int)
Switch – (IBAction)switchAction:(BOOL)on @IBAction func switchAction(value: Bool)
Slider – (IBAction)sliderAction:(float)value @IBAction func sliderAction(value: Float)
Menu Item – (IBAction)menuItemAction @IBAction func menuItemAction()

界面可以使用segues或界面控制器的table:didSelectRowAtIndex:方法来响应点击表中某行。 使用segue来显示另一个界面控制器。 在执行segue之前,watchOS调用界面控制器的contextForSegueWithIdentifier:inTable:rowIndex:contextsForSegueWithIdentifier:inTable:rowIndex:方法,以便在显示界面控制器时指定要使用的context对象。 如果使用table:didSelectRowAtIndex:方法而不是segue,你可以直接执行任何操作。

在界面控制器初始化并显示在屏幕上之后,watchOS只有在用户与界面交互时调用界面控制器的方法。 如果您想在无需用户干预的情况下更新用户界面,则必须配置NSTimer对象来处理相应的任务。

隐藏界面对象

隐藏对象使您可以使用相同的界面控制器来显示不同类型的内容。故事板文件中的场景都必须包含需要在运行时显示的所有界面对象。 如果您想基于可用数据自定义界面,则可以隐藏不需要的对象。 当你隐藏一个对象时,它就会从你的界面中移除。 在布局过程中,隐藏的项目被视为完全从布局中移除。 要隐藏一个对象,调用它的setHidden:方法并传递值YES。

文本和标签

要在Watch应用程序中显示文本,可以使用标签对象。 标签支持格式化文本,并且可以在运行时以代码方式动态修改。

要将标签添加到界面控制器,只需要将其拖放到相应的故事板场景中。在那里,你可以配置标签的初始文本字符串和格式。你也可以使用标准字体或自定义字体。 下图显示了可供您使用的标准字体样式。

Standard font styles for labels

有关在Xcode和代码中配置标签对象的更多信息,请参阅WKInterfaceLabel Class Reference

使用自定义字体

默认情况下,Watch应用程序界面和通知界面使用系统字体来显示文本。您的Watch应用程序界面也可能使用自定义字体(通知只能使用系统字体)。要使用自定义字体,您必须将字体安装到Watch应用程序包和WatchKit扩展包中,如下所示:

  • 将自定义字体文件添加到在Watch应用程序包和WatchKit扩展程序包中。
  • UIAppFonts字段添加到Watch应用程序的Info.plist文件中,并使用它来指定添加到该软件包的字体。有关此键的更多信息,请参阅Information Property List Key Reference

重要
您必须把字体分别添加到WatchKit扩展程序以及Watch应用程序中。当您在WatchKit扩展程序中创建属性字符串(attributed strings)时,属性字符串需要该字体,以便可以在结果字符串中对其信息进行编码。 Watch应用程序需要字体才能正确显示属性字符串。

要使用自定义字体设置文本格式,请使用字体信息创建一个属性字符串,然后使用该字符串设置标签的文本,如下代码所示。字体名称和大小使用属性字符串编码,然后用于更新用户Apple Watch上的标签。如果您指定的字体名称既不是系统字体,也不是您的自定义安装字体之一,则watchOS将使用系统字体。

1
2
3
4
5
6
7
8
// Configure an attributed string with custom font information
 
let menloFont = UIFont(name: "Menlo", size: 12.0)!
var fontAttrs = [NSFontAttributeName : menloFont]
var attrString = NSAttributedString(string: "My Text", attributes: fontAttrs)
 
// Set the text on the label object
self.label.setAttributedText(attrString)

如果自定义字体不包含显示字符串所需的字形,则系统必须回退到另一种字体才能检索缺少的字形。这回退过程可能会导致您的应用程序性能不佳。 为避免延迟,请务必确保您的自定义字体包含需要显示内容的字形。

管理文本输入

watchOS提供了一个标准的模式界面,用于获取用户的文本输入。当呈现时,界面允许用户通过听写输入文本,或从标准的短语或表情符号集中选择,如下图所示。

Gathering text input from the user

要呈现此界面,请调用当前活动界面控制器的presentTextInputControllerWithSuggestions:allowedInputMode:completion:方法。在呈现界面时,您可以指定您支持的输入类型以及执行结果的块。您还可以指定要在界面中显示的一组初始短语。用户可以从可用的短语中选择或使用控件输入不同的短语。

下面代码显示了如何配置文本输入控制器并处理结果。指定初始短语和输入模式后,控制器将异步运行。当用户选择一个项目或取消输入时,block将在主线程上执行。使用block来获取用户选择的文本或表情符号图像,并更新您的应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
NSArray* initialPhrases = @[@"Let's do lunch.", @"Can we meet tomorrow?", @"When are you free?"];
[self presentTextInputControllerWithSuggestions:initialPhrases
     allowedInputMode:WKTextInputModeAllowAnimatedEmoji
     completion:^(NSArray *results) {
        if (results && results.count > 0) {
            id aResult = [results objectAtIndex:0];
            // Use the string or image.
        }
        else {
           // Nothing was selected.
        }
    }];

国际化您的文本代码

Watch应用程序可以使用iOS应用程序使用的相同技术进行国际化。

  • 使用Xcode对故事板和xib文件的基本国际化支持。基本国际化让您只有一组故事板文件,它支持所有本地化。故事板的本地化字符串分别存储在特定于语言的字符串文件中。
  • 使用NSLocalizedString系列的宏以代码方式检索本地化的字符串。
  • 使用NSFormatter子类来使用用户的区域和区域设置格式化值。系统为许多值类型提供格式化程序。例如:

当你的应用程序国际化,你的主要关注点应该是安排你的界面,使标签(和其他文字控制)有足够的空间来扩大。例如,水平排列三个按钮,而不是使用一个组排列三个按钮,垂直排列按钮使每个房间在文字变长时水平增长。

有关使您的应用国际化的更多信息,请参阅Internationalization and Localization Guide

图片

watchOS提供以下方法将图片添加到您的内容中:

指定图片资源(Image Assets)

以下是创建图片资源时要遵循的准则:

  • 尽可能使用PNG格式的图片。
  • 总是要为您的界面创建合适大小的图片。对于无法控制大小的图像,使用界面对象的setWidth:setHeight:方法确保图片以所需的大小显示。
  • 使用图片资源来管理您的图片。图片资源可让您为每个设备大小提供不同版本的图像。

使用命名的图片(Named Images)来提高性能

有几种方法可以改变界面对象的当前图片:

按名称指定图像效率更高,因为只有名称字符串必须传输到Watch应用程序。 watchOS会在您的Watch应用程序包中搜索您指定名称的图片文件。有效指定图片的最有效方法是将它们存储在Watch应用程序包中,并根据需要使用setImageNamed:setBackgroundImageNamed:来配置相应的对象。

在您的WatchKit扩展中创建的图片必须传输到Watch应用程序才能使用。例如,在您的扩展中使用imageNamed:方法会从WatchKit扩展的包中加载图像,而不是从Watch应用程序的包中加载。然后你可以调用setImage:方法,传入图片对象。 WatchKit扩展程序会自动将图像传输到Watch应用程序进行显示。虽然与直接从Watch应用程序包加载图像相比,这有一些额外的开销,但它不应该对性能或电池寿命产生重大影响。

音频和视频

watchOS支持从您的应用程序播放音频或视频内容以及将音频录制到本地文件的功能。 Apple Watch上的音频播放主要用于播放短音频片段,但您也可以播放更长的音频文件。您播放的任何音频都会路由到配对的蓝牙耳机。

重要
如果您正在使用HealthKit来监视用户的心率,那么当您开始播放媒体文件或录制音频时,watchOS将停止收集心率数据。在播放或录制完成后才能恢复收集心率数据。

显示一个电影对象(Movie Object)

WKInterfaceMovieWKInterfaceInlineMovie对象可让您将短音频或视频片段直接嵌入到界面中。这两个电影对象都会显示海报图片,如下图所示。当用户点击海报图片时,Watch应用程序播放媒体内容。您可以使用这些对象来播放纯音频片段,纯视频片段或音频和视频片段。

A movie interface object

WKInterfaceMovieWKInterfaceInlineMovie对象在播放媒体时有很多不同。

  • WKInterfaceMovie对象呈现系统的媒体界面控制器来处理媒体内容的播放。
  • WKInterfaceInlineMovie对象用媒体内容替换海报,并以内联方式播放内容,将内容嵌入到应用程序界面中。

您可以在故事板文件中配置许多电影对象的属性,包括海报图像和调整大小选项。当您的界面控制器初始化时,您可以指定要从WatchKit扩展中播放的媒体文件。 您为媒体资产指定的URL可能是指位于远程服务器上的本地文件或资产。 对于远程资产,电影对象在播放之前完全下载电影。

播放短的音频和视频片段

您也可以通过调用当前界面控制器的presentMediaPlayerControllerWithURL:options:completion:方法以代码方式播放媒体文件。这种方法提供了系统的媒体播放界面,提供了在Watch应用程序场景中嵌入WKInterfaceMovie对象的替代方法。使用此方法你可以使用按钮,表格行和其他交互项来触发媒体播放。该方法还提供了一组扩展的播放内容的选项,包括自动开始播放以及在片段中间开始播放的功能。

下面代码演示了如何播放位于WatchKit扩展包中的电影文件。传入WKMediaPlayerControllerOptionsAutoplayKey选项会使电影在准备就绪后立即开始播放。媒体播放器处理所有的用户交互,并提供一个完成按钮,用户可以用来返回之前页面。您不必在完成处理程序块中显式关闭界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C
NSBundle* myBundle = [NSBundle mainBundle];
NSURL* movieURL = [myBundle URLForResource:@"myMovie" withExtension:@"mp4"];
 
NSDictionary* options = @{WKMediaPlayerControllerOptionsAutoplayKey : @YES};
 
[self presentMediaPlayerControllerWithURL:movieURL
      options:options
      completion:^(BOOL didPlayToEnd, NSTimeInterval endTime, NSError * __nullable error) {
         if (error)
            NSLog(@"Error: %@", [error description]);
 
         // Perform any other tasks.
    }];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Swift
let myBundle = NSBundle.mainBundle()
if let movieURL = myBundle.URLForResource("myMovie", withExtension: "mp4") {
    self.movie.setMovieURL(movieURL)
    
    self.presentMediaPlayerControllerWithURL(movieURL,
                                             options: [WKMediaPlayerControllerOptionsAutoplayKey: true],
                                             completion: { (didPlayToEnd : Bool,
                                                endTime : NSTimeInterval,
                                                error : NSError?) -> Void in
                                                if let anErrorOccurred = error {
                                                    // Handle the error.
                                                }
                                                // Perform other tasks
    })
}

录制短音频片段

要录制短音频片段,请使用WKInterfaceController的presentAudioRecordingControllerWithOutputURL:preset:maximumDuration:actionTitle:completion:方法显示标准录音界面。在呈现界面时,您可以指定所需的音频质量以及生成的音频文件的位置。在用户记录了内容之后,用户必须在指定的URL写入磁盘之前明确接受记录。

有关音频录制选项的信息,请参阅WKInterfaceController Class ReferencepresentAudioRecordingControllerWithOutputURL:preset:maximumDuration:actionTitle:completion:方法的描述。

播放扩展的音频内容

WatchKit扩展可以通过配对的蓝牙音频耳机启动扩展音频内容的播放。播放扩展音频音频涉及使用几个不同的对象。一些对象管理媒体文件本身,而另一些则管理播放过程。

要管理音频文件,请创建一个WKAudioFileAsset对象,然后创建一个WKAudioFilePlayerItem对象。WKAudioFileAsset对象管理关于音频文件本身的信息,例如磁盘上的文件URL以及与该文件相关联的元信息。WKAudioFilePlayerItem对象管理关于音频文件的瞬时信息,例如音频是否正在播放以及到目前为止播放了多少音频。图11-2显示了这些对象相互之间以及与关联的媒体文件的关系。

The arrangement of an audio asset and item

要播放媒体文件,请将WKAudioFilePlayerItem对象传递给WKAudioFilePlayer对象。播放器对象使用播放器项目对象来管理的单个音频文件的播放。要播放音频文件序列,请改为使用WKAudioFileQueuePlayer对象。

播放器对象通过Watch应用程序路由音频,然后在系统的帮助下处理播放。为了防止Watch应用程序在播放音频时终止,您必须将带有音频值的UIBackgroundModes字段添加到Watch应用程序的Info.plist文件中。添加此字段可让您的Watch应用程序继续运行以播放音频。如果该字段不存在,则当用户停止与您的应用程序交互时,播放结束。

重要
由于音频是路由到Watch应用程序的,因此您必须将音频文件放置在WatchKit扩展程序和Watch应用程序包均可访问的位置。有关媒体文件放置位置的信息,请参阅使媒体文件可以被您的手表应用程序访问

使用WKAudioFilePlayer对象开始播放音频文件后,请使用该对象或WKAudioFilePlayerItem对象来监视或更改播放的状态。例如,您可以使用这些对象来检查播放过程中的错误。用户还可以使用系统的正在播放应用程序来控制播放。

有关用于管理音频播放的类的信息,请参阅WatchKit Framework Reference

使媒体文件可以被您的手表应用程序访问

虽然您的WatchKit扩展程序会创建播放媒体所需的对象,但不会处理该媒体的播放。由于电影和音频播放是通过您的应用程序界面进行的,所以您的Watch应用程序界面将处理播放。因此,您的Watch应用程序必须能够访问您所播放的任何媒体文件。

具体而言,当以下类和方法使用引用本地文件的URL时,这些文件必须存储在WatchKit扩展和Watch应用程序均可访问的位置。

在考虑将媒体文件放置在文件系统中的位置时,请遵循以下准则:

  • 将您的应用程序附带的媒体片段放入您的WatchKit扩展包中。 您的WatchKit扩展程序位于Watch应用程序包中,因此您的Watch应用程序可以访问扩展程序包目录中的任何文件。在您的扩展代码中,使用NSBundle类的方法来定位扩展包中的文件。
  • 将您从网络下载的媒体文件(或从iOS应用程序传输)放在共享组容器中。 共享组容器为Watch应用程序和WatchKit扩展提供了公共存储。在您的扩展代码中,为容器内的任何媒体文件创建URL,并使用它们来配置媒体界面。

注意
当为WKInterfaceMovie对象或界面控制器的presentMediaPlayerControllerWithURL:options:completion:方法指定媒体片段时,可以传递指向远程服务器上的文件的URL。对于使用http或https方案的URL,媒体界面在播放前尝试在指定的URL下载文件。在下载过程中,界面显示给用户的进度界面。

有关如何设置共享组容器的信息,请参阅Sharing Files Between a Watch App and WatchKit Extension at Runtime

编码媒体资产

下表列出了在用户的Apple Watch上创建媒体文件时要使用的编码信息。 对于直接从您的应用播放的音频和视频资源,请保持片段相对较短。 短片在磁盘上占用更少的空间,使用更少的电力,并且花费更少的时间下载。

媒体类型 推荐的编码
视频资产 视频编解码器:H.264 High Profile
比特率:高达30 fps的160 kpbs
全屏分辨率:纵向208 x 260
16:9分辨率:横向320 x 180
音频比特率:32 kpbs立体声
仅音频资产 比特率:32 kbps立体声

对于录音,请使用WKAudioRecordingPreset类型定义的音频预设。 预设值可根据最终音频的预期用途进行选择。 例如,您可以将WKAudioRecordingPresetNarrowBandSpeech选项用于不需要高保真音频的语音留言和其他内容。 其他选项提供更高质量的音频选项。

选择器(Picker)

使用选择器显示项目列表,让用户从列表中选择一个项目。 Watch应用程序支持使用WKInterfacePicker类的单列选择器。 选择器单独显示图像或图像和文本的组合。 激活一个选取器让用户使用数码表冠(Digital Crown)来滚动项目。

WKInterfacePicker选择器的外观主要由它显示的项目决定。 选择器的样式定义了选取器如何显示其内容,以及如何随着滚动来动画的切换。 下图显示了一个使用列表样式的选择器,其中项目看起来像在旋转轮的表面上。其他风格也包括只显示图像,并提供不同的选项,从一个图像动画切换到下一个。

A picker using the list style

您可以在单个屏幕上包含多个选择器,并配置选取器的大小和其他属性以适应布局的需要。显示图像时,通常需要调整选择器的大小以匹配图像的大小。

要将选择器添加到应用的界面,请将选择器对象拖到故事板场景中。配置故事板中的选择器样式,并在相应的界面控制器中为选择器创建一个出口(outlet)。您必须为您的选择器有一个出口,因为您会以代码方式配置选择器的内容。您在WatchKit扩展中配置的选择器有两个属性:

  • 选择器项目。 使用setItems:方法指定选择器的内容。每个项目都是WKPickerItem类的一个实例,它包含要在选择器界面中显示的文本或图像。
  • 协调的界面对象。 对于复杂的选择器界面,您可以将选择器链接到一个或多个包含动画图像序列的WKInterfaceImageWKInterfaceGroup对象。当用户转动数码表冠来选择下一个选择器项目时,选择器将更新每个协调对象中显示的图像。

将界面对象链接到选择器可以为界面创建同步的动画。当用户转动数码表冠时,选择器会同时更新自己的内容和任何协调对象的内容。这种行为可以让你创建下图所示的界面。在该示例中,选择器被嵌入在其背景包含显示扩展圆环的动画图像的组对象中。选择器包含完成百分比值的图像,并将该组用作协调界面对象。当用户用户转动数码表冠时,同时更新组中的选择器和圆环图像的百分比值。

注意
协调界面对象提供了一个拣选对象和动画图像之间的简单协调。要控制更复杂的用户界面,请直接使用WKCrownSequencer对象访问表冠。

Coordinating animations with the picker contents

每次选择器值更改时,WKInterfacePicker对象都会将更改报告给其关联的操作方法。 这个操作方法的格式如下:

1
2
// Objective-C
- (IBAction)pickerAction:(NSInteger)index
1
2
// Swift
@IBAction func pickerAction(index: Int)

您可以使用操作方法的索引值从您用来配置选择器的项目数组中获取所选项目。无论用户转换数码表冠的速度如何,选择器都会将其每个更改都报告给其操作方法。如果您的应用程序只响应用户选择的项目,请使用WKInterfaceControllerpickerDidSettle:方法来获取所选项目。

有关如何配置和使用WKInterfacePicker对象的详细信息,请参阅WKInterfacePicker Class Reference

表(Tables)

使用表显示内容动态变化的数据列表。 watchOS使用WKInterfaceTable类支持单列表。在表中显示数据需要事先定义数据的布局,并编写代码以在运行时用实际数据填充表。具体而言,您需要在Xcode项目中执行以下操作:

  • 在你的故事板文件中:
    • 添加一个表对象到你的界面控制器场景中。在界面控制器中为该表创建一个outlet。
    • 按照配置行控制器(row controllers)中的描述为您的表配置一个或多个行控制器。
  • 在你的代码中:
    • 为您定义的每个行控制器定义一个行控制器类;请参阅配置行控制器
    • 在初始化时,按照在运行时配置表内容中的描述,将行添加到表中。
    • 响应与处理行选择中所述的表行交互。

对于每个表,您可以定义多个行控制器类型,每个都有不同的外观。在运行时,您可以指定所需的行类型以及按照什么顺序排列在表中。有关如何配置表的其他信息,请参阅WKInterfaceTable Class Reference

配置行控制器

行控制器是用于在表中显示单行数据的模板。当你添加一个表到你的界面控制器场景时,Xcode会自动创建一个初始行控制器,但是你可以添加更多。例如,对于表中的内容行,页眉和页脚,可以使用不同的行控制器。

将行控制器添加到表中

  1. 选择故事板文件中的表对象。
  2. 打开属性检查器。
  3. 使用行属性来更改可用行控制器的数量。

每个行控制器最初包含一个单独的组元素。对于该组,您添加要包含在行中的标签,图像和其他对象。在行控制器中的标签和图像的内容在设计时是不相关的。在运行时,您可以在配置行时替换每个项目的内容。

每个行控制器都由您用来访问行内容的自定义类来支持。大多数行控制器类只包含访问行界面对象的属性 – 很少包含任何代码。但是,如果将按钮或其他交互控件添加到行中,则行类还可以包含用于响应用户与这些控件的交互的操作方法。

为行控制器定义自定义类

  1. 添加一个新的Cocoa Touch类到您的WatchKit扩展中。
  2. 使你的新类成为NSObject的一个子类。
  3. 为您计划在运行时访问的每个标签,图像或控件添加声明的属性。对声明的属性使用以下格式,将类更改为匹配相应界面对象的类:
1
2
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* label; // Objective-C
@IBOutlet weak var label: WKInterfaceLabel! // Swift

下面代码显示了一个示例行控制器类的定义。 在这个例子中,这个类包含一个图像和一个标签的插座。

1
2
3
4
5
// Objective-C
@interface MainRowType : NSObject
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* rowDescription;
@property (weak, nonatomic) IBOutlet WKInterfaceImage* rowIcon;
@end
1
2
3
4
class MainRowType: NSObject {
    @IBOutlet weak var rowDescription: WKInterfaceLabel!
    @IBOutlet weak var rowIcon: WKInterfaceImage!
}

您可以通过设置其类并连接任何outlet来完成故事板文件中行控制器的配置。您还必须为该行分配一个标识符字符串。在创建行时,您可以使用该字符串。

在故事板中配置行控制器

  1. 在故事板文件中,选择行控制器对象。
  2. 将行控制器的标识符属性设置为该表的唯一值。该值在表格的行类型中必须是唯一的,但实际值由您自行决定。在“属性”检查器中设置此值。
  3. 将行控制器的类设置为您的自定义类。在“标识”检查器中设置此值。
  4. 将标签和其他元素连接到自定义班级的outlet。将故事板文件中的项目连接到您的outlet,将两者结合在一起。watchOS需要这些信息来在运行时创建一个行界面对象。

下图显示了一个使用标识符mainRowType和类MainRowType配置的行控制器的示例,上面代码中定义了该类。该类中的rowDescription和rowIcon outlet连接到行中的图像和标签对象。

Examining a row controller in Xcode

在运行时配置表的内容

在运行时,您将行添加到表中并以编程方式配置每行的内容。添加和配置行作为您的界面控制器的初始化过程的一部分。

创建和配置表格的行

  1. 根据要显示的数据确定所需的行数和类型。
  2. 使用setRowTypes:setNumberOfRows:withRowType:方法来创建行。
    这两种方法都会在界面中创建行,并在WatchKit扩展中实例化每行对应的类。实例化的类存储在表中,可以使用rowControllerAtIndex:方法访问。
  3. 使用rowControllerAtIndex:方法遍历行。
  4. 使用行控制器对象来配置行内容。

setRowTypes:setNumberOfRows:withRowType:方法实例化与相应行控制器关联的类。在调用其中一个方法之后,立即检索新创建的行控制器对象并使用它们来配置行。下面代码使用一些提供的数据为一行配置标签和图像。数据由MyDataObject类型的自定义数据对象的数组提供。 (MyDataObject类公开了一个字符串和图像作为属性,其实现在这里没有显示。)这些行本身是自定义的MainRowType类的实例,在上面代码示例中已经定义。

1
2
3
4
5
6
7
8
9
10
11
// 创建和配置表的行
- (void)configureTableWithData:(NSArray*)dataObjects {
    [self.table setNumberOfRows:[dataObjects count] withRowType:@"mainRowType"];
    for (NSInteger i = 0; i < self.table.numberOfRows; i++) {
        MainRowType* theRow = [self.table rowControllerAtIndex:i];
        MyDataObject* dataObj = [dataObjects objectAtIndex:i];
 
        [theRow.rowDescription setText:dataObj.text];
        [theRow.rowIcon setImage:dataObj.image];
    }
}

配置表时,可以通过限制最初创建的行数来提高性能。但是,因为表行必须全部在前面创建,所以创建大量的行会对应用程序的性能产生不利影响。行的确切数量取决于数据的复杂性以及创建每个数据所需的时间,但请考虑将总行数保持为20或更少。对于需要更多行的表,请考虑最初只加载行的子集,然后向用户提供用于加载更多行的控件。更好的解决方案是只显示最重要的行子集。例如,您可能使用位置数据将行数限制为与用户当前位置最相关的行数。

处理行选择

一个界面控制器负责处理它拥有的任何表中的行选择。当用户点击表的一行时,watchOS选择该行并调用界面控制器的table:didSelectRowAtIndex:方法。使用该方法为您的应用程序执行相关操作。例如,您可能会显示一个新的界面控制器或更新行的内容。如果不希望表行可选,请取消选择故事板中相应行控制器的“选择”选项。

表还支持项目分页。在层级应用程序中,只要选中某行,表便会展开到详细视图,然后启用项目分页可让用户在详细视图之间滚动,而无需返回表格。项目分页默认是禁用的。有关更多信息,请参阅WKInterfaceTable Class Reference中的支持项目分页。

上下文菜单

在Apple Watch上发现的带有Force Touch的Retina显示器提供了一种与内容交互的新方法。代替轻点屏幕上的项目,用少量的力量按下屏幕激活与当前界面控制器相关联的上下文菜单(如果有的话)。上下文菜单是可选的,它提供了一种显示与当前屏幕相关的操作的简单方法。顾名思义,它们和macOS应用程序中的上下文菜单具有相同的作用。例如,在macOS中,通过按住Control键并点击文本视图,可以选择剪切,复制或粘贴文本。

watchOS会在您的内容上显示菜单,如下图所示。

A context menu with three items

上下文菜单最多可以显示四个操作。每个操作都由标题字符串和图像表示。点击一个动作的图像会关闭菜单并执行与该菜单项相关的动作方法。点击任何其他地方都不需要进一步的操作。

设计您的菜单项目

每个菜单项由一个可点击区域和一个标题组成。可点击的区域包含一个圆形的背景,在其上面是您提供的图像。图像必须是模板图像,其中alpha通道定义要在背景上绘制的形状。图像的不透明部分显示为黑色,完全或部分透明的部分让背景颜色显示。

您提供的模板图像应该比它们所在的圆形背景小。有关菜单图像大小的更多信息以及如何创建菜单的准则,请参阅Apple Watch人机界面指南

添加一个上下文菜单到界面控制器

您可以在设计时配置界面控制器的上下文菜单,但是您也可以在运行时添加和删除菜单项。在设计时,编辑故事板以包含您始终希望出现在给定界面控制器的菜单上的菜单项。稍后初始化界面控制器时,可以添加菜单项以补充在故事板中创建的菜单项。您以编程方式添加的菜单项也可以被删除。菜单中的菜单项总数不能超过四个,不管是将它们包含在故事板中还是以编程方式添加它们。

将上下文菜单添加到界面控制器

  1. 打开你的故事板文件。
  2. 从库中拖动一个菜单对象并将其添加到您的界面控制器场景中。初始菜单包含一个菜单项。
  3. 从库中再拖动三个项目到菜单。您也可以使用菜单的属性检查器来设置项目的数量。您添加的项目不能稍后以编程方式删除。
  4. 对于每个项目,使用属性检查器指定菜单的标题和图像。两者都是必需的。
  5. 将每个菜单项连接到界面控制器类中的操作方法。菜单操作方法具有以下格式:
    1
    - (IBAction)doMenuItemAction
  6. 保存你的故事板文件。

要在运行时添加菜单项,请调用界面控制器对象的addMenuItemWithImage:title:action:addMenuItemWithImageNamed:title:action:方法。您指定的项目被添加到故事板文件中的项目。以编程方式添加的项目保持附加到菜单,直到您明确地删除它们或直到您的界面控制器被释放。

处理菜单项中的点击

当用户点击一个菜单项时,watchOS关闭菜单并调用相关的操作方法。 您可以使用以下语法在界面控制器中定义操作方法:

1
2
3
4
// Objective-C
- (IBAction)doMenuItemAction {
    // Handle menu action.
}
1
2
3
4
// Swift
@IBAction func doMenuAction() {
    // Handle menu action.
}

如果需要任何状态信息来执行该操作,则您有责任在界面控制器对象中存储和维护该信息。例如,如果某个操作依赖于当前选定的一行表格,则您的界面控制器必须包含一个变量来跟踪最近选定的行。而且,如果您想在点击菜单操作后向用户请求更多信息,则您的操作方法必须提供模式界面控制器。

Alerts和Action Sheets

Alerts提供了一种标准的方式来提醒用户在您的应用程序中的不常见的情况,如错误的情况。Action Sheets提供了一个标准的界面,用于提示用户从一组特定的选项中进行选择。这两种类型的界面都会影响工作流程的中断,因此只能在必要时使用。

要显示alert或action sheet,请从当前接口控制器调用presentAlertControllerWithTitle:message:preferredStyle:actions:方法。在呈现界面时,您可以指定要显示给用户的标题和消息,以及一个或多个按钮的信息。系统使用该信息来构建界面。下图显示了您可以创建的三种界面样式。

Alert and action sheet styles

有关如何配置和显示alerts和action sheets的信息,请参阅WKInterfaceController Class Reference

设置

首选项和设置是不经常更改的数据值,用于配置应用程序的行为或外观。如果您的Watch应用程序使用首选项来配置,则可以将手表特定的设置绑定添加到您的项目中,以将这些设置呈现给用户。这个设置包存在你的iOS应用程序中,并且这些设置本身由用户的iPhone上的Apple Watch应用程序显示。

Watch专用设置包的工作方式与iOS设置包的工作方式相同。设置包定义了您希望系统显示的控件以及每个控件修改的首选项的名称。用户iPhone上的Apple Watch应用程序显示您的设置,并将所有更改写入与您的应用程序关联的默认数据库。该数据库必须存储在共享应用程序组中,以便您的WatchKit扩展可以访问它。

您的WatchKit扩展可以读取首选项的值,但是不应该写入新的值。首选项从iOS转发到Apple Watch,但您所做的任何修改都不会发送回iOS。如果您的Watch应用程序需要更改首选项,请使用Watch Connectivity框架将值发送回您的iOS应用程序,然后在其中更改值。

有关设置包的工作方式的一般信息,请参阅Preferences and Settings Programming Guide

创建你的设置包

要将Watch特定的设置包添加到您的iOS应用程序,请在Xcode中执行以下操作:

  1. 选择文件>新建>文件。
  2. 在iOS部分中,选择Settings Bundle,然后单击Next。
  3. 使用名称Settings-Watch.bundle创建设置包并将其添加到您的iOS应用程序目标。命名设置包Settings-Watch.bundle需要将其与iOS应用程序的设置包(如果有的话)区分开来。

Watch专用设置包的初始内容与iOS应用程序的设置包相同,如下所示。要配置您的设置,请使用要显示的控件配置Root.plist文件。

1
2
3
4
Settings-Watch.bundle/
   Root.plist
   en.lproj/
      Root.strings

有关如何配置设置包的内容的信息,请参阅Implementing an iOS Settings Bundle。 有关可以包含在“设置”包中的密钥的详细信息,请参阅设置应用程序架构参考。
有关如何配置设置包的内容的信息,请参阅Implementing an iOS Settings Bundle。 有关可以包含在“设置”包中的字段的详细信息,请参阅Settings Application Schema Reference

配置共享应用程序组以存储您的设置

您必须配置共享应用程序组以存储任何Watch特定的设置。共享应用程序组标识Apple Watch应用程序可以访问包含您的首选项的默认数据库的位置。您的Watch应用程序还使用共享应用程序组的标识符来访问您的首选项。要为您的设置配置共享应用程序组,请执行以下操作:

  • 为您的iOS应用,WatchKit扩展和Watch应用启用应用组功能。从列表中选择一个标识符或创建一个新的标识符。
  • 将ApplicationGroupContainerIdentifier密钥添加到Settings-Watch包的Root.plist文件中。把字段放在你的property list的顶层。将其值设置为您在“应用程序组”功能中指定的标识符。

在运行时访问设置

要从WatchKit扩展中访问首选项,请使用initWithSuiteName:方法来初始化一个新的NSUserDefaults对象。指定共享应用程序组的标识符作为suite name。您可以使用生成的用户默认值对象来获取首选项值。您的WatchKit扩展只能读取数据库中的值。如下显示了一个访问自定义组的例子。

1
2
3
// Objective-C
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.example.MyWatchKitApp"];
BOOL enabled = [defaults boolForKey:@"enabled_preference"];
1
2
3
// Swift
let defaults = NSUserDefaults(suiteName: "group.com.example.MyWatchKitApp")
let enabled = defaults?.boolForKey("enabled_preference")

有关如何访问首选项值的更多信息,请参阅NSUserDefaults Class Reference

复杂功能(Complications)

复杂功能(Complication)要点

复杂功能(Complications)是显示在表盘上的小元素,可以快速访问常用数据。用户可以自定义大多数表盘并安装他们希望看到的复杂功能。系统为,天气信息,即将到来的日历事件,用户活动等越来越多的类型的数据提供了内置的复杂功能。你的应用程序也可以添加对复杂功能的支持并显示应用程序特定的数据。

复杂功能的大小和位置由watchOS决定,并基于选定表盘上的可用空间。下图显示了模块化表盘的布局,它有两种不同尺寸的五种不同复杂功能的空间。

Complications on the Modular watch face

注意
应用程序不是必须要提供复杂功能;不过,我们强烈推荐,即使他们只是作为应用程序的启动器。在Watch表盘上有一个复杂功能的提供了以下好处:

  • 它使您的应用程序有机会在用户看表时为用户提供重要的信息。
  • 它使您的应用程序暂停在内存中。 当用户点击复杂功能时,系统可以快速唤醒您的应用程序。
  • 它为您的应用程序提供了更大的后台任务预算。

为了实现复杂功能,将ClockKit框架导入到您的WatchKit扩展中。 ClockKit框架定义了用来实现复杂功能的类,并提供Apple Watch所需的数据。 有关此框架的类的信息,请参阅ClockKit Framework Reference

复杂功能系列(Complication Families)

Apple Watch支持多种复杂功能系列,它们定义了复杂功能的大小和特征。下图显示了复杂功能及其在特定表面上的显示方式。鼓励您在您的应用中支持所有可用的系列。

Complication families

在给定的系列中,您决定要显示哪些数据,并使用ClockKit模板之一来排列数据。每个系列在可用空间中都有不同的模板来显示文本,图像或两者的组合。您选择要使用的模板并提供所需的文本和图像数据。在运行时,ClockKit根据模板的设计呈现数据。

复杂功能是如何工作的

表盘是苹果手表上浏览次数最多的屏幕。用户每天几次浏览表盘以获得当前时间,并从显示的复杂功能中获得其他重要信息。由于与手表的交互事件一般都会很短,所以ClockKit必须提前更新复杂功能,以确保及时显示。为了最大限度地降低功耗,ClockKit会要求您提供尽可能多的数据,然后缓存数据并在需要时进行渲染。

当ClockKit需要复杂功能的数据时,它将运行WatchKit扩展并调用复杂功能数据源对象的方法来获得所需的数据。数据源对象必须遵守CLKComplicationDataSource协议,并且由你来提供这个对象。您可以使用此对象的方法将元信息返回到ClockKit,并提供您要显示的过去,现在和将来的数据。

数据源对象返回的过去,现在和未来条目用于构建复杂功能数据的时间线。每个时间轴条目都包含一个NSDate对象和一个包含要显示的数据的复杂功能的模板。当指定的日期和时间到达时,ClockKit会将来自相应模板的数据渲染到复杂功能所占用的空间中。随着时间的推移,ClockKit会根据时间线中的条目更新您的复杂功能。

构建数据时间线的另一个优点是,它允许用户在时间旅行中查看更多的数据。如果启用“时间旅行”,用户可以使用“数码表冠”来查看或预览可见复杂功能提供的任何数据。

在构建时间线条目时,选择对您显示的数据有意义的日期非常重要。 ClockKit会在到达您指定的精确时间时,开始在时间线条目中显示数据。对于某些类型的数据,您可能需要修改您选择的日期。例如,如果您正在实现会议应用程序,那么在会议开始之前显示用户的下次会议是有意义的。下图说明了如何配置相应的时间线条目。每次新会议都在上次会议开始时显示,给用户时间准备。

Timeline entries for a meeting app

管理复杂功能

为了支持您的应用程序的复杂功能,请执行以下操作:

  1. 确定你想要显示的数据。
  2. 选择您为每个系列支持的模板。
  3. 实现一个数据源对象,将您的数据提供给ClockKit。
  4. 配置您的WatchKit扩展,告诉ClockKit您支持复杂功能。

当创建一个新的Watch应用程序时,您可以要求Xcode创建复杂程序所需的资源。 Xcode为您提供了一个数据源类,并将您的项目配置为使用该类。如果您在创建Watch应用程序时未启用复杂功能支持,则可以稍后添加该支持。

设计复杂功能

在创建复杂功能之前,您需要确定复杂功能的内容。确定您打算在复杂功能中显示的内容时,请考虑以下因素:

  • 你能让你的数据正确的显示在可用的复杂功能模板中? 展示数据的空间有限。您可以选择要为不同系列使用的模板,但是对于许多模板,可能只有文本的少量字符或小图像的空间。你可以使用可用的空间来传达你的信息给用户吗?
  • 您是否已经使用通知向用户传达及时的信息? 如果您正在使用通知向用户传递更新,则复杂功能可能会成为传递相同数据的更不显眼的方式。例如,运动应用程序可能会使用复杂功能来发布更新的运动成绩,而不是在成绩改变时发送通知。
  • 你可以预先提供多少数据? 如果您的应用程序的数据频繁更改,则可能难以提供足够的数据以显示复杂功能。更糟的是,如果您频繁刷新复杂功能数据,则可能会超出后台执行或传输的预算。在活动表盘上以复杂功能的方式观看应用程序的背景任务预算较大,但每小时的后台执行时间仍然有限。或者,您可以在用户的​​iPhone上生成复杂功能数据并将其传输到Watch应用程序,但是每天只能处理50次复杂功能的传输。确保您可以在这些限制内提供有用和及时的信息。

您应该强烈建议为您的应用程序创建复杂功能,即使它们只作为Watch应用程序的启动程序。然而,当应用程序的复杂功能能够提供及时,最新和有用的信息时,用户更可能将它们添加到表盘。

在确定了你打算在复杂功能中显示的内容之后,下一步是定义数据源对象并使用它来提供有关复杂功能的信息并创建时间线条目。

实现数据源对象

ClockKit通过您的应用程序的复杂功能数据源对象与您的复杂功能进行交互。数据源对象是一个遵守CLKComplicationDataSource协议的类的实例。您可以定义类,但是不要在运行时创建该类的实例。而是在Xcode项目设置中指定类的名称,ClockKit根据需要实例化该类。在实例化过程中,ClockKit使用标准的init方法初始化数据源类。

数据源类的工作是尽可能快地为ClockKit提供所需的数据。数据源方法的实现应该是最小的。不要使用数据源方法从网络获取数据,计算值或做任何可能延迟数据传递的事情。如果您需要为复杂功能获取或计算数据,请在您的iOS应用程序或WatchKit扩展程序的其他部分(例如,安排后台应用程序刷新任务)执行此操作,然后将数据缓存到复杂功能数据源可以访问的位置。数据源方法应该做的唯一事情就是获取缓存的数据,并将其放入ClockKit所需的格式。

有关CLKComplicationDataSource协议的详细信息,请参阅CLKComplicationDataSource Protocol Reference

提供你的时间线数据

将您的应用程序特定的数据打包成ClockKit可以显示的模板,是您的数据源最重要的工作。每种复杂功能系列都有一个或多个模板,您可以用来构建您的内容。模板定义了分配给复杂功能的文本和图像的位置。用数据创建模板对象后,将其分配给CLKComplicationTimelineEntry对象,指定显示数据的适当日期,并将时间线条目返回给ClockKit。

为模板指定数据时,可以使用CLKTextProviderCLKImageProvider对象来指定值,代替原始字符串和图像。 提供者对象获取原始数据,并以相应模板的最佳方式对其进行格式化。ClockKit提供了几个用于处理日期和时间的text providers,包括动态更新的相对时间值,而不影响复杂功能的预算执行时间。如下演示了如何为复杂功能创建当前时间线条目。 从扩展代理获取所需的数据后,该方法为指定的复杂功能系列配置模板对象,并将其分配给时间线条目对象。您为当前条目指定的日期必须等于或早于当前时间。 对于过去和将来的条目,将日期设置为显示相应数据的适当时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Objective-C
// Keys for accessing the complicationData dictionary.
extern NSString* ComplicationCurrentEntry;
extern NSString* ComplicationTextData;
extern NSString* ComplicationShortTextData;
 
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication
            withHandler:(void(^)(CLKComplicationTimelineEntry *))handler {
    // Get the current complication data from the extension delegate.
    ExtensionDelegate* myDelegate = (ExtensionDelegate*)[[WKExtension sharedExtension] delegate];
    NSDictionary* data = [myDelegate.myComplicationData objectForKey:ComplicationCurrentEntry];
 
    CLKComplicationTimelineEntry* entry = nil;
    NSDate* now = [NSDate date];
 
    // Create the template and timeline entry.
    if (complication.family == CLKComplicationFamilyModularSmall) {
        CLKComplicationTemplateModularSmallSimpleText* textTemplate =
        [[CLKComplicationTemplateModularSmallSimpleText alloc] init];
        textTemplate.textProvider = [CLKSimpleTextProvider
                textProviderWithText:[data objectForKey:ComplicationTextData]
                           shortText:[data objectForKey:ComplicationShortTextData]];
 
        // Create the entry.
        entry = [CLKComplicationTimelineEntry entryWithDate:now
                                       complicationTemplate:textTemplate];
    }
    else {
        // ...configure entries for other complication families.
    }
 
    // Pass the timeline entry back to ClockKit.
    handler(entry);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Swift
// Keys for accessing the complicationData dictionary.
let ComplicationCurrentEntry = "ComplicationCurrentEntry"
let ComplicationTextData = "ComplicationTextData"
let ComplicationShortTextData = "ComplicationShortTextData"
 
func getCurrentTimelineEntryForComplication(complication: CLKComplication,
                                            withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
    // Get the complication data from the extension delegate.
    let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
    var data : Dictionary = myDelegate.myComplicationData[ComplicationCurrentEntry]!
    
    var entry : CLKComplicationTimelineEntry?
    let now = NSDate()
    
    // Create the template and timeline entry.
    if complication.family == .ModularSmall {
        let longText = data[ComplicationTextData]
        let shortText = data[ComplicationShortTextData]
        
        let textTemplate = CLKComplicationTemplateModularSmallSimpleText()
        textTemplate.textProvider = CLKSimpleTextProvider(text: longText, shortText: shortText)
        
        // Create the entry.
        entry = CLKComplicationTimelineEntry(date: now, complicationTemplate: textTemplate)
    }
    else {
        // ...configure entries for other complication families.
    }
    
    // Pass the timeline entry back to ClockKit.
    handler(entry)
}

更新您的复杂数据

ClockKit提供了几种方法来在运行时更新复杂功能数据:

  • 当您的WatchKit扩展程序正在运行时,显式更新数据。
  • 安排后台应用程序刷新任务来更新您的复杂功能数据。
  • 从您的iOS应用程序传输复杂功能数据。
  • 使用推送通知来更新您的数据。

只要您的应用程序有复杂功能的新数据,请调用CLKComplicationServer对象的reloadTimelineForComplication:extendTimelineForComplication:方法来更新您的时间线。reloadTimelineForComplication:方法删除并替换整个时间轴,而extendTimelineForComplication:方法将数据添加到现有时间轴的末尾。无论哪种情况,ClockKit都会实例化您的数据源对象,并从中请求所需的数据。

除非您有复杂功能的新数据,否则不要调用这些方法。请注意,后台任务和复杂功能传输都是预算限制的。如果您超出预算,则在恢复预算之前无法更新您的复杂功能。

一定要保持你在一个恒定的状态下观看应用程序,通知和复杂功能。每当你更新你的应用程序的数据,一定要也更新你的复杂功能。如果您收到带有新数据的推送通知,请更新应用程序和您的复杂功能以使其与通知保持一致。

如果您的数据在可预测的时间更改,请考虑安排后台应用程序刷新任务以更新您的复杂功能。当后台任务被触发时,收集新的数据(例如,使用NSURLSession后台传输)。只要有更新的数据,请调用数据源的reloadTimelineForComplication:extendTimelineForComplication:方法来更新时间表,并安排下一个后台应用程序刷新任务。

或者,您可以在iOS应用程序中执行更复杂或更昂贵的数据收集任务,然后将该数据传输到手表。使用Watch Connectivity框架通过调用WCSession对象的transferCurrentComplicationUserInfo:方法将更新发送到手表。这种方法会向您的WatchKit扩展发送高优先级消息,根据需要将其唤醒以传递数据。一旦手表收到数据,就会调用会话代理的session:didReceiveUserInfo:方法。在此方法中,使用提供的用户信息字典更新复杂功能数据,然后调用数据源的reloadTimelineForComplication:extendTimelineForComplication:方法更新您的时间线。

提供占位符模板

在定制时钟表面期间,ClockKit显示由数据源的getLocalizableSampleTemplateForComplication:withHandler:方法返回的可本地化的占位符模板。您必须为您支持的每个复杂系列提供占位符模板。 在Apple Watch上安装应用程序后,ClockKit只会请求占位符模板一次。它会缓存您提供的模板,在您的应用重新安装或更新之前,不会再次请求它们。 有关更多信息,请参阅占位符模板。

配置您的Xcode项目以支持复杂功能

要运行你的扩展,ClockKit需要知道你的数据源类的名字以及你所支持的复杂功能系列。Xcode提供了一个界面,用于在WatchKit扩展项目的General选项卡中指定这些信息,如下图所示。如果您在创建Watch应用程序时要求Xcode配置复杂功能,则这些字段应该已经包含默认值。

Configuring the complication settings

您在“常规”选项卡中指定的数据将添加到WatchKit扩展的Info.plist文件中。 Xcode添加以下键并根据您提供的信息适当地设置值。

  • CLKComplicationsPrincipalClass。此键的值是一个字符串,其中包含提供应用程序复杂功能数据的数据源类的名称。
  • CLKSupportedComplicationFamilies。这个键的值是一个包含你的应用程序支持的复杂功能系列的字符串数组。每个字符串的值是CLKComplicationFamily枚举类型中的一个常量的名称。例如,如果仅支持模块复杂功能,则可以包含字符串CLKComplicationFamilyModularLargeCLKComplicationFamilyModularSmall的条目。

复杂功能和手表应用程序库

用户可以使用Apple Watch应用程序的图库区域在iPhone上配置自己的手表。为了将复杂功能添加到图库中,您必须创建一个复杂功能包,为用户提供复杂功能的预览。复杂套件存储在您的iOS应用程序包中,并由Apple Watch应用程序用于填充图库。

有关创建复杂功能包的说明,请参阅向图库添加复杂功能

通知

通知要点

即使您的应用程序未运行,本地和远程通知也可以将信息传递给用户。应用程序可以直接指定时间或位置值来安排本地通知。您的服务器也可以通过Apple推送通知服务(APN)向用户的iPhone发送远程通知,系统会将这些通知视具体情况来决定发送给你的iPhone还是Apple Watch。

不支持通知的应用程序仍然可以免费获得一些通知行为,但是自定义应用程序的通知支持是改善watchOS体验的好方法。您可以通过以下方式自定义Watch应用程序的通知支持:

  • 回应本地和远程通知。
  • 为到达的通知提供自定义界面。
  • 直接从Watch应用程序安排本地通知。
  • 添加对可操作通知的支持,以便用户可以直接从通知界面进行响应。

在watchOS 3及更高版本中,您可以使用用户通知框架来安排和处理本地通知。您也可以使用该框架来处理由用户的iPhone转发到您的Watch应用程序的远程通知。有关如何触发和处理本地和远程通知的信息,请参阅Local and Remote Notification Programming Guide。有关用户通知框架的类别的其他信息,请参阅User Notifications Framework Reference

短通知(Short-Look)

当通知首次到达Apple Watch时,系统将显示短通知界面,如下图所示。短通知界面是一个不能定制的非滚动屏幕。系统使用模板来显示应用程序名称和图标以及存储在本地通知或远程通知payload中的标题字符串。如果用户继续查看通知,则系统从短通知界面快速转换到长通知界面。

A short-look interface

短通知界面中使用的标题字符串提供了通知意图的简要说明。对于本地通知,可以使用为通知创建的UNMutableNotificationContent对象的title属性指定此字符串。对于远程通知,可以使用payload中的alert字典的title键值来指定此字符串。有关配置通知内容的更多信息,请参阅Local and Remote Notification Programming Guide

长通知(Long-Look)

长通知界面是一个可滚动的屏幕,显示通知的内容和任何相关的操作按钮。如果您不提供自定义通知界面,Apple Watch会显示一个默认界面,其中包含您的应用程序图标,通知的标题字符串以及alert消息。如果您提供自定义通知界面,Apple Watch会显示您的自定义界面。

长通知界面分为三个区域:

  • 窗框是包含应用程序图标和应用程序名称的叠加层。窗框颜色是可配置的。
  • 内容区域包含有关传入通知的详细信息。有关如何自定义此区域内容的信息,请参阅管理自定义长通知界面
  • 底部区域包含一个关闭按钮和任何对应iOS应用程序注册的操作按钮。

下图显示了一个包含多个操作按钮的长通知界面通知示例。

A long-look notification interface

点击应用程序图标启动您的Watch应用程序。点击其中一个应用程序定义的操作按钮将选定的操作传递给您的iOS应用程序或Watch应用程序。(前台操作始终传送到您的Watch应用程序。本地通知的后台操作会传送到您的Watch应用程序,但远程通知的后台操作会传送到您的iOS应用程序。)点击Dismiss按钮关闭通知界面并通知您的应用程序如果你要求了。点击其它地方的话就什么都不做。

有关如何为您的应用程序提供自定义长外观界面的信息,请参阅管理自定义长通知界面

通知发送到哪里?

Apple Watch和iPhone共同向用户发送通知。通知出现在最有可能拥有用户当前焦点的单个设备上,而不是同时出现在两台设备上。系统根据通知类型,哪个进程创建通知以及哪个设备处于活动状态来决定哪个设备接收通知。下表列出了iOS和watchOS用于确定在通知发送到哪里的规则。

通知类型 发送方 接收方
本地通知 iOS应用程序 苹果手表或iPhone,取决于两个设备的锁定/解锁状态。
本地通知 WatchKit扩展程序 苹果手表
远程通知 服务器 苹果手表或iPhone,取决于两个设备的锁定/解锁状态。
静默(Silent)通知 服务器 iPhone

对于可以传送到任一设备的通知,系统使用Apple Watch和iPhone的锁定状态来确定目的地。当Apple Watch在用户的手腕上并解锁时,通知将传送到Apple Watch。当用户的iPhone也处于解锁状态并且屏幕处于打开状态时,会发生一个例外,在这种情况下,通知将传送到iPhone。当Apple Watch不在用户的手腕上或未解锁时,通知将传送到iPhone。如果用户有多个Apple Watch,则您的Watch应用程序在一台设备上触发的本地通知仅会传送到该设备。

远程通知在转发到适当的设备之前,总是由用户的iPhone接收。由于静默通知不会导致任何用户交互,因此它们始终由您的iOS应用处理,并且永远不会转发给Apple Watch。

当你的Watch应用程序位于前台,这时候接收到通知,系统会调用用户通知中心代理的方法userNotificationCenter:willPresentNotification:withCompletionHandler:,以便您可以决定要执行的操作。您可以选择静默处理通知或要求系统提醒用户。如果您没有实现该方法,则系统将通知保持沉默并且不会将其传送到您的Watch应用程序。

有关处理通知的其他信息,请参阅Local and Remote Notification Programming Guide

请求用户交互的授权

在系统为你的应用程序通知弹出提示信息或播放声音之前,Watch应用程序或iOS应用程序必须请求授权才能与用户进行交互。您可以通过调用共享UNUserNotificationCenter对象的requestAuthorizationWithOptions:completionHandler:方法来执行此操作。在执行涉及本地或远程通知的操作之前,您必须调用此方法。例如,您可能会在您的应用程序启动周期中调用此方法。当您的Watch应用程序或iOS应用程序首次启动时,此方法会提示用户授予请求的授权。后续任一应用的启动都不会再次提示用户。

有关如何请求授权以与用户交互的信息,请参阅Local and Remote Notification Programming Guide

配置可操作通知

包含应用程序自定义操作按钮的long look通知称为可操作通知,因为它允许用户执行操作来响应通知。为通知配置操作的过程与安排和处理通知本身的过程是分开的。在应用的启动周期早期,注册一个或多个通知类别,并指定要与每个通知类别关联的自定义操作。在注册过程中,您可以指定每个操作按钮使用的标题以及该操作按下时的行为。

注意
在watchOS 3及更高版本中,操作被转发通知的设备分配给指定的通知。因此,对于在iPhone上安排的本地通知以及所有远程通知,即使该通知随后被转发到Apple Watch进行响应,通知也会收到iOS应用程序注册的操作。在Apple Watch上安排的本地通知会收到Watch应用程序注册的操作。

要显示通知的一组操作,请在通知内容中包含相关类别的名称。对于本地通知,使用UNMutableNotificationContent对象的categoryIdentifier属性指定类别名称。对于远程通知,您的服务器将此名称指定为远程通知payloadcategory键的值。通知到达时,系统使用类别名称来查找相关的操作。然后它为每个动作创建按钮并将这些按钮附加到通知界面。

有关如何为Watch应用程序配置类别和操作的信息,请参阅Local and Remote Notification Programming Guide

响应选定的操作

当用户点击通知的操作按钮时,用户的响应会传送到您的iOS应用程序或Watch应用程序以进行处理。哪个应用程序接收到响应取决于该操作是配置为前台还是后台操作。

  • 前台操作始终由启动的应用程序来处理响应操作。例如,如果用户在Apple Watch上选择前台操作,则系统会启动您的Watch应用程序并向用户发送回应。
  • 对于后台操作,操作的处理取决于通知是在什么地方被安排的:
    • 对于在Apple Watch上安排的本地通知,后台操作由Watch应用程序处理。
    • 对于在iPhone上安排的本地通知,后台操作由您的iOS应用处理,无论哪个设备显示通知。
    • 对于远程通知,后台操作总是由您的iOS应用程序处理,无论哪个设备显示通知。

您可以在注册应用程序的类别和操作时指定操作是前台还是后台操作。操作默认配置为后台操作。如果要在前台处理该操作,则必须在创建UNNotificationAction对象时指定UNNotificationActionOptionForeground选项。

为用户提供文本输入建议

要为文本输入提供建议的响应,请覆盖通知界面控制器的suggestionsForResponseToActionWithIdentifier:forNotification:inputLanguage:方法并返回一个字符串数组。当用户选择相关的操作时,watchOS将这些字符串作为建议。有关实现通知界面控制器的更多信息,请参阅WKUserNotificationInterfaceController Class Reference

管理自定义长通知界面

自定义长通知界面由两个独立的界面组成:一个静态和一个动态。静态界面是必需的,并且是显示通知的提示消息和您在设计时配置的任何静态图像和文本的简单方法。 动态界面是可选的,并为您提供了在运行时自定义通知内容的显示方式。

当您将新的通知界面控制器添加到故事板文件时,Xcode将同时创建静态和动态界面。如果你不需要动态界面,你可以删除它。你可以通过选择静态界面,并在“属性”检查器中取消选中Has Dynamic Interface复选框,来删除动态界面。下图显示了故事板文件中未修改的静态和动态界面场景。静态和动态场景与通知类型相关联,你可以使用附加于静态场景中的通知类别对象来配置通知类型。

Static and dynamic notification interfaces

当正确类型的通知到达时,watchOS会根据几个因素选择是否显示您的静态或动态界面。当动态界面不可用时,或没有足够的权力保证显示动态界面时,或者当您明确告诉watchOS不要显示动态界面时,watchOS会自动在Notification Center中显示静态界面。在所有其他情况下,watchOS会显示您的动态界面。做出选择后,watchOS加载相应的故事板资源并准备如下图所示的界面。除了需要处理通知界面控制器特有的通知payload外,动态界面的加载过程与应用程序的其他界面控制器的加载过程基本相同。

Preparing the notification interface

将自定义通知界面添加到您的应用程序

为您的应用程序创建Watch应用程序目标时,默认情况下会选择包含通知场景选项,Xcode为您提供一个空的故事板场景和用于通知界面控制器的自定义子类。如果在创建目标时禁用此选项,或者需要创建其他通知界面,则可以稍后创建通知界面。

要创建新的通知界面,请将通知界面控制器对象拖放到故事板文件。新界面最初只包含静态界面控制器。要添加动态界面,您必须执行一些额外的配置步骤。

配置动态通知界面控制器

  1. 在您的项目中,创建一个新的WKUserNotificationInterfaceController子类。创建新的源文件并将其添加到您的WatchKit扩展目标。给你的子类一个适当的名称,以区别于其他通知界面控制器。
  2. 启用通知类别的Has Dynamic Interface属性。这一步将动态场景添加到故事板文件。
  3. 将您的动态通知界面控制器的类设置为您在步骤1中创建的类。

应用程序可能有多个通知界面,但每个界面的类别必须是唯一的。

配置自定义界面的类别

每个通知界面都必须有一个分配的通知类别,告诉Apple Watch何时使用它。传入的通知可以在其payload中包含类别键值,其值是您定义的字符串。 Apple Watch使用类别名称来决定显示哪个通知场景。如果传入通知不包含类别字符串,Apple Watch会显示配置了默认类别的通知界面。

要为通知界面分配通知类型,请在故事板中选择Notification Category对象,然后转到Attributes检查器,如下图所示。在检查器的名称字段中输入类别名称。 您还可以在检查器中为自定义界面设置窗框颜色和标题文字颜色。

Configuring the notification type information

在生成远程通知时,服务器通过在payload的aps字典中包含类别键值来指定通知类型。对于本地通知,您可以在UNMutableNotificationContent对象的categoryIdentifier属性中指定此值。

注意
类别字符串还定义操作按钮或文本输入控件是否附加到通知界面的末尾。有关支持自定义操作的更多信息,请参阅Configuring Actionable Notifications

配置静态通知界面

使用静态通知界面来定义自定义通知界面的简单版本。静态界面的目的是在您的WatchKit扩展无法及时配置动态界面的情况下提供回退界面。静态界面也显示在通知中心。创建静态界面的规则如下:

  • 所有图片必须位于Watch应用程序包中。
  • 界面不能包含控件,表格,地图或其他交互元素。
  • 界面的notificationAlertLabel的outlet必须连接到标签。标签的内容设置为通知的提示消息。所有其他标签的文本不会更改。

下图显示日历应用程序中自定义通知界面的静态和动态场景的配置。通知箭头指向静态场景,其中包含一个自定义图标和两个标签。在静态接口中,包含字符串<message>的标签是与notificationAlertLabel的outlet关联的标签,因此会在运行时接收通知的提示消息。

Static and dynamic scenes for a single notification type

配置动态通知界面

动态通知界面使您可以为用户提供更丰富的通知体验。通过动态界面,您不仅可以显示提示消息,还可以显示更多信息。您可以合并其他信息,配置多个标签,显示动态生成的内容等等。

要实现动态通知界面,您必须创建一个自定义的WKUserNotificationInterfaceController子类。如何实现该子类将决定通知界面中显示的信息。

设计您的动态界面

像其他任何界面控制器场景一样配置动态界面。在您的子类中包含outlets来引用场景中的标签,图像和其他对象,并使用这些outlets在运行时配置场景的内容。点击通知界面启动应用程序,所以通知界面不应该包含交互式控件。

  • 在大部分界面上使用标签,图像,组和分隔符。
  • 仅在您的界面中根据需要包含表格和地图。
  • 不要包含按钮,开关或其他交互式控件。
  • 使用SpriteKit场景,SceneKit场景或内嵌视频来生成视觉丰富的通知。

在运行时配置您的动态界面

当适当类型的通知到达时,watchOS将从故事板显示适当的场景,并要求您的WatchKit扩展来实例化相应的WKUserNotificationInterfaceController子类。下图显示了watchOS准备界面的步骤。在初始化通知界面控制器之后,watchOS使用didReceiveNotification:withCompletion:方法将payload数据传递给它。您可以使用有payload数据来配置通知界面的其余部分,然后调用提供的完成处理程序块来让watchOS知道您的界面已准备就绪。

Configuring the dynamic notification interface

始终使用didReceiveNotification:withCompletion:方法来配置您的通知界面。在实现该方法时,一旦配置了界面,就立即执行提供的完成处理程序。如果等待时间过长,Apple Watch会放弃尝试显示动态界面,而是显示静态界面。如果您希望watchOS显示您的静态界面,请在调用完成处理程序时指定WKUserNotificationInterfaceTypeDefault常量。

注意
通知界面仅支持使用标签和其他文本的系统字体。自定义字体只在主Watch应用程序中受支持。

测试您的自定义通知

当你准备好在模拟器中测试你的动态界面时,如果你还没有这样做,那么创建一个自定义的构建方案来运行你的通知界面。在配置界面时,指定包含要传递到界面的测试数据的JSON数据文件。Xcode提供用于指定这些数据的自定义JSON文件。有关更多信息,请参阅Specifying a Payload for Remote Notification Testing and Debugging

在真实设备上,通常是在您的Apple Watch处于手腕并且iPhone已锁定的情况下,Apple Watch会收到通知。要在设备不在手腕上时测试通知界面,请执行以下操作:

  • 在配对iPhone上的Apple Watch应用程序中,禁用“腕检测”选项。 (您可以在常规设置中找到此选项。)
  • 确保您的Apple Watch不在充电。
  • 确保你的iPhone被锁定。

有关设置构建方案和配置有效内容数据的更多信息,请参阅Building, Running, and Debugging Your Watch App

 

转载自http://blog.derekcoder.com/2018/01/15/app-programming-guide-for-watchos/

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注