AppleWatch开发分享

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的用法和表现形式基本相同

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

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

- (void)awakeWithContext:(id)context 
{
    [super awakeWithContext:context];
    
    if (![context[@"isModal"] boolValue]) 
    {
        [self setTitle:@"我是Test1"];
    }
}

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

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

2.5、Force Touch弹出菜单

调用用WKInterfaceController的如下系列方法进行添加,每个item可独立设置响应方法。当然也可以通过SB中拖入Menu和Menu Item控件进行添加和属性设置,在此不详述。

- (void)addMenuItemWithImage:(UIImage *)image title:(NSString *)title action:(SEL)action;
- (void)addMenuItemWithImageNamed:(NSString *)imageName title:(NSString *)title action:(SEL)action;
- (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。

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

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

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

#pragma mark - Timeline Configuration
//支持时间旅行的方向
- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler 
{
    handler(CLKComplicationTimeTravelDirectionNone);
}

//时间旅行起点时间
- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler 
{
    handler(nil);
}

//时间旅行终点时间
- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler 
{
    handler(nil);
}

#pragma mark - Timeline Population
//获取当前时间的各个表盘组件信息
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
{
    CLKComplicationTemplate *curTemplate = nil;
    NSString *curLuck;
    NSString *nextLuck;
    
    if (complication.family == CLKComplicationFamilyUtilitarianLarge)
    {
        CLKComplicationTemplateUtilitarianLargeFlat *template = [CLKComplicationTemplateUtilitarianLargeFlat new];
        template.imageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
        curLuck = [[Common sharedInstance] getCurrentTimeLuck];
        template.textProvider = [CLKSimpleTextProvider textProviderWithText:[NSString stringWithFormat:@"当前时辰:%@", curLuck]];
        curTemplate = template;
    }
    else if (complication.family == CLKComplicationFamilyModularLarge)
    {
    }
    
    if (curTemplate)
    {
        CLKComplicationTimelineEntry *entry = [CLKComplicationTimelineEntry entryWithDate:[NSDate date] complicationTemplate:curTemplate];
        handler(entry);
    }
    else
    {
        handler(nil);
    }
}

//时间旅行,未来时间的组件信息,limit=100,提供的话不要超过这个数量
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler 
{
    handler(nil);
}

//时间旅行,过去时间的组件信息,limit=100,提供的话不要超过这个数量
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler 
{
    handler(nil);
}

#pragma mark - Placeholder Templates
//提供设置界面使用的默认模板,可以是假数据,示意即可
- (void)getLocalizableSampleTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
{
    if (complication.family == CLKComplicationFamilyModularLarge)
    {
        CLKComplicationTemplateModularLargeStandardBody *tmp = [CLKComplicationTemplateModularLargeStandardBody new];
        tmp.headerImageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
        tmp.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:@"龙易吉凶时"];
        tmp.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:@"当前时辰:吉"];
        tmp.body2TextProvider = [CLKSimpleTextProvider textProviderWithText:@"下一时辰:凶"];
        handler(tmp);
    }
    else if (complication.family == CLKComplicationFamilyUtilitarianLarge)
    {
        CLKComplicationTemplateUtilitarianLargeFlat *tmp = [CLKComplicationTemplateUtilitarianLargeFlat new];
        tmp.imageProvider = [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"Modular"]];
        tmp.textProvider = [CLKSimpleTextProvider textProviderWithText:@"当前时辰:吉"];
        handler(tmp);
    }
    else
    {
        handler(nil);
    }
}

//刷新表盘组件数据
- (void)updateComplication
{
    CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
    for (CLKComplication *complication in server.activeComplications)
    {
        [server reloadTimelineForComplication:complication];
    }
}

4、通知界面开发

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

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

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

5、手表手机间通信

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

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

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

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

//手机端激活传输session
if ([WCSession class] && [WCSession isSupported])
{
    self.session = [WCSession defaultSession];
    self.session.delegate = self;
    [self.session activateSession];
}

//手机端发送数据
if (self.session.paired && self.session.watchAppInstalled)
{
    /*
    手表必须配对已连接,并且手表app跑在前台才是 Reachable 的。而手表向手机发数 据不用判断这个,因为手表可以从后台唤醒手机app来发,反之不会。
    */
    if (self.session.isReachable)
    {
        [self.session sendMessage:message replyHandler:nil errorHandler:nil];
    }
}

//手表端激活传输session
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];

//手表端发送数据
[self.session sendMessage:message replyHandler:nil errorHandler:nil];

//两端接收数据都通过 WCSessionDelegate 代理进行
......

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 目录: 前言 一、Apple Watch简介 二、Watch App在项目中的结构 三、 Apple Watch上...
    _小秋_阅读 7,054评论 3 13
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,041评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,876评论 0 2
  • 昨天考过了阿里规范,心里舒坦了好多,敲代码也犹如神助。早早完成工作回家喽
    常亚星阅读 3,034评论 0 1
  • 三军可夺气,将军可夺心。是故朝气锐,昼气惰,暮气归。善用兵者,避其锐气,击其惰归,此治气者也。以治待乱,以静待哗,...
    生姜牛奶泡腾片阅读 1,572评论 0 1