[关闭]
@pockry 2017-08-16T18:27:09.000000Z 字数 7738 阅读 1571

Apple Watch开发

移动


1、AppleWatch界面及功能简介

2、手表主应用界面开发

2.1、表盘分辨率

都是2x屏

38mm款: 272px * 340px (136pt * 170pt)

42mm款: 312px * 390px (156pt * 195pt)

2.2、工程目录结构

新建工程,选项以及工程结构如下图说明:

2.3、UI布局介绍

手表UI界面开发只采用StoryBoard方式,所有界面都必须预先在StoryBoard布局好,并设置好属性,程序运行起来之后不能再动态添加界面,只能改变文字内容、图片内容等控件的属性。

UI布局不使用frame坐标系统,也不使用AutoLayout系统,默认采用从左到右,从上到下的线性布局方式;如果布局内容超过1屏,在SB中会自动扩展表盘的高度,运行时手指向上滑会滚动显示出底下内容,类似ScrollView的效果。

如果运行时调用 setHidden 方法隐藏了某个控件,则后续控件会自动补上,填充隐藏控件所在的位置。

Tip1:Group可以设置其内控件为水平或垂直布局,将控件加入Group,再将界面分割成不同的Group,可以方便布局,且Group支持设置背景图。

Tip2:Global Tint设置app的全局主色可以选择StoryBoard,选中其中任意一个Interface Controller在File inspector中对Global Tint属性进行修改。

这个颜色会应用到下面的元素:

status bar中的Title

short-look通知中的应用名称

Tip3: 38mm和42mm大小表盘适配,控件属性面板中点击+号,即可分别设置。

Tip4:图片放在2个Target中的区别

放在WatchKit App中的图片可以直接在SB的属性面板中索引到并使用,同时也可以用直接使用控件的setImageNamed方法在代码中进行设置[self.image setImageNamed:@"img1"];

放在Watchkit Extension中的图片只能通过setImage的方法在代码中进行设置[self.image setImage:[UIImage imageNamed:@"img2"]];

2.4、界面跳转Push Present

与UIKit的用法和表现形式基本相同

  1. [self pushControllerWithName:@"Test1Controller" context:@{@"isModal":@(NO)}];
  2. [self presentControllerWithName:@"Test2Controller" context:@{@"isModal":@(YES)}];
  3. [self presentControllerWithNames:@[@"Test1Controller", @"Test2Controller"] contexts:@[@{@"isModal":@(YES)}, @{@"isModal":@(YES)}]];

然后被弹出的InterfaceController在awakeWithContext方法中取得参数

  1. - (void)awakeWithContext:(id)context
  2. {
  3. [super awakeWithContext:context];
  4. if (![context[@"isModal"] boolValue])
  5. {
  6. [self setTitle:@"我是Test1"];
  7. }
  8. }

启动默认成为可水平滚动的多页应用,如下代码所示:

  1. [WKInterfaceController reloadRootControllersWithNames:@[@"InterfaceController", @"Test1Controller", @"Test2Controller"] contexts:nil];

2.5、Force Touch弹出菜单

调用用WKInterfaceController的如下系列方法进行添加,每个item可独立设置响应方法。

  1. - (void)addMenuItemWithImage:(UIImage *)image title:(NSString *)title action:(SEL)action;
  2. - (void)addMenuItemWithImageNamed:(NSString *)imageName title:(NSString *)title action:(SEL)action;
  3. - (void)addMenuItemWithItemIcon:(WKMenuItemIcon)itemIcon title:(NSString *)title action:(SEL)action;

如果此菜单按钮使用自定义的图片,建议图片大小38mm手表70x70,42mm手表80x80,单位:px

模拟器中调试,CMD+Shift+2鼠标左键变成Force Touch功能可呼出此菜单,CMD+Shift+1鼠标左键恢复普通单击功能

3、表盘小组件界面开发

Complications 是 watchOS 2 新加入的特性,它是表盘上的小界面元素,用于自定义表盘,可以支持直接从表盘唤起自己的App。

苹果官方提供的表盘有很多种,但是表盘小组件归只归纳为以下几种类型,许多表盘使用相同类型的小组件。

  1. typedef NS_ENUM(NSInteger, CLKComplicationFamily) {
  2. CLKComplicationFamilyModularSmall = 0,
  3. CLKComplicationFamilyModularLarge = 1,
  4. CLKComplicationFamilyUtilitarianSmall = 2,
  5. CLKComplicationFamilyUtilitarianSmallFlat = 6,
  6. CLKComplicationFamilyUtilitarianLarge = 3,
  7. CLKComplicationFamilyCircularSmall = 4,
  8. CLKComplicationFamilyExtraLarge = 7,
  9. };

对于开发人员来说,就是实现系统提供的 ComplicationController.m 中的几个代理方法,根据不同的小组件类型,返回对应的显示模板即可。其他具体可参考这篇文章:

http://www.jianshu.com/p/56aa823dd903

这里对代理方法简要说明如下:

  1. #pragma mark - Timeline Configuration
  2. //支持时间旅行的方向
  3. - (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler
  4. {
  5. handler(CLKComplicationTimeTravelDirectionNone);
  6. }
  7. //时间旅行起点时间
  8. - (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
  9. {
  10. handler(nil);
  11. }
  12. //时间旅行终点时间
  13. - (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
  14. {
  15. handler(nil);
  16. }
  17. #pragma mark - Timeline Population
  18. //获取当前时间的各个表盘组件信息
  19. - (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
  20. {
  21. CLKComplicationTemplate *curTemplate = nil;
  22. NSString *curLuck;
  23. NSString *nextLuck;
  24. if (complication.family == CLKComplicationFamilyUtilitarianLarge)
  25. {
  26. CLKComplicationTemplateUtilitarianLargeFlat *template = [CLKComplicationTemplateUtilitarianLargeFlat new];
  27. template.imageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
  28. curLuck = [[Common sharedInstance] getCurrentTimeLuck];
  29. template.textProvider = [CLKSimpleTextProvider textProviderWithText:[NSString stringWithFormat:@"当前时辰:%@", curLuck]];
  30. curTemplate = template;
  31. }
  32. else if (complication.family == CLKComplicationFamilyModularLarge)
  33. {
  34. }
  35. if (curTemplate)
  36. {
  37. CLKComplicationTimelineEntry *entry = [CLKComplicationTimelineEntry entryWithDate:[NSDate date] complicationTemplate:curTemplate];
  38. handler(entry);
  39. }
  40. else
  41. {
  42. handler(nil);
  43. }
  44. }
  45. //时间旅行,未来时间的组件信息,limit=100,提供的话不要超过这个数量
  46. - (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
  47. {
  48. handler(nil);
  49. }
  50. //时间旅行,过去时间的组件信息,limit=100,提供的话不要超过这个数量
  51. - (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
  52. {
  53. handler(nil);
  54. }
  55. #pragma mark - Placeholder Templates
  56. //提供设置界面使用的默认模板,可以是假数据,示意即可
  57. - (void)getLocalizableSampleTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
  58. {
  59. if (complication.family == CLKComplicationFamilyModularLarge)
  60. {
  61. CLKComplicationTemplateModularLargeStandardBody *tmp = [CLKComplicationTemplateModularLargeStandardBody new];
  62. tmp.headerImageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
  63. tmp.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:@"龙易吉凶时"];
  64. tmp.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:@"当前时辰:吉"];
  65. tmp.body2TextProvider = [CLKSimpleTextProvider textProviderWithText:@"下一时辰:凶"];
  66. handler(tmp);
  67. }
  68. else if (complication.family == CLKComplicationFamilyUtilitarianLarge)
  69. {
  70. CLKComplicationTemplateUtilitarianLargeFlat *tmp = [CLKComplicationTemplateUtilitarianLargeFlat new];
  71. tmp.imageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
  72. tmp.textProvider = [CLKSimpleTextProvider textProviderWithText:@"当前时辰:吉"];
  73. handler(tmp);
  74. }
  75. else
  76. {
  77. handler(nil);
  78. }
  79. }
  80. //刷新表盘组件数据
  81. - (void)updateComplication
  82. {
  83. CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
  84. for (CLKComplication *complication in server.activeComplications)
  85. {
  86. [server reloadTimelineForComplication:complication];
  87. }
  88. }

4、通知界面开发

只要手机App支持AppleWatch,那么对应的通知就会被同步到手表上。

对于开发者来说,只要实现系统提供的 NotificationController.m 中的通知回调方法即可。其他具体可参考这篇文章, 这里对回调方法简要说明如下:

  1. - (void)didReceiveNotification:(UNNotification *)notification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler
  2. {
  3. //取出自定义的通知的内容并展示到界面的各个组件上
  4. UNNotificationContent *content = notification.request.content;
  5. NSDictionary *customDic = [content.userInfo objectForKey:@"customKey"];
  6. [self.lbl1 setText:customDic[@"key1"]];
  7. [self.lbl2 setText:customDic[@"key2"]];
  8. completionHandler(WKUserNotificationInterfaceTypeCustom);
  9. }

5、手表手机间通信

当前AppleWatch S2支持WiFi网络,不支持蜂窝移动网络(据说下一代S3支持),手表端的应用可以直接调用NSURLSession接口从网络获取数据。

在无WiFi的情况下,手表可以通过蓝牙与手机通信,向手机端请求数据。

手表端简单的数据存储可以通过NSUserDefaults进行存储。(手表端可以使用sqlite数据库进行存储么?留待大家去验证:)

其他具体可参考这篇文章, 这里对手表手机蓝牙通信简要说明如下:

  1. //手机端激活传输session
  2. if ([WCSession class] && [WCSession isSupported])
  3. {
  4. self.session = [WCSession defaultSession];
  5. self.session.delegate = self;
  6. [self.session activateSession];
  7. }
  8. //手机端发送数据
  9. if (self.session.paired && self.session.watchAppInstalled)
  10. {
  11. /*
  12. 手表必须配对已连接,并且手表app跑在前台才是 Reachable 的。而手表向手机发数据不用判断这个,
  13. 因为手表可以从后台唤醒手机app来发,反之不会。
  14. */
  15. if (self.session.isReachable)
  16. {
  17. [self.session sendMessage:message replyHandler:nil errorHandler:nil];
  18. }
  19. }
  20. //手表端激活传输session
  21. self.session = [WCSession defaultSession];
  22. self.session.delegate = self;
  23. [self.session activateSession];
  24. //手表端发送数据
  25. [self.session sendMessage:message replyHandler:nil errorHandler:nil];
  26. //两端接收数据都通过 WCSessionDelegate 代理进行
  27. ......

6、AppStore上架踩坑

1、手表端应用的的AppIcon不要带透明背景,黑色背景即可,否则使用Application Loader上传ipa包成功,但是在ITC的构建版本那边找不到应用,苹果也不提示原因。

合格的手表AppIcon图示:

不合格的手表AppIcon图示:

2、如果WatchApp支持表盘小组件功能,那么上架传给ITC的应用功能截图也不要带表盘组件功能的截图,只要带应用内的截图即可,否则会被苹果认为你开发的也是手表看时间的应用,体验不如苹果自家的看时间的应用,会被拒。

以下两张应用内截图可以上传ITC


以下带表盘功能小组件的截图就不能上传到ITC

7、参考链接

watchOS Human Interface Guidelines

App Programming Guide for watchOS

Watch开发:Complications教程

watchOS中通知的应用

watchOS 2-WatchConnectivity

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注