之前,我利用业余时间和两个同事一起为我们的GlowApp做了一个AppleWatch应用。写这篇文章是为了介绍一下AppleWatch的开发,同时也罗列一下开发过程中遇到的一些坑。虽然WatchOS2已经发布并且我们在WatchKit中进行开发,但很多东西也适用于WatchOS2。希望这篇文章对大家有所帮助。IntroductionDesignWatchKitAppArchitectureDataCommunicationProvisioningProfilesandEntitlementsTipsDesign从本质上讲,您可以将AppleWatch视为iPhone的扩展屏幕。您无需掏出手机。只需轻轻抬起手腕,即可获取信息或进行一些简单的操作。其实大家在开发WatchApp的时候,会发现Watch模拟器是作为iPhone模拟器的外接显示器来实现的。然而,AppleWatch展现了一种全新的人机交互方式,iOSApp的设计交互准则并不适用于Watch。因此,在设计和开发一个WatchApp之前,有必要了解它的交互和基本的UI元素。先说交互。除了熟悉的手势交互,AppleWatch还提供了3种新的交互方式:ForceTouchAppleWatch的显示屏在感应用户的点击的同时也感应压力。通过“重新按下”可以显示最多包含4个操作的上下文菜单。数字表冠与传统手表一样,表冠是最常用的交互方式。但在AppleWatch上,表冠不用于调整时间和日期,也不用于给手表上弦。通过转动数码表冠,您可以精确地缩放、滚动或选择数据,而不会挡住您的视线。它还具有作为按钮返回的功能。按一下返回主界面,按两下返回时钟界面。听起来很美,但是皇冠的API还没有开放,滚动是系统自动帮你完成的:[SideButton是皇冠下面的一个长按钮。按下它会带你到朋友屏幕。在这里,您可以与您选择的12个联系人通话、发短信或进行交流,例如点击他们、画涂鸦或发送心跳。嗯,这个还没有开放相关的API,考虑到它的联系功能,估计以后不会开放了。AppleWatch人机交互指南WatchkitAppArchitecture当你添加一个WatchkitApptarget时,你会发现Xcode实际上添加了2个新的可执行文件,它们与你的iOSApp一起打包编译。它们之间的依赖关系如下图所示。WatchApp依赖WatchkitExtension,WatchkitExtension依赖iOSApp。从上下两张图可以看出,WatchApp中只有Storyboard和ImageAssets。没错,在WatchOS1中,WatchApp只包含一些静态的东西,而Extension才是真正执行代码的地方。Extension负责从iOSApp获取数据并控制在WatchApp界面上显示的内容。WatchApp的运行也是由iOSApp的Extension发起的。WatchApp不直接与iOSApp通信。WatchApp的每个页面都需要有一个对应的WKInterfaceController子类。Extension文件夹的InterfaceController和GlanceInterfaceController如上图。除了init之外,WKInterfaceController还有3个生命周期相关的方法://在init之后调用,可以在这个方法中配置界面上的元素-(void)awakeWithContext:(id)context;//当this在页面即将显示给用户时调用-(void)willActivate;//页面不再显示时调用-(void)didDeactivate;数据通信前面提到,WatchApp本身只包含一些静态内容,并没有保存数据,也无法发送网络请求。它只能通过Extension与iOSApp进行交互。因此WatchApp与iOSApp之间的数据传输是关键,也是大部分WatchApp的主要开发工作。主要有5种数据传输方式。首先是使用WKInterfaceController提供的openParentApplication:reply,然后在iOS端实现application:handleWatchKitExtensionRequest:reply来处理WatchExtension发送的请求。***Wormhole是一个第三方库,通过Dawrin通知中心发送消息和携带数据。中间三者都是通过AppGroup在一个独立的共享沙箱中传递数据。WKInterfaceControlleropenParentApplication:replyNSUserDefaultsCoreDataNSFileManagerDawrin通知中心-MMWormholeWKInterfaceControlleropenParentApplication:reply这种方式非常直观,也是几种数据传输方式中最实时可靠的。可以使用Enum来定义几种请求类型,然后在发送请求的时候将请求类型一起传递,这样iOSApp在收到请求的时候就知道该做什么了。iOSApp使用reply回调来发回请求结果。这样即使在后台也可以调用iOSApp。但是iOSApp无法主动唤醒WatchExtension。NSDictionary*request=@{kRequestType:@(requestType)};[InterfaceControlleropenParentApplication:requestreply:^(NSDictionary*replyInfo,NSError*error){}];-(void)application:(UIApplication*)applicationhandleWatchKitExtensionRequest:(NSDictionary*)userInforply:(void(^)(NSDictionary*))reply{RequestTyperequestType=userInfo[kRequestType];if(requestType==RequestTypeRefreshWatchData){//}}中间三个方法很相似,都是将数据存储在一个独立的sharedsandbox其中,区别在于它们的存储方式。如果iOSApp或WatchApp需要数据,请到沙箱中查找。就像一个秘密邮箱,只有他们两个人知道它在哪里。所以这也是一种异步的传递方式,双方不直接打交道。请参阅下面的代码了解如何使用它。NSUserDefaults是最容易使用的NSUserDefaults,但是有数据大小限制。NSString*appGroupName=@"group.com.yourcompnay.shared";NSUserDefaults*defaults=[[NSUserDefaultsalloc]initWithSuiteName:appGroupName];[defaultssetObject:user.nameforKey:@"userName"];CoreData如果你的iOSApp已经把CoreData放在一个共享的沙箱中,那么可以考虑这个方法。NSString*appGroupName=@"group.com.yourcompnay.shared";NSURL*storeURL=[[NSFileManagerdefaultManager]containerURLForSecurityApplicationGroupIdentifier:appGroupName];storeURL=[storeURRLLByAppendingPathComponent:@"glow.sqlite"];[self.persistentStoreCoordinatoraddPersistentStoreType:NSURLconfigstoreURLoptions:nilerror:&error]NSFileManager&&NSFileCoordinator读写文件肯定涉及到多线程的问题,不过不用担心,用NSFileCoordinator就可以了。-coordinateReadingItemAtURL:options:error:byAccessor:-coordinateWritingItemAtURL:options:error:byAccessor:[coordinatorcoordinateWritingItemAtURL:fileURLoptions:nilerror:nilbyAccessor:^(NSURL*writingURL){[dataToSavewriteToURL:newURLAtomically:true];}];实现NSFilePresenter协议监听文件变化,无需自己实现刷新机制,免费获得实时更新。-presentedItemDidChangeDawrinnotification-MMWormhole***一个用起来也很方便。WatchExtension和iOSApp一侧发送消息,另一侧监听消息。而且Wormhole还有一个很大的好处就是Wormhole会保存最后一次传输的数据,这样当WatchApp唤醒的时候,可以优先使用Wormhole中的数据,当最新的数据从iOSApp传输过来时会更新界面。//initself.wormhole=[[MMWormholealloc]initWithApplicationGroupIdentifier:kApplicationGroupIdentifieroptionalDirectory:kWormholeDirectory];//iOSappNSDictionary*message=[selfmakeWatchData];[self.wormholepassMessageObject:messageidentifier:kWormholeWatchData];//WatchKitExtensionNSDictionary*message=[self.wormholemessageWithIdentifier:Datak]或;//dosomethingwithmessage[self.wormholelistenForMessageWithIdentifier:kWormholeWatchDatalistener:^(idmessageObject){NSLog(@"Receiveddatafromwormhole.");}];这也是我最初开发它的方式。但是在使用过程中,我发现如果iOSApp处于后台模式,是无法实时接收到来自WatchKitExtension的消息的。所以***,我们选择混合使用openParentApplication:reply和Wormhole。当WatchApp唤醒时,利用Wormhole中的数据保证WatchApp的响应速度,通过openParentApplication:reply向iOS请求最新更新。在ProvisioningProfiles和Entitlements开发之初,最头疼的可能就是CodeSigning、Provisioning和entitlements。每个目标都必须有自己的AppID。所以我们总共需要三个:yourAppIDyourAppID.watchkitextensionyourAppID.watchkitapp您还需要为每个AppID创建一个关联的ProvisioningProfile。如果你使用Xcode自动创建ProvisioningProfile,它只会为你创建前两个,你需要去开发者中心手动创建。此外,您还需要确保您的三个权利是正确的。VersionNumber、BuildNumber和AppGroups(如果使用)必须相同,否则会编译失败。TipsDebug有时候,你会需要同时调试iOSApp和WatchApp。但是Xcode只允许你指定一个target来运行,你既可以调试iOSApp的代码,也可以调试WatchApp的代码。但是你可以通过Xcode的AttachtoProcess同时调试。具体步骤如下:RunWatchKitApp在Simulator中打开你的iOSApp在XcodeDebug->AttachtoProcess的菜单栏上,选择你的iOSApp,同时调试iOS和WatchKitApp。应用程序图标和iTunesConnect如果您在将应用程序上传到iTunesConnect时遇到无效二进制错误。很可能是因为您的WatchApp图标有透明层或alpha通道。比较方便的解决办法是用Preview打开图片,选择export(导出),然后取消勾选最下面的Alpha选项,OK。结尾
