这篇文章旨在总结一下最近Watch开发下碰到的问题和细节
1、关于Watch的真机调试问题
一般的情况下,你为IOS主应用创建了一个extention,比如说Today Extension 。Xcode都会自动帮你生成该extention的appid,然后生成对应的Provisioning Profile。然后在Targets-->Build Settings-->Code Signing 里选择对应的Provisioning Profile,勾选对应的keychain就可以run了。
如果你为你的项目创建了AppleWatch,那么就会在原项目中新增了 watchkit extension 和 watchkit app 两个target。那么你需要在苹果开发者中心为watchkit extension 和 watchkit app 分别创建正确的bundle identifier。而为watchkit这两个扩展新建的bundle identifier都是从主应用的bundle identifier进行扩展的,这样的命名规则必须遵守。假如你的主应用bundle identifier是com.jaybin.myapp。那么watchkit extension的bundle identifier就必须为com.jaybin.myapp.watchkitextension。watchkitapp的bundle identifier就必须为com.jaybin.myapp.watchkitapp。因为在 iOS 系统上,app 本体是核心。所有的运行实体都是依托在本体上的。
到这里你就可以为watchkit这两个target对应的appid创建对应的Provisioning Profile。加上主应用的话,一共需要6个ProvisioningProfiles。3个 development Profiles,3 个 archiving/store Profiles。
最后一步就是将证书部署到Xcode项目中运行或者打包。首先按照下面操作 Xcode >Preferences > Accounts > YOUR_ACCOUNT > View Details 。
可以看到该账号下的所有证书,随便选中其中的一个Profiles,选择在Finder打开,然后将该文件夹下的所有证书删除,这样可以清空原先的无效Profiles。然后点击视图左下角的刷新按钮,刷新证书,Xcode会将最新的证书download下来,包括刚才创建的新Profiles。最后确保项目中的target,主target和watchkit target都选择关联上了正确的team。然后选择对应正确的Provisioning Profiles(一般情况下Provisioning Profiles选择automaic就可以run了)。不过奇怪的是,我只对watchkit extention创建了对应的appid和profile 就可以run了。估计是Xcode 自动帮我们把Watchkit app 的Provisioning Profile设置好了。
别忘了在运行之前先clean项目,因为Xcode有时候会缓存旧的profile和设置。
最后提醒一下,AppleWatch的开发/真机调试和iphone是一样,也需要在开发者中心中的账号下添加开发测试设备。否则就会 “提示错误:应用验证失败”
查看AppleWatch的UDID和查看iphone设备的一样,如果watch和iphone已经配对成功的话,在查看iphone的UDID页面下方就能查看到watch的UDID。在Xcode下的 Window > Deivces 就可以查看。
2、WatchKit app的特殊导航类型
按照导航形式和风格分为两种
1)分层风格(push/pop、Present和iphone上的导航风格一样)
2)分页风格(presentControllerWithNames、becomeCurrentPage)
具体的视图切换、页面的跳转API
- (void)pushControllerWithName:(NSString *)name context:(id)context
- (void)popController;
- (void)popToRootController;
- (void)presentControllerWithName:(NSString *)name context:(id)context;
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts;
- (void)dismissController;
- (void)becomeCurrentPage;
需要注意的是使用以上这一组API进行视图切换与跳转时,Push和Present方法第一个参数是对应的在Storyboard中为WKInterfaceController设置的identifier字符串。
当然对于WatchKitApp,是可以在Storyboard里建立多个InterfaceController并像在iOS应用一样直观的画出视图转换连接的,可以通过视图控制器代码实现相应视图切换与跳转。直接在Storyboard中设置Triggered Segues。使用Segues时,Selection同样支持Push和Model两种跳转方式。关于分页导航,我们也可以在Storyboard里按住control从视图A拖到视图B选择next page可以建立此关系。
关于WatchKitapp的导航最值得关注的就是分页导航(Page-based)与分层导航(Stack)模式的混用。因为在视图的切换与跳转中,为了方便与用户的交互我们常常需要多种导航方式混用。但是分页导航与分层导航是互斥的,因此必须使用模态方式(Present)进行切换。
比如主控制器为分页视图时(页面导航用Page-based),要正确弹出一个分级视图栈可以用presentControllerWithName:conext:方法,而主控制器为分级视图时,要正确弹出单页视图,也用presentControllerWithName:conext:。
如果想弹出多个页面组成的分页视图,我们可以present一组Controller, 这一组Controller将以page control的形式展示,这时需要改为用presentControllerWithNames:contexts:。不过这样有一个问题,如果我在一个分级视图中present一组Controller,即当前展现的视图以page control的形式展示。对于这组分页视图而言,所有视图的左上角都会默认自带一个返回的“cancel”按钮,点击后当前的这组视图将Dismiss掉,返回上一层视图。但是如果我在当前这组分页视图中再present出一个模态视图,然后返回,会发现原先这组分页视图左上角上面的返回“cancel”按钮消失了,也就无法返回到上一层了。貌似在这些视图中调用dismissController也不起作用。所以,对于presentControllerWithNames:contexts:,如果present出一组Controller后,在这组Controller视图中就不要再present出模态视图,否则这组分页视图将无法返回到上一级页面。
不过关于分页视图,还有一个很好用的方法,reloadRootControllersWithNames:contexts: 当app启动时,如果想以分页界面的形式展现视图控制器,即用户可以左右滑动切换视图。那么我们就可以在初始界面控制器的init方法中调用reloadRootControllersWithNames:contexts:方法。这种情况,我们通常会在storyboard文件中配置一组初始的页面集合。当app启动时,WatchKit会实例化和初始化第一个展示的页面,即初始界面控制器(MainInterfaceController),然后是分页界面中的其他界面控制器。也就是说当系统加载WatchKit app界面时,它将一下子实例化和初始化组成界面的所有界面控制器。当用户从一个界面控制器切换至下一个时,它将调用当前界面控制器的didDeactivate方法,以及即将展示的界面控制器的willActivate方法。willActivate方法可确保界面中的信息是最新的。当然我们也可以在app运行时在willActivate方法中调用该方法。
3、关于Watch与Iphone主应用的数据通信
如果在watchkit extension中进行数据的申请,比如网络调用。这样就会造成无法复用项目中已有的代码,会写出很多重复的代码,性能上优化的空间不大。 所以如果Apple Watch应用需要执行长时间运行在后台的任务,比如网络调用,这时应该让iPhone端的主应用来做这个工作。
1)使用WKInterfaceController中的openParentApplication:reply:方法在后台唤醒iPhone端主应用,由主应用去进行网络数据的处理,处理完成后再返回WatchKit扩展所需的数据。
watchkit extension发送请求唤醒主应用:
+(BOOL)openParentApplication:(NSDictionary *)userInfo reply:
(void(^)(NSDictionary*replyInfo, NSError *error)) reply;
watchkit extension中,具体Demo如下:
//watchkit extension 向主应用发送请求,唤醒主应用
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:@"say something" forKey:@"openType"];
[WKInterfaceController openParentApplication:userInfo reply:^(NSDictionary *replyInfo, NSError *error){
//主应用处理完后的回调,返回extension所需的数据
NSString *words = [replyInfo objectForKey:@"words"];
NSLog(@"say: %@",words);
}];
2)主应用处理WatchKit请求的方法,UIApplicationDelegate方法:
-(void)application:(UIApplication*)application
handleWatchKitExtensionRequest:(NSDictionary*)userInfo reply:(void (^)(NSDictionary *))reply;
需要注意的是主应用每次执行UIApplicationDelegate方法,处理完成WatchKit的请求后都要回调reply(replyInfo);否则这个方法会响应失败。
主应用中,具体Demo如下:
//主应用处理来自watchkit extension的请求
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply{
if(userInfo){
NSString *openValue = [userInfo objectForKey:@"openType"];
if(openValue && [@"say something" isEqualToString:openValue]){
NSMutableDictionary *replyInfo = [NSMutableDictionary dictionary];
[replyInfo setObject:@"Hello World!" forKey:@"words"];
//主应用处理完成后,回调来自watchkit extension的 reply(replyInfo),否则方法响应失败
reply(replyInfo);
}
}
}
say: Hello World!
即 watchkit extension 通过请求获得主应用传送过来的数据了。
另外一种通信方法就是我们熟知的IOS应用中,主应用与extension通过App Group来进行数据共享通信。