鉴于CSDN的Markdown很多bug,大家请看这里: WatchKit-Programming-Guide-Apple-Watch开发指南官方翻译及总结
Overview
### Developing for Apple Watch
Apple Watch可以让用户以一种很私密,不招摇的样式查看信息(官方文档是这样的哈,但是我想大多数人不会不招摇的查看Apple Watch上的信息??).用户可以在不拿出手机的情况下,通过查看Apple Watch快速的获取重要的信息.
Apple Watch Home screen:
![](http://7xpl1c.com1.z0.glb.clouddn.com/AppleWatchHumanInterfaceGuidelineswatch_hero_2x.png?imageMogr2/thumbnail/!100p)
Apple Watch app由两个独立的部分组成:Watch App和WatchKit Extension.Watch App包含app中所有界面的storyboard和资源文件,WatchKit Extension包括管理这些界面和如何响应用户的交互的代码.这两部分虽然一直都是iOS app的一部分.但是其在WatchOS 1 和WatchOS 2上的配置和安装是不一样的.在WatchOS 2上这两部分都被安装和运行在Watch上.但是在WatchOS 1,app运行在Watch上,WatchKit extension运行在用户的手机上.
在watchOS 1和watchOS 2上使用的技术大部分是一致的.但是每个平台还存在不同的差异.更多信息请参考: watchOS 2 Transition Guide
Watch app和WatchKit extension一起作用来展示app的内容.除了Watch app的界面外,还可以提供一些专门的界面(optional)来展示数据.当然这些界面也是Apple Watch设计的一部分,它们可以更快速的给用户传递重要的信息.
#### The Watch App
当用户从home screen点进来加载的的是Watch app.可以支持一个或多个screen.使用Watch app可以展示能够在Apple Watch上显示的所以内容(一般是iOS app内容的一个小子集).Watch app和WatchKit extension协同工作:Watch app相当于脸:包含所有界面的storyboard和资源文件:WatchKit extension相当于大脑:包含管理内容,如何响应用户的交互和更新界面的代码.
关于如何开发一个Watch App请看这里: [UI Essentials](#anchor_UIEssentials)
#### The Glance Interface
Glance(optional)是一个只显示您app中最重要信息的界面.顾名思义,glance就给用户快速浏览内容用的.glance不能滚动,整个界面必须是一个单一的screen.glance中的信息是只读的,所以不能包含按钮,switch或者其他可交互的控制.点击glance界面可以跳到您的Watch app.Glance虽然不能单独运行,但是它的安装和您的Watch app安装是分开的.同样glance的storyboard也在Watch app中,其代码配置在WatchKit extension中.
关于如何创建一个glance请看这里: [Glance Essentials](#anchor_GlanceEssentials).
#### Custom Interfaces for Local and Remote Notifications
Apple Watch和其配对的手机一起工作显示本地或者远程的通知.首先苹果会用最简单的界面–short look–来显示进来的通知,如果用户的动作表面他想进一步了解该信息的话,取而代之的是long look界面,此界面会显示该通知的更多内容.您可以自定义long look的界面,为其添加自定义的图形或者对其数据进行排版.在iOS 8中,Apple Watch对actionable通知进行了动态支持.actionable通知可以为您的通知界面添加相关的动作按钮.比如对于一个约会要求的通知可能包含接受和拒绝选项.当您的iOS app注册了actionable通知后,Apple Watch会自动的为其添加合适的选项.您需要做的就是在WatchKit extension中处理用户的选择.
#### Complications
Complications(组件)是显示在表面的用于传达重要信息的小元素.当用户查看时间的时候,组件总是显示的.大多数表面支持至少2到3种组件.用户可以自定义要显示哪些组件.关于组件的更多信息和如何实现请看这里: [watchOS 2 Transition Guide](https://developer.apple.com/library/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/index.html#//apple_ref/doc/uid/TP40015234)
#### Designing Your User Interface
Apple Watch本身是比较个性化的,所以需要对其进行特殊的设计.您的界面需要展示重要的信息,能快速导航和交互.这样的界面就意味着您不能单纯的复制iOS app,而是要和其进行互补.关于如何设计Apple Watch的界面请看这里: [Apple Watch Human Interface Guidelines(Watch人机交互指南)官方文档翻译](http://coderlady.com/2015/12/24/Apple%20Watch%20Human%20Interface%20Guidelines/)
### Configuring Your Xcode Project
Watch app需要一个存在的iOS app.在您的iOS app配置中需要添加一个新的Watch app target:包括Watch app和WatchKit extension.在AppStore中,这两部分都由您的iOS app管理.Xcode提供的Watch app target包含创建Watch app的所有东西.
>> 注意: WatchKit需要iOS8.2以上的SDK.
#### 在现有的iOS项目添加Watch App
创建Watch App必须要有iOS项目.Watch app部署在一个单独的target上,会和您的iOS app一起built和打包.在现有iOS app上添加一个Watch app步骤:
1. 用Xcode打开iOS工程.
2. 选择 File > New > Target,选择Apple Watch部分.
3. 选择WatchKit App点击next.
4. 如果您打算实现glance或者自定义通知界面,选择相应的选项.对于通知界面,即使您不打算马上实现也建议您勾选Notification Scene选项.因为该选项会为您的项目添加一个额外的debug界面,否则以后您就只能手动添加了.
5. 点击finish.Xcode会为您的Watch app和WatchKit extension添加需要的文件到iOS项目中. bundle IDs会自动基于您iOS项目的 bundle ID进行配置,这三个的bundle ID必须一致,如果您更改了iOS项目的bundle ID,相应的必须对Watch app和WatchKit extension的bundle ID进行修改.
#### App Target结构
添加一个Watch app target会创建两个新的可执行文件并且会更新您项目的依赖.iOS项目bulid的时候会创建三个可执行文件(the iOS app, WatchKit extension, and Watch app)并且会把它们一起打包.
Target structure in watchOS 1:
![Target structure in watchOS 1](http://7xpl1c.com1.z0.glb.clouddn.com/AppleWatchHumanInterfaceGuidelinestarget_structure_2x.png?imageMogr2/thumbnail/!100p)
上图描述的是iOS app和WatchKit可执行文件的结构.Watch app会打包进您的WatchKit extension,而WatchKit extension又会打包进iOS app中.当用户在手机上安装iOS app的时候,如果有和手机配对的Apple Watch,系统会提示用户安装Watch app.如果用户同意安装,iOS会自动的对其安装.
#### Build, Run, and Debug阶段Xcode还会为Watch app的bulid和debug单独的创建一个build scheme.
当创建Watch app target的时候,Xcode会自动的配置一个build scheme来运行和debug您的Watch app.使用这个scheme可以让您的项目在iOS模拟器或真机上运行.对于包含glance和自定义通知界面的app,您必须配置额外的build schemes来对这些界面进行测试.使用glance scheme在模拟器上来debug您的glance界面,或者使用notification scheme来测试动态和静态的通知界面.
配置自定义为glances和notifications创建build schemes:
1. 选中存在的Watch app scheme.
2. 在scheme菜单中选择Edit Scheme.
![Target structure in watchOS 1](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideedit_scheme_menu_2x.png?imageMogr2/thumbnail/!100p)
- 复制存在Watch app scheme,给这个schem一个合适的名字.比如”Glance - My Watch app”,来这个scheme是用来运行和debugglance的.
- 选择scheme editor左侧列表的run.
- 在info列,选择和是的可执行scheme.
![Target structure in watchOS 1](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideduplicate_scheme_2x.png?imageMogr2/thumbnail/!100p)
- 关闭scheme编辑器来保持修改.
当您为您的通知界面创建一个build scheme的时候,在测试的时候指定一个JSON文件来作为通知的payload.更多信息请看这里:Specify a Notification Payload for Testing
#### 测试指定通知的负载
当在模拟器上debug自定义的通知界面是时候,可以为要加载的数据指定一个用来测试的JSON文件.使用scheme编辑器来指定您想要运行的通知界面的payload.这个payload本身是一个后缀名为.apns的文件.
>> 当创建Watch app target的时候如果您勾选了Notification Scene选项,Xcode会自动提供用于测试的 PushNotificationPayload.apns文件(这个文件在WatchKit extension的Supporting Files里面).您也可以稍后自己创建.
PushNotificationPayload.apns文件包含您需要模拟的远程通知的大多数key.如果需要您也可以添加更多的key.下图就是JSON文件的格式:
A simulated remote notification payload:
![Target structure in watchOS 1](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidePushNotificationPayload.apns_2x.png?imageMogr2/thumbnail/!100p)
大多数JSON数据以字典的形式打包并且在运行时提交给您的代码.因为模拟器不能访问iOS app的注册actions.所以您要使用payload文件来指定您界面的action button.WatchKit的模拟器Actions key包含一个字典数组,每一个字典代表一个加到您界面上的action button.每一个字典包含以下几种key:
* title(required): action button的title.
* identifier(required): value是一个传给界面控制器application:handleActionWithIdentifier:forLocalNotification:completionHandler: 或者 application:handleActionWithIdentifier:forRemoteNotification:completionHandler: 方法的string.
* destructive(optional): value是0或1.1代表这个按钮是一个破坏性动作(和action sheet的红色按钮类似).
用合适的JSON payload配置好build scheme来测试您的通知界面.当您选择一个执行的通知界面的时候,Xcode会为您选择的payload文件添加一个menu.您可用为不同通知的payload创建不同的build scheme,或者在测试之前为一个存在的build scheme更新不同的payload文件.
### Watch App Architecture
您的Watch app和WatchKit extension协调工作来显示app界面.在Apple Watch app上的交互会在合适的设备上加载您的WatchKit extension.然后Watch app和WatchKit extension会一同运行界面直到用户停止交互.至于这两部分如何协调工作取决于在watchOS 2还是watchOS 1上运行.下面插图说明了两个版本上Watch app和WatchKit extension是如何工作的.
![Communication between a Watch app and WatchKit extension](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuide275E78CC-778A-46B4-A883-B8380B3FE38B.jpg)
#### 界面控制器
每一个scene都被一个WKInterfaceController类型的界面控制器管理.和iOS的viewController类似,它们展示和管理在界面显示的内容和对用户的交互作出反应.但是不同的是,界面控制器不是真正的管理界面而是被WatchKit管理.Watch apps一般包括多个interface controller.每一个界面都显示不同的内容.屏幕上只能显示一个interface controllers.更多信息请看这里: [Interface Navigation](#anchor_InterfaceNavigation)
>> 注意: Glance和自定义的通知界面是独立于您的app的interface controller的.详细信息请参考: [Glance Essentials](#anchor_GlanceEssentials)和[Notification Essentials](#anchor_NotificationEssentials).
#### Watch App的生命周期
用户交互决定着app的启动和生命周期.可以通过Home screen,glance,自定义UI界面的通知启动您的app,这些方式都能加载您的Watch app并且和WatchKit extension进行通信.Watch app和WatchKit来回的交互信息直到用户停止交互.此时iOS会挂起extension,直到用户的下一次交互.在启动时WatchKit自动的加载适当的storyboard.如果用户浏览的是glance,WatchKit会加载storyboard中的glance场景.如果用户直接启动的是app,watchKit会加载您的app.在场景加载完以后,watchKit会请求WatchKit extension来创建和界面控制器通信的对象.下图是这一系列的步骤,更多信息请看: [UI Essentials](#anchor_UIEssentials).
![Launching a Watch app](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidelaunch_cycle_2x.png?imageMogr2/thumbnail/!50p)
使用interface controller的init和awakeWithContext: 方法来加载需要的数据,把这些值赋给您的界面元素为界面的显示做准备.不要使用 willActivate来初始化您的interface controller.这个方法是用来在界面将要出现在屏幕上时做最后一刻的改变.在watchOS 2, WatchKit会调用didAppear方法来让您知道界面是否真正的显示在屏幕上.当界面显示在屏幕上的时候,您界面控制器的自定义方法来处理用户的操作.
注意: glance界面不支持交互.tap glance界面只能加载您的app.
只有当用户和Apple Watch上的app进行交互时WatchKit extension才会一直运行.在Apple Watch上的交互必须精简.所以界面控制器必须轻量级,所以不要运行长时间跑的任务.当用户离开了或者停止和app交互的时候,iOS会deactivates当前的界面控制器,最终挂起您的extension.
![The life cycle of an interface controller](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidewatch_app_lifecycle_simple_2x.png?imageMogr2/thumbnail/!50p)
当用户重启与之配对的iPhone的时候,在第一次锁屏之前,一些Watch app可能不能使用(在watchOS 1上这些app甚至不会运行).watchOS 2在没锁屏之前能够运行,但是不能和iPhone进行通信.
#### 在模拟器上测试
测试的时候,您可以通过锁屏和解锁模拟器来测试您的代码在activation和deactivation状态是不是正确的运行.当您在Hardware > Lock,锁定模拟器是时候,WatchKit会调用当前界面控制器的didDeactivate方法.当解锁模拟器的时候,WatchKit会调用didDeactivate方法.
#### 和iOS app共享数据
在watchOS 1,iOS app和WatchKit extension使用app group来存储共同的数据文件.app group是线程安全的.一般的,每一个进程在其单独的沙盒环境中运行,但是app group可以让两个进程共用一个目录,并且能共享文件和user defaults.
>> 注意: 在watchOS 2,iOS app and WatchKit extension通过Watch Connectivity framework 进行通信.WatchKit extension可能仍然使用app group来和与之通信的Watch app来共享媒体文件.关于watchOS 2上进程之间的通信请看这里: [watchOS 2 Transition Guide](https://developer.apple.com/library/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/index.html#//apple_ref/doc/uid/TP40015234).
#### 和iOS app进行通信
在watchOS 2,WatchKit extension使用Watch Connectivity framework和其父iOS app进行通信.这个framework支持在两个进程间传递文件和字典数据提供双向通信.这个framework还支持后台发送数据以便在启动的时候等待其他app.
>> 注意: 在watchOS 1,使用openParentApplication:reply: 来请求和父iOS app进行通信并且不一定能接受到相应(用这个方法来给iOS app发送请求,使用NSProcessInfo的 performExpiringActivityWithReason:usingBlock: 方法来防止WatchKit extension在发送消息或收到反馈之前deactivated ).iOS app delegate必须要实现 application:handleWatchKitExtensionRequest:reply: 方法来接收信息并作出反馈.关于Watch Connectivity framework更多信息请看这里: [watchOS 2 Transition Guide](https://developer.apple.com/library/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/index.html#//apple_ref/doc/uid/TP40015234).
### Leveraging iOS Technologies
watchOS 1的WatchKit可以使用iOS app中的技术,因为它是iOS app的扩展.但一些技术可能被限制并且不推荐使用一些第三方的技术.下面是一些参考:
* 该技术必须在iPhone上被允许使用: 当使用一些特殊的系统技术的时候,比如说地理位置,必须要获取用户的允许.在WatchKit extension上使用这些技术的时候会在iPhone上触发请求用户允许的弹框.Apple Watch也会显示一个弹框让用户在iPhone上查看该许可请求.关于需要获取用户许可的技术请在[App Programming Guide for iOS]中查看 “Supporting User Privacy”部分.
* 不要在后台运行: Watch apps是前台运行的因为只有当用户与之交互的时候Watch apps才会运行.因此WatchKit extension不能在后台执行任务.
* 不要运行耗时的任务: 当用户不和watch app交互的时候,WatchKit extension会很快的被挂起.因为Watch app的交互都很简短,所以在一个耗时任务还没完成之前,extension很可能就被完全的挂起了.
对于耗时的任务,最好的办法就是让iOS app运行.比如说定位,可以在iOS app上开始,在extension上实时更新.您的iOS app采集需要的数据然后放到app group中,然后您的extension就能访问这些数据了.关于iOS app和WatchKit extension如何通信,请看这里: [Communicating with Your Containing iOS App](#anchor_CommunicatingwithYourContainingiOSApp).
#### Handoff
可以使用Handoff将Apple Watch当前的任务转移到其他的设备上.使用WKInterfaceController的updateUserActivity:userInfo:webpageURL: 来创建活动并把其发布到其他设备上.除了glance的深度链接以为,Apple Watch不处理其他设备上的活动.在glance的界面控制器,您可以使用一个字典来存储对您主app有用的信息.当用户通过点击glance加载您的app的时候,WatchKit可以将这个字典传递给app的extension delegate或者主界面控制机.您可以使用这些数据来更新您app的UI.关于如何通过glance传递信息给您的app,请看这里: [Customizing App Launch from Your Glance](#anchor_CustomizingAppLaunchfromYourGlance).
#### Remote Control Events and Now Playing Information
Apple Watch可以远程控制与之配对的iPhone上的音频和视频的播放. Now Playing glance通过传输控制系统来控制app中当前播放的内容.当有Now Playing” app,iOS中注册了MPRemoteCommandCenter对象的话会自动的接收到这些事件.您不需要在WatchKit extension中做额外的操作来支持收到从Apple Watch来的远程通知.
>> 注意: 对于喜欢,不喜欢,设置标签这些命令, Apple Watch使用了localizedShortTitle代替MPFeedbackCommand中的localizedTitle string.
Now Playing glance会自动的播放iOS app上正在播放的MPNowPlayingInfoCenter对象.当您的app播放其内容的时候,也应该更新nowPlayingInfo字典中的值.Apple Watch会自动取回并显示该信息.此外点击这个Now Playing glance可以加载此app的Watch app(如果可用).在watchOS 2,Now Playing glance会显示被app播放支持extended的音频内容的track信息.glance可以查看正在播放的文件的WKAudioFileAsset和WKAudioFilePlayerItem属性.
关于如何支持远程控制事件和如何在iOS app中显示信息,请看: [Remote Control Event](https://developer.apple.com/library/watchos/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html#//apple_ref/doc/uid/TP40009541-CH7).关于如何在watchOS 2上播放extended audio,请看这里: [watchOS 2 Transition Guide](https://developer.apple.com/library/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/index.html#//apple_ref/doc/uid/TP40015234).
## Watch Apps
### UI Essentials
创建Watch app第一步就是界定您的storyboard.每一个scene都是您app界面的一部分.您可以为不同的Apple Watch尺寸自定义不同的scene,也可以为您的界面设计不同的外观(我觉得这段显然是废话??).
#### Assembling Your Storyboard Scenes
和iOS的布局不同的是,在Apple Watch上,Xcode会自动安排元素,垂直堆一列.运行时,Apple Watch会将这些元素显示在您可以用的空间上.尽管Xcode安排您大体的界面,大多数元素的位置和大小还是通过Attributes inspector设置的.Group也是您界面布局很重要的一部分.group可以设置其包含元素的水平或垂直对齐.group之间可以嵌套使用.group模式是不可视的,但是如果需要您可以设置它的背景色和图片.下图展示了如何在storyboard中安排各种元素.
![Interface objects in Xcode](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidestoryboard_layout_2x.png?imageMogr2/thumbnail/!50p)
当在Xcode中创建界面的时候,让元素在界面中自动设置大小.app的界面必须在Apple Watch的不同尺寸上都能正常运行.让系统调整对象的大小来适应可用的空间,最大程度的减少您为适配不同尺寸的代码量.
#### Accommodating Different Display Sizes
在storyboard editor中的界面默认是应用到所有屏幕上的,但是您如果想在不同的尺寸上显示不同的内容,也可以单独的为每种尺寸设计不同的内容.比如您可以为不同的尺寸设置不同的元素间隔或者设置不同的图片.当为不同尺寸设置不同界面的时候,点击Attributes inspector的加号按钮,选择特定的尺寸.
![Customizing attributes for different devices](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidescalable_text_2x.png?imageMogr2/thumbnail/!50p)
但是不同尺寸的界面不应该有明显的差异,所以尽量不要为不同的尺寸准备不同的显示界面.元素的spacing和margins尽量不要变.尽管可以设置某个元素在不同尺寸界面上不安装,但是不推荐这样用.尽量在所以尺寸上显示相同的内容(以上这部分是sizeclass知识,不太明白的可以去单独学一下).
#### Updating Your Interface at Runtime
在运行时,界面控制器可以对storyboard中的对象进行以下修改:
* 设置或更新数据
* 改变一些支持修改的外观
* 改变大小
* 改变透明度
* 设置显示或隐藏
您也可以添加对象或者改变现有对象的顺序.尽管您不能删除,但是可以隐藏.因为删除元素可能导致界面布局错误.
#### Setting Your App’s Key Color
每一个app都有一个相关联的key color.这个颜色会应用到下面的元素:
* status bar中的title
* short-look通知的app名字
app的key color保存在app storyboard的Global Tint属性中.可以选择storyboard,在File inspector中对Global Tint属性进行修改.
#### Internationalizing Your Interface
storyboard默认是支持国际化的.这个特性可以将您storyboard中的字符自定的添加到项目的Localizable.strings文件中.您要做的是在不同的国际化版本中对其进行翻译.当您在运行时创建storyboard的时候,Xcode会插入合适的本地化字符串.
与其把多个按钮放在一行,不如把他们垂直的放成一列,这样就有足够的空间来显示不同的国际化文字.
国际化的文字和图片设置和iOS的一样:
* 使用NSLocalizedString宏来加载不同文件中的资源
* 使用NSNumberFormatter对象来格式化数字.
* 使用NSDateFormatter对象来格式化日期.
在WatchKit extension中使用NSLocale对象会返回在Apple Watch上配置国际化信息.更多信息请看: [Internationalization and Localization Guide](https://developer.apple.com/library/watchos/documentation/MacOSX/Conceptual/BPInternational/Introduction/Introduction.html#//apple_ref/doc/uid/10000171i).
### Interface Navigation
对于不只包含一个screen的Watch apps,您必须选择一个合适的导航.Watch apps支持两种导航结构(这两种结构是相互独立的):
1. Page based: 这种风格适合哪种之间的内容没有多大联系的数据.一个Page based界面包含两个以上独立的界面控制器,但是在任一刻只能显示一个界面.在运行时,用户通过作用滑动来切换界面.下面的点点指示当前页面的位置.
2. Hierarchical: 这种风格适合哪种master-detail结构.而且更容易对其进行扩展.
尽管这两种导航结构不能混用,但是可以对这两种导航结构进行扩展.Modal presentations可以打断当前的工作流来请求输入或者显示信息.Modal presentations本身包含一个单独的screen或者布置在一个page-based布局类型上的多个screen.
#### implementing a Page-Based Interface
通过设置两个界面的next-page在storyboard中配置的Page-Based界面.下面是在界面之间创建next-page segue.
1. 在storyboard中为每一个page添加界面控制器.
2. Control-click app主界面控制器,拖线segue到另一个界面控制器scene.
3. 松开鼠标.
4. 选择next page关系.
5. 同样的方法脱其他的控制器,在storyboard中的顺序就是最终显示的顺序
![](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidecreateanext-pageseguebetweeninterfacecontrollers.jpg?imageMogr2/thumbnail/!50p)
在app加载的时候,在storyboard中使用这些segues定义的就是page-based界面.您可以使用reloadRootControllersWithNames:contexts: 方法来改变您想要加载的page集合.比如,您可以在init方法中调用这个方法来来强制WatchKit加载一个不同的page集合.WatchKit根据用户的界面跳转来激活或停用相应的界面控制器.在watchOS 1,使用didDeactivate和willActivate方法来确定哪个界面要显示(这两个方法的调用顺序不是固定的).在watchOS 2,使用didAppear和willDisappear方法来判定哪个控制器被显示.
#### Implementing a Hierarchical Interface
继承结构的导航结构WatchKit会使用当前界面的segues或者调用pushControllerWithName:context: 方法来跳到下一界面.在storyboard界面,可以给button, group或者table row创建跳转的push segues.如果想要手码实现跳转,可以调用pushControllerWithName:context: 方法.当push出一个想的界面的时候,可以使用context来传递参数.这个参数会在界面显示之前就被传递到控制器.push出的控制器会在左上角显示一个”
#### Presenting Interface Controllers Modally
modal出来的界面会暂时中断当前的导航流来提示用户或者显示一些信息.您可以从任一界面modal出一个界面控制器,具体实现如下:
* 在storyboard中创建一个modal segue.
* 调用presentControllerWithName:context: 方法显示一个单一的控制器.
* 调用presentControllerWithNames:contexts: 方法来多个使用page-based布局的界面.
把创建的modal segue拖到您想要显示的界面控制器上.当使用modal segue连接多个界面控制器的时候,要先使用next-page segue把界面之间连接起来,然后modal segue连接组中的第一个界面就可以.如果您连接的是中间的控制器,那么前面的控制器不会被显示.左上角会显示界面控制器的title.当用户点击的时候,会关闭moda出的界面.比如说,当您显示信息的时候,可以把这个title设成Done或者Close.如果没有指定的话,会默认显示Cancle.
### Interface Objects
WatchKit extension使用界面对象控制着Watch app的UI.一个界面对象是一个WKInterfaceObject对象或者其子类.界面对象是不见的,但他们是view的代理对象.WatchKit framework提供的保护界面对象的大多数可视元素(不是全部)是可以直接添加到storyboard scenes上的.
>> 注意: 在Apple Watch上界面对象和与之关联的view通信是单向的.信息从WatchKit extension流向Apple Watch.换句话说,您可以设置界面对象的值但是不能获取其属性.因为从Apple Watch取回,修改和写入数据是有延迟的,而且很耗性能,所以推荐将WatchKit extension中的界面配置信息存储起来.
#### Creating an Interface Object
您添加的界面对象会直接被加到storyboard scene中.在storyboard中添加了一个界面元素以后,在界面控制器中创建一个outlet.在界面控制器初始化的时候,WatchKit会自动的为outlet创建对于的界面元素.下面代码是如何为界面对象创建outlet:
OBJECTIVE - C
@interface MyHelloWorldController()
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* label;
@end
SWIFT
class MySwiftInterfaceController {
@IBOutlet weak var label: WKInterfaceLabel!
}
#### Configuring Your Interface at Design Time
好多布局相关的属性只能在设计的时候配置.比如,可以改变label的文本,color和字体.但是不能改变number of lines 或者 the minimum scale factor.这些属性必须在Xcode中配置.更多信息请看这里: [WatchKit Framework Reference](https://developer.apple.com/library/watchos/documentation/WatchKit/Reference/WatchKit_framework/index.html#//apple_ref/doc/uid/TP40014968).
![Configuring a label object](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideformatted_text_attributes_2x.png?imageMogr2/thumbnail/!50p)
#### Changing Your Interface at Runtime
使用WatchKit extension中的相关的方法来更新app的UI.当一个界面控制器active或者初始化的时候可能需要更改其界面显示.在init, awakeWithContext:, 和 willActivate方法中来设置界面元素的数据.或者还可能需要在执行某些操作时更新UI.在初始化过程中,在执行其他操作之前最重要的是让WatchKit来初始化界面控制器.WatchKit 会根据WKInterfaceController和其子类的方法来创建app的界面对象.所以您的所有初始化代码都必须先调用super方法.下面代码展示了界面控制器中一个WKInterfaceLabel类型的名字为label的的outlet在init方法中的实现.
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;
}
SWIFT
override init() {
// Initialize properties here.
super.init()
// It is now safe to access interface objects.
label.setText("Hello New World")
}
WatchKit framework对对app的界面对象进行优化.当在一个run loop循环中为一个或多个界面对象设置值的时候,这些新值会被合并,并且一起传递给Apple Watch.只有最后改变的值才会传递给设备.而且最重要的是,将相同的值给同一个属性会生成log信息来帮您跟踪重复调用问题.更多信息请看这里: [WatchKit Framework Reference](https://developer.apple.com/library/watchos/documentation/WatchKit/Reference/WatchKit_framework/index.html#//apple_ref/doc/uid/TP40014968).
#### Responding to User Interactions
使用buttons, switches和其他可交互的动作按钮来为app提供可交互操作.当点击一个按钮或者按钮的值改变的时候,WatchKit会调用界面控制器中与之对应的方法.每一种界面元素的方法都有一个固定的格式,如下图所示:
![Gathering text input from the user](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideActionmethodsforinterfaceobject.jpg?imageMogr2/thumbnail/!50p)
界面使用segues 或者 table:didSelectRowAtIndex: 方法来响应在table中的行的点击事件.使用segue来显示其他的界面控制器的时候,在执行segue之前WatchKit会调用 contextForSegueWithIdentifier:inTable:rowIndex: 或者 contextsForSegueWithIdentifier:inTable:rowIndex: 方法,在这个方法里可以设置要传递给下一界面的数据.如果是使用table:didSelectRowAtIndex: 方法进行跳转,可以这设置您要做的操作.当界面控制器被初始化并显示在屏幕上的时候,WatchKit只有在用户进行交互的时候才会调用界面控制器的方法.如果要想在用户不交互的时候更新界面必须使用NSTimer对象.
#### Hiding Interface Objects
storyboard中必须包含所以需要在运行时显示的界面对象.如果您要根据当前可以的信息配置界面显示,可以把不需要的元素隐藏.当设置隐藏的时候,实际上是在界面中删除了.在布局的时候,隐藏的元素会当被删除对待.使用setHidden: 方法隐藏.
### Text and Labels
使用label来显示文字.label支持可以在运行时被程序改变的formatted text.更多信息请看这里: [WKInterfaceLabel Class Reference](https://developer.apple.com/library/watchos/documentation/WatchKit/Reference/WKInterfaceLabel_class/index.html#//apple_ref/doc/uid/TP40014960).
#### Using Custom Fonts
Watch apps, glance interfaces, 和 notification interfaces可以使用系统字体.您主app的界面控制器可以使用自定义字体(但是Glance 和 notification interfaces必须使用系统字体).关于自定义字体,您必须遵循实现以下步骤:
* 在Watch app和your WatchKit extension bundle都要包含自定义字体文件.
* 在Watch app的Info.plist添加UIAppFonts key.只有这个key来指定要添加到bundle中的字体.更多信息请看这里: [Information Property List Key Reference](https://developer.apple.com/library/watchos/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009247).
>> 重要: 必须在WatchKit extension和Watch app都要包含这个字体.当在WatchKit extension创建attributed strings的时候,attributed strings需要字体对信息进行解码.
使用自定义字体格式化文本的时候,使用字体信息创建一个属性字符串,然后使用这个属性字符串来设置label的文字.下图所示,字体的名称和大小和属性字符一起编码用来更新Apple Watch上label的文字.如果指定的字体不存在的话默认会使用系统字体.
Using a custom font in a label string:
// 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)
#### Managing Text Input
WatchKit提供了一个标准的界面来获取用户输入的内容,当界面显示的时候,运行用户输入一些通过语言输入的短语或者emoji.如下图所示:
![Gathering text input from the user](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidetext_input_phrases_2x.png?imageMogr2/thumbnail/!50p)
使用当前活动的界面控制器的presentTextInputControllerWithSuggestions:allowedInputMode:completion: 方法来显示上面界面.当这个界面显示的时候,可以指定输入类型,使用block来执行结果.还可以为界面指定一组初始化的短语.用户可以使用这些准备好的短语或者使用控制输入不同的短语.下面代码展示了如何配置输入控制器和处理结果.在指定了初始化短语和输入模式之后,控制器将异步的运行.当用户选择了一个item或者取消输入的时候,block就会在主线程上执行.使用block来取回用户选择的文字或者emoji图片,并更新您的app.
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.
}
}];
#### Internationalizing Your Text Code
Watch apps的国际化和iOS的一样:
* Xcode的国际化支持storyboard和xib文件. Base internationalization 可以让您在只有一组storyboard的情况下就能够支持所有的本地化.每种国际化会被存储在不同的文件中.
* 使用NSLocalizedString宏来自动的取回当前国际化的string.
* 使用NSNumberFormatter类来根据用户的区域和位置设置格式化数字值.
* 使用NSDateFormatter类来根据用户的区域和位置设置格式化日期.
当对您的app进行国际化的时候,您最需要关注的就是你的界面元素有足够的区域延伸.比如说,与其把三个按钮水平放一组不如垂直摆放,这样就有更多的区域来放文本.关于如何对app进行国际化请看这里: [Internationalization and Localization Guide](https://developer.apple.com/library/watchos/documentation/MacOSX/Conceptual/BPInternational/Introduction/Introduction.html#//apple_ref/doc/uid/10000171i).
### Images
WatchKit提供了以下几种方法来显示图片:
* WKInterfaceImage类来将一个单独的图片或者一个帧动画作为独立的内容显示.
* WKInterfaceGroup,WKInterfaceButton和WKInterfaceController可以指定图片作为内容的背景.
* WKInterfaceSlider滑杆的显示增大和减小的图片.
* WKInterfaceMovie中用来显示video或者audio的海报.
* WKInterfacePicker的内容可以包含image.
#### Specifying Your Image Assets
以下是创建图片资源时应遵循的准则:
* 必须使用PNG格式.
* 为您的界面创建大小合适的图片,对于image大小不能控制的使用setWidth: 和 setHeight: 方法来保证image以一个合适的大小显示.
* 使用image assets来管理图片.image assets可以让您为不同的设备指定不同的图片.
#### Using Named Images to Improve Performance
下面几种方法可以改变当前的图片:
* 使用 setImageNamed: 或者 setBackgroundImageNamed: 方法来设置已经在Watch app bundle中或者在设备cache中的图片.
* 使用setImage:, setImageData:, setBackgroundImage:, 或者 setBackgroundImageData: 方法通过无线网来将WatchKit extension的image数据转换成Watch app的image.
通过image指定图片时更高效,因为只有名字会传递到您的Watch app上.WatchKit通过指定的名字搜寻Watch app bundle中的图片.在watchOS 1,在extension创建的image在使用浅必须通过无线传递给用户的Apple Watch.因为通过imageNamed: 方法来加载的是WatchKit extension’s bundle中的图片,而不是Watch app’s bundle中的图片.最高效的办法就是将图片放在Watch app bundle中,使用setImageNamed: 或者 setBackgroundImageNamed: 方法指定图片.
#### Caching Images on the Device
在watchOS 1,可以使用device-side image caches来提高高频率使用的图片的性能.缓存可以让图片从WatchKit extension到Apple Watch的传递只进行一次,却可以使用多次.可以使用WKInterfaceDevice的addCachedImage:name: 或者addCachedImageWithData:name: 方法缓存图片.然后通过以下方式访问:
* 对于WKInterfaceImage对象,调用setImageNamed: 并指定缓存中image的名字.
* 对于WKInterfaceGroup和WKInterfaceButton对象,调用setBackgroundImageNamed: 方法来指定缓存image的名字.
>> 重要: 当缓存图片的时候,使用animatedImageWithImages:duration:方法来为所有动画的frame和缓存的image创建一个单独的UIImage对象.不要单独的缓存分开的图片.
Apple Watch缓存的图片是有大小限制的.每个app大改有5MB的缓存.缓存是永久的,可以在Watch app加载的的时候使用.如果缓存满了,在添加新图片的时候必须把存在的删掉.使用removeCachedImageWithName: 方法来删除单独的图片,或者使用removeAllCachedImages方法来删除所有的缓存.在watchOS 2中不支持图片缓存,因为WatchKit extension直接运行在Apple Watch上,传输图片的话会导致很大的开销.虽然您仍可以在Watch app bundle中存储图片并在使用的时候指定其图片名.但是您可以在不用担心性能和电池寿命的情况下直接的发送其他所有的图片.
### Tables
可以使用table来展示一系列的动态数据.WatchKit支持使用WKInterfaceTable类的single-column table.使用table展示数据需要提前定义好布局并且手码在运行时填充数据.具体来说.您需要进行以下操作:
* 在storyboard文件:
* 添加一个table,并拖线到界面控制器.
* 为table创建至少一个row 控制器.具体请看这里: [Configuring Row Controllers](#anchor_anchor_ConfiguringRowControllers).
* 在代码里:
* 为每一个row控制器创建一个row控制器类.具体请看这里: [Configuring Row Controllers](#anchor_anchor_ConfiguringRowControllers).
* 初始化时,向table中添加row,具体请看这里: [Configuring the Table’s Contents at Runtime](#anchor_ConfiguringTheTable’sContentsAtRuntime).
* 相应table的交互,具体请看这里: [Handling Row Selections](#anchor_HandlingRowSelections).
对于每一个table,可以定义多个不同界面的row控制器类型.在运行时,可以指定您想要的row控制器类型.更多信息请看这里: [WKInterfaceTable Class Reference](https://developer.apple.com/library/watchos/documentation/WatchKit/Reference/WKInterfaceTable_class/index.html#//apple_ref/doc/uid/TP40014965).
#### Configuring Row Controllers
row控制器是table中每一行显示内容的模板.当您在scene添加一个row控制器的时候,Xcode会自动创建一个初始化的row控制器,但是你可以添加更多的.比如说在内容的row,header,或者footer中可能使用不同的模板.下面步骤展示了如何为table添加一个row控制器:
1. 在storyboard中选择table对象.
2. 打开Attributes inspector.
3. 使用Rows attribute来改变数量.
每一个row控制器包含一个初始化的组元素.在这个group,您可以添加labels,images和其他对象.在设计的时候label和image的内容无关紧要,因为运行时可以为其设置内容.每一个row控制器都有一个自定义的类,使用这个类可以访问每一行的内容.大多数row控制器只包含一些界面元素,很少包含代码.您如果添加按钮或可交互的元素,也可以在row类中实现这些控件的方法.
#####为您的row控制器创建一个自定义的类
1. 在WatchKit extension中添加Cocoa Touch类.
2. 您的类要继承自NSObject.
3. 为需要配置的控件设置属性,格式如下:
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* label; // Objective-C
下面是包含一个label和一个image的示例代码:
Objective-C
@interface MainRowType : NSObject
@property (weak, nonatomic) IBOutlet WKInterfaceLabel* rowDescription;
@property (weak, nonatomic) IBOutlet WKInterfaceImage* rowIcon;
@end
SWIFT
class MainRowType: NSObject {
@IBOutlet weak var rowDescription: WKInterfaceLabel!
@IBOutlet weak var rowIcon: WKInterfaceImage!
}
此外您还必须为row类指定identifier,用来在运行时创建row.
##### 在storyboard中配置界面控制器
1. 在storyboard界面,选择界面控制器对象.
2. 选择控制器的Identifier属性来为table指定一个独一无二的值.
3. 在Identity inspector,把这个row类设置成界面控制器的class.
4. 把界面元素连线到自定义的类.
#### Configuring the Table’s Contents at Runtime
在运行时,可以手码实现想table添加row并且配置其内容.把添加和设置row作为您界面控制器初始化的一部分.下面是创建并配置table的row:
1. 根据您要是展示的内容决定row的数量和类型.
2. 使用setRowTypes: 或者setNumberOfRows:withRowType: 方法来创建row.这两种方法都能在界面创建row和在WatchKit extension实例化row的类.实例化的类存在table中,并且使用rowControllerAtIndex: 方法访问.
3. 使用rowControllerAtIndex: 方法遍历row.
4. 使用row控制器设置row的内容.
使用setRowTypes: 和 setNumberOfRows:withRowType: 方法来实例化与之相关的row控制器.在调用其中的方法后,您会去到最新创建的row控制器对象,并且使用他们来配置row.下面代码显示的是设置row中的label和image.string和image由MyDataObject提供(这里并没有展示其具体实现).row的类型为自定义MainRowType类.
- (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];
}
}
当配置table的时候,可以通过限制初始化的数量来提高性能.但是因为table的row也也必须提前创建,创建太多的row也会影响app的性能.精确的行数取决于您的数据的复杂度和创建每一个的时间,但是最好不要超过20行.对于大数量的数据,在初始化的时候只加载一部分,然后让用户滚动来加载更多的数据.最好的解决办法是,最开始只加载最重要的数据.
#### Handling Row Selections
界面控制器可以处理table的选中的行.当用户点击table的row的时候,WatchKit会选中这一行并且会调用table:didSelectRowAtIndex: 方法.使用这个方法可以进行一下相关的操作,比如说展示一个新的界面控制器或者更新row本身的内容.如果您不想table被选中,在storyboard中设置deselect选项.
### Context Menus
Apple Watch上的Force Touch提供一种交互的新方式.除了点击屏幕外,稍用力按压屏幕的时候会显示一个有几个按钮的界面:Context Menus(如果有的话).Context Menus是可选的,您可以使用他们来显示于当前界面相关联的操作按钮组.WatchKit会在您的内容上显示menu.一个context menu会显示至多4个按钮,每一个按钮都有一个image和一个title.点击按钮会触发相应的操作,点击其他界面会关闭context menu.如下图所示:
![A context menu with three items](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidecontext_menu_2x.png?imageMogr2/thumbnail/!50p)
#### Designing Your Menu Items
每一个menu item都包含一个可点击的区域和title.可点击的区域包含一个圆形的背景.这个图片必须是一个template image(alpha通道定义顶部背景的图形).图片的不透明部分为黑色,并且完全或部分透明来透过背景色.您提供的template images 应该比圆形的背景小.关于menu images的大小和创建请看这里: [Menu_Icons](http://coderlady.com/2015/12/24/Apple%20Watch%20Human%20Interface%20Guidelines/#Menu_Icons).
#### Adding a Context Menu to an Interface Controller
context menu是在设计时配种的,但是您也可以在运行时添加或者删除.在设计时,您可以在storyboard中可以为界面控制器添加一个只有Force Touch就会显示的menu.当您初始化您的界面控制器以后,也可以添加item.不管您在storyboard还是手码添加的,总数量不能超过4个.下面是为您的界面控制器添加一个menu的步骤:
1. 打开storyboard文件.
2. 拖进一个menu对象(初始化的menu只包含一个item).
3. 最多再从library众拖进3个item.也可以使用Attributes inspector来设置menu的数量.注意添加的item不能被删除.
4. 对于每一个item,使用Attributes inspector来指导item的image和title.这两个缺一不可.
5. 在界面控制器中为item添加方法.格式如下:
- (IBAction)doMenuItemAction
6. 保存storyboard文件.
调用addMenuItemWithImage:title:action: 或者 addMenuItemWithImageNamed:title:action: 方法在运行时为menu添加item.手码添加的item可以被删除或者会在界面控制被释放的时候自动释放.
#### Handling Taps in a Menu Item
当用户点击item的时候,WatchKit会关闭menu并调用您在界面控制器中以以下语法实现的方法:
OBJECTIVE - C
- (IBAction)doMenuItemAction {
// Handle menu action.
}
SWIFT
@IBAction func doMenuAction() {
// Handle menu action.
}
执行该操作需要的状态都需要保存在您的界面控制器中.比如说如果action需要依赖当前table选中的行.而且如果想要请求用户点击的item的更多信息,您的方法必须present一个界面控制器.
### Settings
偏好设置是用来配置app的运作和外观的不经常变动的数据.如果您的Watch app使用偏好设置来存储配置数据,可以添加一个watch–specific settings bundle到项目中来为用户展示这些信息.这个settings bundle寄生在iOS app,并且这些设置在用户的iPhone的Watch app里显示.
watch-specific settings bundle的工作方式和iOS settings bundle的类似.settings bundle定义了您想要由系统展示的控件和及偏好设置的名称.在iPhone上的Watch app显示用户的设置并且将改变写入与之关联的app数据库中.但是存储的数据库必须和WatchKit extension有一个共享的app group.
>> 注意: 在watchOS 2,WatchKit extension可以读取偏好设置的值,但是不能写(可读不可写).watchOS 2的偏好设置是iOS和Apple Watch的媒介,但是您的任何修改并不会传给iOS.在watchOS 1,WatchKit extensions可以直接方位数据库进行读写.
关于如何settings bundles如何工作请看这里: [Preferences and Settings Programming Guide](https://developer.apple.com/library/watchos/documentation/Cocoa/Conceptual/UserDefaults/Introduction/Introduction.html#//apple_ref/doc/uid/10000059i).
#### Creating Your Settings Bundle
添加一个watch-specific settings bundle到您的iOS app中的步骤:
1. 选择File > New > File.
2. iOS > Resource > Settings Bundle,点击next.
3. 创建名字为Settings-Watch.bundle的settings bundle(为了和iOS app的settings bundle进行区分),并且将其添加到iOS app target中.
watch-specific settings bundle的初始化和iOS app的一样.在Root.plist配置您想要显示的控件.下列代码是WatchKit settings bundle的内容.
Settings-Watch.bundle/
Root.plist
en.lproj/
Root.strings
关于如何配置settings bundle的内容请看: [Implementing an iOS Settings Bundle](https://developer.apple.com/library/watchos/documentation/Cocoa/Conceptual/UserDefaults/Preferences/Preferences.html#//apple_ref/doc/uid/10000059i-CH6).关于具体包含的key请看这里: [Settings Application Schema Reference](https://developer.apple.com/library/watchos/documentation/PreferenceSettings/Conceptual/SettingsApplicationSchemaReference/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007071).
#### Configuring a Shared App Group to Store Your Settings
存储watch-specific settings必须要配置一个共享的app group.这个共享的app group定义了Watch app可以访问在iOS上包含偏好设置的默认数据库上的位置.您的Watch app也可以使用这个app group来访问您的偏好设置.下面是为您的settings设置app group的配置:
* 在capability打开App Groups属性(在In watchOS 2必须在Watch app打开这个属性).选择一个identifier或者创建一个新的.
![](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideConfiguringaShareAppGrouptoStore.jpg?imageMogr2/thumbnail/!50p)
- 在Root.plist文件中添加ApplicationGroupContainerIdentifier key.把这个属性放在plist的顶级中,把值设置成步骤一设置的值.
#### Accessing Settings at Runtime
使用initWithSuiteName: 方法初始化一个新的NSUserDefaults对象可以访问WatchKit extension的偏好设置.把app group的identifier设置为suite name.就可以使用user defaults对象来获取偏好设置中的值.在watchOS 1,您也可以对defaults数据库进行写操作.但是watchOS 2,您的WatchKit extension只能进行读操作.下面代码展示了如何访问一个自定义的group,更多信息请看这里: [NSUserDefaults Class Reference](https://developer.apple.com/library/watchos/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html#//apple_ref/doc/uid/TP40003764).
OBJECTIVE - C
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.example.MyWatchKitApp"];
BOOL enabled = [defaults boolForKey:@"enabled_preference"];
SWIFT
let defaults = NSUserDefaults(suiteName: "group.com.example.MyWatchKitApp")
let enabled = defaults?.boolForKey("enabled_preference")
## Glance
### Glance Essentials
glance是对用户如何浏览app重要信息的一个补充方式.并不是所有的app都需要glance.glance提供了一个即使的重要信息.比如对于一个日历app.glance可能会显示关于用户下次会议的信息.而对于一个航班类app,glance可以提供一个登机口信息.下图是”Lister”的glance,这个界面显示了完成的条目数和剩余的条目数:
![A glance interface for the Lister sample app](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideglance_lister_2x.png?imageMogr2/thumbnail/!50p)
glance作为您Watch app的一部分被提交.glance的界面在Watch app的storyboard文件中,并且通过自定义的WKInterfaceController(在WatchKit extension中创建)界面进行管理.glance界面控制器的唯一工作就是设置glance的显示内容.其本身是不可交互的,点击glance会加载您的Watch app.
#### The Glance Life Cycle
除了glance的界面控制器会被提前初始化(为了更快的加载)外,和其他的界面控制器生命周期是一样的.在glance的初始化和显示的这段时间,在willActivate方法中检查要显示的信息是不是最新的.关于界面控制的生命周期请看这里: [Watch App Life Cycle](#anchor_WatchAppLifeCycle)
#### Glance Interface Guidelines
Xcode为glance提供了固定的布局,在您选择了一个适合您内容的布局以后,添加的内容的时候要遵循以下准则:
* glance要能快速的传递信息: 不要仅显示一堆的文字,使用合适的图表,颜色和动画来传递信息.
* 专注于更重要的信息: glance不能替代您的Watch app.glance是您Watch app的精简版.
* 不能包含可交互的控制: glance不能包含可交互的内容,比如:按钮,switch,sliders和menu.
* 不能使用tables和maps.
* 显示的信息要及时: 使用所以可用的资源,包括时间和地理位置来为用户提供相关的信息.并且记得要对其进行更新.
* 所以的文字使用系统字体: 非要使用自定义字体的话,把它弄成image显示.
一个app只有一个glance界面控制器,这个控制器要能够显示您想要显示的信息.
### Managing Your Glance Interface
当在Xcode中添加一个Watch app targe的时候,您可以指定是否展示glance界面.当然以后也可以添加.glance的界面控制器和app的storyboard稍微不同,尤其是它有一个一目了然的入口执行它,并且有一个默认的布局.如下图所示:
![An interface controller with the glance entry point object](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideglance_interface_2x.png?imageMogr2/thumbnail/!50p)
使用基于WKInterfaceController的控制器在运行时设置glance的内容.
#### Adding a Glance Interface to Your App
当为app创建Watch app的时候,选择glance选项会创建glance所需要的所有文件.Xcode会为您提供一个glance storyboard和一个自定义的glance界面控制器.如果您在创建target的时候没有勾选该选项,也可以手动配置.
手动的创建glance界面:
1. 创建基于WKInterfaceController的控制器: 创建新的资源文件并添加到WatchKit extension target中.给您创建的控制器一个合适的名字,比如: GlanceInterfaceController.
2. 在storyboard文件中,拖一个glance界面控制器到storyboard中: glance如上图所示.
3. 在storyboard中选择glance界面控制器,打开Identity inspector.
4. 把glance界面控制器的class设置成您第一步创建的控制器.
Watch apps只能有一个glance,所有storyboard中有一个glance界面控制器就好.
#### Implementing and Updating a Glance Interface Controller
glance界面的实现相对简单,因为界面只能放图片和label.
* 使用init和awakeWithContext: 方法来初始化glance界面和位label和image设置初始值.
* 使用willActivate方法在界面还没有显示的时候来更新界面的值.
在界面加载出来以后,可以使用一个NSTimer来进行周期性的更新.您不需要更新WKInterfaceDate和WKInterfaceTimer对象,因为这些会自动更新.如果您使用本地的日期来更新您glance的内容,使用最近缓存的地理位置而不是去请求一个新的,当地理位置没有对app授权的时候会去请求授权. 与其把请求您的权限改为”总是”(这对只用于更新glance来说是不合适的),不如使用一个缓存的值代替.
>> 注意: 如果您的iOS app支持后台刷新,根据更新时间来为glance准备新的内容.在watchOS 2中,您可以使用Watch Connectivity来更新Apple Watch上的数据.
#### Customizing App Launch from Your Glance
当点击glance的时候,会加载与之关联的Watch app.一般会加载app的主界面,如果要自定义从glance启动显示的界面请实现以下步骤:
1. 在glance界面控制器:
* 在init和willActivate方法中配置一般情况.
* 调用updateUserActivity:userInfo:webpageURL: 方法,并且使用userInfo来接收glance的状态信息.在加载过程中,您的app能根据这个上下文信息来显示不同的界面控制器.
2. 在app的主界面控制器中:
* 实现handleUserActivity: 方法.使用userInfo字典来配置您的UI.
在加载的过程中,调用updateUserActivity:userInfo:webpageURL: 方法来告诉WatchKit调用主界面控制器的handleUserActivity: 方法,使用提供的上下文数据来配置您的UI显示.比如,如果您的app是一个page-based界面可能需要根据提供的数据来选择合适的page来显示.
## Notifications
### Notification Essentials
Apple Watch可以使用iOS中支持的可交互通知.如果您的iOS app支持通知,Apple Watch会在合适的时间显示这些通知.当iPhone收到远程或者本地通知的时候,iOS决定是否在iPhone或者Apple Watch上显示该通知.对于发送到Apple Watch上的通知,系统会巧妙的让用户知道有一个通知到啦.如果用户选择浏览通知,系统会先显示一个通知的概括,接下来显示详细信息.用户可以关闭通知,加载Watch app,或者点击通知上的按钮.app不需要做任何事就能显示通知.系统会为通知显示一个默认的界面,当然您也可以自定义通知的界面.
>> 注意: 只有在iOS支持通知的时候,Apple Watch才会显示本地或远程的通知.关于在iOS app上如何支持本地或远程通知请看这里: [Local and Remote Notification Programming Guide](https://developer.apple.com/library/watchos/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40008194).
#### The Short-Look Interface
用户最开始看到的通知是Short-Look界面,如下图所示.short-look界面不能滚动而且不能自定义.系统会使用一个固定的模板来显示app的名字,icon和存储在本地或远程通知中的内容.如果用户继续浏览这个通知,short-look会快速的转换成long-look通知.short-look中的title要简洁明了.对于本地通知通过指定UILocalNotification的alertTitle属性设置,对于远程通知,在字典中中添加title和alert key.更多信息请看这里: [](https://developer.apple.com/library/watchos/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40008194).
![A short-look interface](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuideshortlook_calendar_2x.png?imageMogr2/thumbnail/!50p)
#### The Long-Look Interface
Long-Look界面是一个可以显示通知的内容和一些按钮的可以滚动的界面.如果您没有自定义Long-Look界面,系统会默认提供一个包含app icon,title和alert的界面.
Long-Look分为三部分:
1. sash包含app icon和name.其颜色是可配置的(global tint color).
2. 内容区域包含通知的内容,关于如何自定义这块区域的内容,请看:[Managing a Custom Long Look Interface](#anchor_ManagingaCustomLongLookInterface).
3. 底部区域包含一个消失的按钮和在iOS app中注册的一些时间.
下图是一个包含一些按钮的通知界面:
![A short-look interface](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidelonglook_calendar_2x.png?imageMogr2/thumbnail/!50p)
点击app icon可以加载您的Watch app.前台的工作可以移交给您的Watch app和extension.后台的动作可以移交给您的iOS app.点击Dismiss按钮可以关闭弹框,点击其他区域不做任何动作.关于如何自定义long-look界面请看这里:Managing a Custom Long Look Interface.
#### Adding Action Buttons to Notifications
Apple Watch充分利用了iOS中可交互通知带来的便利.动作按钮可以让用户直接和通知交互.在iOS 8以后,app需要注册 notification-generated alerts类型来显示UIUserNotificationSettings对象.此外app还可以注册一系列用来显示按钮的自定义通知类.Apple Watch也可以使用这些类在long-look界面显示.
下面是在iOS中注册通知的设置和类.这个方法有iOS实现,并不是被WatchKit extension实现,并且在程序启动的适合由iOS app delegate调用.下面代码显示了创建和注册一个包含accept或者忽略一个会议邀请的”invitation”类.更多信息请看这里: [Local and Remote Notification Programming Guide](https://developer.apple.com/library/watchos/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40008194).
func registerSettingsAndCategories() {
var categories = NSMutableSet()
var acceptAction = UIMutableUserNotificationAction()
acceptAction.title = NSLocalizedString("Accept", comment: "Accept invitation")
acceptAction.identifier = "accept"
acceptAction.authenticationRequired = false
var declineAction = UIMutableUserNotificationAction()
declineAction.title = NSLocalizedString("Decline", comment: "Decline invitation")
declineAction.identifier = "decline"
declineAction.activationMode = UIUserNotificationActivationMode.Background
declineAction.authenticationRequired = false
var inviteCategory = UIMutableUserNotificationCategory()
inviteCategory.setActions([acceptAction, declineAction],
forContext: UIUserNotificationActionContext.Default)
inviteCategory.identifier = "invitation"
categories.addObject(inviteCategory)
// Configure other actions and categories and add them to the set...
var settings = UIUserNotificationSettings(forTypes: (.Alert | .Badge | .Sound),
categories: categories)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}
#### Responding to Taps in Action Buttons
当用户点击通知里的一个动作按钮的时候,系统会根据UIUserNotificationAction中注册的对了来决定执行怎样的动作.触发的任务可以在不需要用户授权的情况下在前台或后台运行.但是前台和后台任务会有以下不同:
* 前台运行的动作会加载Watch app并且把点击按钮的ID传给主界面控制器的handleActionWithIdentifier:forRemoteNotification: 或 handleActionWithIdentifier:forLocalNotification: 方法(在watchOS 2中,在extension delegate中实现这些方法).
* 后台动作按钮会在iOS app的后台运行.选择的按钮的信息会传递给app delegate的application:handleActionWithIdentifier:forRemoteNotification:completionHandler: or application:handleActionWithIdentifier:forLocalNotification:completionHandler: 方法.
对于前台运行的动作,并不是WKUserNotificationInterfaceController类管理这个进程.选择一个前台的动作来加载您的app来处理这个动作.在watchOS 2中,您的extension delegate要实现这些方法.
### Managing a Custom Long Look Interface
long-look包括两个单独的部分:静态部分和动态部分.今天界面是必须的,用来放通知的alert message和任意静态图片,其文字是在设计时配置的.动态界面是可选的,可以让您自定义通知的显示内容.当您添加了一个新的界面控制器到您的storyboard文件中时,Xcode会只创建一个静态的界面.您可以通过勾选Has Dynamic属性来添加一个动态界面.下图展示的是没有修改过的静态和动态界面.
![Static and dynamic notification interfaces](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidenotification_static_dynamic_2x.png?imageMogr2/thumbnail/!50p)
当一个正确类型的通知进来的时候,WatchKit会自动选择将您的通知显示在动态还是静态界面上.但是当动态界面不可用,电量不足或者您明确的告诉WatchKit不要显示动态界面的时候会自动显示静态界面.WatchKit会加载合适的storyboard资源文件和为显示做准备,流程如下图所示.动态通知界面的加载过程和您app的其他界面控制器类似,但是通知界面能处理通知的有效负载.
![Preparing the notification interface](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidenotification_process_2x.png?imageMogr2/thumbnail/!50p)
#### Adding a Custom Notification Interface to Your App
当为您的Watch app创建target的时候,如果您没有创建通知界面,以后可以手动配置:
1. 创建一个WKUserNotificationInterfaceController类.并把它加到您的WatchKit extension target中.并取一个区别于其他界面控制器的名字.
2. 勾选Has Dynamic Interface可以添加一个动态通知界面.
3. 把动态通知界面的class设置成您在第一部创建的控制器.
根据不同的category,app可能包含多个通知界面.在您的storyboard文件夹,使用通知category来指定每一个scene中的名字.运行时,WatchKit使用category的值来决定要家长哪一个scene.如果进来的通知没有category,WatchKit会家长category名为default的scene.
#### Configuring the Category of a Custom Interface
每一个通知界面都必须有一个category来告诉Apple Watch什么时候使用它.进来的通知可以在其内容里包含一个string的categorykey用来让Apple Watch知道要加载哪个scene.如果没有该category,会显示默认的.选择您的storyboard中的通知category对象,在Attributes inspector里指定通知的category型.如下图所示,可以设置category的名字,sash颜色和title的颜色.
![Configuring the notification type information](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidenotification_type_config_2x.png?imageMogr2/thumbnail/!50p)
对于远程通知,服务器要在字典里指定category的key.对于本地通知,您可以在UILocalNotification中指定category.
注意: category中也要包含通知action buttons的信息.更多内容请看这里: Adding Action Buttons to Notifications
#### Configuring a Static Notification Interface
使用静态通知来定义一个简单的通知界面.静态通知界面是用来在动态界面无法即使显示时显示的.这个界面也会在通知中心显示,创建静态通知界面的规则如下:
* 所以使用的image必须打包进bundle.
* 界面不能包含按钮,table,底图或者其他可交互元素.
* 界面的notificationAlertLabel的outlet必须是label.而且label的内容是唯一固定的.
下图是日历app中的静态和动态通知界面.通知的箭头指向了一个包含一个icon和两个label的静态scene.label的与notificationAlertLabel的outlet有关,因此能在运行时收到通知的alert message.
![Static and dynamic scenes for a single notification type](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidenotification_design_2x.png?imageMogr2/thumbnail/!50p)
#### Configuring the Dynamic Notification Interface
动态的通知界面可以为用户提供更丰富的交互体验.要实现动态通知界面,您必须要创建一个自定义的WKUserNotificationInterfaceController subclass.而怎么实现这个subclass要取决于您想要什么内容.
##### Designing Your Dynamic Interface
和其他界面控制器一样,在subclass中包含outlets来在运行时配置label,image或其他元素的内容.点击通知会加载app,所以通知界面不能包含可交互的元素.
* 使用label,image,group和separateor.
* 只有在必要的情况下才使用table和map.
* 不要包含按钮,switch或者其他可交互的按钮.
##### Configuring Your Dynamic Interface at Runtime
当一个正确类型的通知进来的时候,displays会显示您storyboard中相应的scene并且让WatchKit extension来初始化正确的WKUserNotificationInterfaceController subclass.下图展示了WatchKit展示您界面的步骤.在初始化通知界面控制器以后,WatchKit会通过didReceiveRemoteNotification:withCompletion: 或者 didReceiveLocalNotification:withCompletion: 方法来传递通知中的内容.您使用通知中的数据来显示您的通知界面并且提过完成的block来让WatchKit知道界面何时准备好.
![Static and dynamic scenes for a single notification type](http://7xpl1c.com1.z0.glb.clouddn.com/WatchKitProgrammingGuidenotification_event_cycle_2x.png?imageMogr2/thumbnail/!50p)
使用didReceiveRemoteNotification:withCompletion: 和 didReceiveLocalNotification:withCompletion: 方法来配置您的通知界面.要快速的配置您的界面,如果等待的时间太长,Apple Watch会自动显示静态界面来替代动态界面.
下面代码是在didReceiveRemoteNotification:withCompletion: 方法中显示的一个关于会议的远程通知.在这个方法中取得远程通知的数据并显示在界面上.为了简要,这个例子假定服务器包含所有key的正确数据,但是您自己实现的时候一点要坚持数据的合法性.在配置完label后,方法会调用completion block来让WatchKit知道自定义的界面已经准备好.
// Standard remote notification payload keys.
NSString* apsKeyString = @"aps";
NSString* titleKeyString = @"title";
// Payload keys that are specific to the app.
NSString* customDataKey = @"cal";
NSString* invitationDateKey = @"date";
NSString* invitationLocationKey = @"loc";
NSString* invitationNotesKey = @"note";
- (void)didReceiveRemoteNotification:(NSDictionary *)remoteNotification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler {
// Get the aps dictionary from the payload.
NSDictionary* apsDict = [remoteNotification objectForKey:apsKeyString];
// Retrieve the title of the invitation.
NSString* titleString = [apsDict objectForKey:titleKeyString];
[self.titleLabel setText:titleString];
// Extract the date and time from the custom section of the payload.
// The date/time information is stored as the number of seconds since 1970.
NSDictionary* customDataDict = [remoteNotification objectForKey:customDataKey];
NSNumber* dateValue = [customDataDict objectForKey:invitationDateKey];
NSDate* inviteDate = [NSDate dateWithTimeIntervalSince1970:[dateValue doubleValue]];
// Format the date and time strings.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
// Call a custom method to get the localized date format string for the user.
// The default date format string is "EEE, MMM d".
dateFormatter.dateFormat = [self dateFormatForCurrentUser];
NSString *formattedDateString = [dateFormatter stringFromDate:inviteDate];
// Call a custom method to get the localized time format string for the user.
// The default time format string is "h:mm a".
dateFormatter.dateFormat = [self timeFormatForCurrentUser];
NSString *formattedTimeString = [dateFormatter stringFromDate:inviteDate];
// Set the date and time in the corresponding labels.
[self.dateLabel setText:formattedDateString];
[self.timeLabel setText:formattedTimeString];
// Set the location of the meeting.
NSString* locationString = [customDataDict objectForKey:invitationLocationKey];
[self.locationLabel setText:locationString];
// Set the invitation‘s notes (if any).
NSString* notesString = [customDataDict objectForKey:invitationNotesKey];
[self.notesLabel setText:notesString];
// Tell WatchKit to display the custom interface.
completionHandler(WKUserNotificationInterfaceTypeCustom);
}
当调用completion handler block的时候,如果您想显示静态的界面,可以讲常量换成WKUserNotificationInterfaceTypeDefault.
>> 注意: 通知界面只支持系统的字体,如果您想显示您的字体,使用image代替.
#### Testing Your Custom Interface
当您在模拟器测试动态界面的时候,为允许的通知创建一个自定义的build scheme.Xcode指定了特殊的JSON文件来包含要测试显示在通知界面的数据.如果您的手机处于锁屏状态并且佩戴Apple Watch的时候,通知会自动显示在Apple Watch上.如果要在Apple Watch没有佩戴的情况下测试,请进行以下操作:
* 在您和Apple Watch配对的手机上的Watch app关闭手腕检测(Watch -> 通用 -> 手腕检测).
* 确保您的Watch没有在充电的界面.
* 手机要处于锁屏状态.
关于如何创建build schemes和配置通知的数据请看这里: [The Build, Run, and Debug Process](#anchor_TheBuildRunAndDebugProcess).
## Revision History
### Document Revision History
![](http://7xpl1c.com1.z0.glb.clouddn.com/AppleWatchHumanInterfaceGuidelinesC3DA6431-982D-4C2C-9A75-75E71081CE91.jpg)