原文地址:http://www.cocoachina.com/ios/20150323/11396.html
本文由CocoaChina翻译组成员YueWang翻译自raywenderlich,敬请指正。
原文:WatchKit FAQ,作者:Soheil Azarpour
Ray友情提示:这篇关于WatchKit的文章是作为 Spring Swift Fling庆贺的一部分而发表。希望你们会喜欢它!
在写这篇文章时,开发者接触WatchKit差不多有3个月时间了。正因为开发者在慢慢地了解这个新的酷炫的技术,一大堆问题毫无疑问地随之而来。
在这篇WatchKit FAQ里,我们将会解答一系列我们在社区、Twitter、邮件以及Stack Overflow上问的比较频繁的问题。 我们也将会在新的问题不断涌现时,周期性地更新这个FAQ。
对于一些没有明确解决方案的问题,答案就综合了开发者的智慧、观点以及部分合理推测等。跟你一样,我们也是不断的探索中不断的学习WatchKit,而且这项技术也正在不断的开发当中,所以也在不断地变化。
关于WatchKit中喜欢的和不喜欢的地方,欢迎和我们分享你的观点和评论,然后提出更多的问题。我们将会根据你的回馈更新这个FAQ。
基础问题
什么是WatchKit,以及它如何工作的?
WatchKit是苹果公司为Apple Watch创建混合型应用程序的框架,且是和Xcode 6.2绑在一起的。
WatchKit工作原理是将你的APP拆分成两个不同的部分:
-
Apple Watch只包含storyboard和asset catalog 之类的界面资源,即便它可以处理用户输入,但它实际上根本不会执行任何代码。换句话说, Apple Watch更像是一个精简的客户端。
-
iPhone包含了所有的代码,而且把它作为一个扩展来执行,就跟Today或Action扩展一样。
一个很酷的事情是Apple Watch和iPhone的通信是自动的,而且这一切只发生在后台。
你按照之前的习惯工作,而WatchKit将代你处理所有的无线通信。就你写的代码而言,你的views和outlets是本地关联的,尽管它们可能是在完全分开的设备上。非常酷!
想学更多,查看我们的WatchKit: Initial Impressions文章。
可以用Swift 编写Apple Watch 应用吗?
是的,你可以用Objective-C或者Swift来创建Apple Watch应用,或者两者都用。苹果已经为WatchKit提供了两个模板工程:
-
WatchKit Catalog: Using WatchKit Interface Elements--是全部用Objective-C写的。
-
Lister: A Productivity App--两种语言编写:Objective-C和Swift.
除此之外,我们的 WatchKit Tutorial, WatchKit video tutorial series和 WatchKit by Tutorials书都是专门用Swift写的。
苹果也已经提供了用Objective-C和Swift写的 WatchKit Framework的文档。
我可以自定义的表盘吗?
不行。目前还不支持自定义表盘。
一个iPhone可以关联几个Apple Watch?
一个iPhone每次可关联一个Apple Watch--它们是一对一的关系。
我可以用我的iPad关联我的Apple Watch吗?
不可以。目前一个Apple Watch只能跟iPhone配对。
iPhone应用可以唤醒它的WatchKit扩展和watch应用吗?
不能。WatchKit 扩展只能让系统去启动关联iPhone应用,这一切都在后台运行。目前还不支持反向操作。
第三方应用可以从Watch应用打电话吗?
不可以。没有任何公有API可以让你直接从WatchKit extension打电话。因为它关联的iPhone应用也并不会被带到前台,系统会默默忽略所有来自关联iPhone应用程序的拨打电话或者openURL:请求。
可以从watch应用接入手表上的心跳传感器和其他传感器吗?
不可以。目前没有任何API可以接入到Apple Watch上的硬件传感器。
Short-Look和Long-Look,静态和动态通知之间有什么区别?
Short-Look通知:Short-Look 通知是由系统提供的。类似于你在iPhone上收到的通知banner,对于Short-Look通知,你没有任何控制权。Short-Look 通知可展示应用程序的icon、名称以及标题。当通知进来时,用户就会看见这个Short-Look通知。如果用户举起手腕,经过一个短暂停留,Short-Look通知就会转变到Long-Look通知。
Long-Look通知: Long-Look通知可以是静态或动态的。
-
Static通知: 静态通知包含一个利用通知payload自动填充了的单一标签。你可以在watch app的storyboard里面创建一个静态通知场景,但是除了改变sash和标题的颜色外,你不能真正地对其进行自定义。
-
Dynamic通知: 动态通知要求你子类化InterfaceController。它从storyboard实例化,而且你可以提供自己的定制界面。注意一点,并不能担保动态通知界面肯定会展现出来。例如,如果手表的电池量很低时,系统可能会决定展现静态通知界面以便节省电量,因为创建静态通知界面成本更低。
进阶问题
选择合适的scheme
我怎样能使用模拟器来测试glance或者通知?
每个glance或通知需要使用自己专用的build scheme才能在模拟器中运行。然后你简单地选择合适的scheme,然后构建并运行 。
可以叠放界面元素吗?
不可以。本来界面元素是不能叠放在一起的。不过我们有变通方法。比如,你可以使用WKInterfaceGroup,并将其背景图片设置为展示你想叠加的控件的背景。在这个分组里,你可以增加必要的标题、按钮以及其他等等。
可以定制界面元素的CALayer属性吗?
不可以。现在Apple Watch界面元素还没有可用的CALayer属性,因为它们不是继承自UIView或CALayer。
WatchKit中可用的类能进行子类化吗?
没有什么可以阻止你子类化WatchKit里的类,但是你可能会发现它不能使用。你可以子类化一些类,比如WKInterfaceController和WKUserNotificationInterfaceController InterfaceController,并在storyboard里面使用它们。
不过,watch app的storyboard不允许你改变任何界面元素的类。而且你已经不能动态地创建界面元素,然后像子视图一样插入或者删除它们。你只能隐藏或者显示已经展现在storyboard中的界面元素。
可以混用基于分页的和基于导航的界面控制器?
可以,但有一些限制。如果应用有一个hierarchical interface(分层界面),你可以展示模态地展示一个基于分页(page-based)的界面。如果任何页面都是hierarchical interface,那会展示界面的根界面控制器,并且你不能向其堆栈上推送任何东西。如果应用包含一个page-based界面,那你可以模态地展示一个hierarchical interface,但只展示根界面控制器,并且你也不能向其堆栈上推送任何东西。
Apple Watch上有 UIActivityIndicator或UIAlertController的等价对象吗?
没有,但是你可以通过模态地展示一个自定义WKInterfaceController来代替UIAlertController。
你可以通过增加一系列图片创建所需动画,从而替代一个活动指示器,或者仅仅展示一个包含合适文本的标签。举个例子,查看苹果的Lister example. 在Watch App的glance里,你会看到360张图片,可用以展示单一的圆圈动画。
我可以使用Core Graphics来动态地创建图片,并添加到watch app中吗? 它们可以缓存在Apple Watch上吗?
可以。但是任何使用Core Graphics的图片组合必须作为扩展的一部分在iPhone上进行。一旦你将Core Grahphics的绘画上下文传递给了一个UIImage的实例,你就可以在手表上用WKInterfaceDevice的addCachedImage(_:name:)方法来缓存它。
我可以在Apple Watch上使用定制视图吗? 我可以自定义公有API之外的界面元素吗?
不可以,你不可以使用定制视图。WatchKit仅仅支持一些原生的界面元素。除了他们的公有API之外,任何界面元素都不能被继承或者自定义。可用的界面元素有WKInterfaceLabel、WKInterfaceButton、WKInterfaceImage、WKInterfaceGroup、WKInterfaceSeparator、WKInterfaceTable、WKInterfaceSwitch、WKInterfaceMap、WKInterfaceSlider以及WKInterfaceTimer.
Apple Watch是怎么样跟你的iPhone交互的?
Apple Watch利用Bluetooth LE 和 Wi-Fi 技术来同它匹配的iPhone进行交互。交互和实现的本质方法对于使用者和开发者来说都是不透明的。
进入飞行模式后打开Wi-Fi 和 Bluetooth
飞行模式状态下可以使用Apple Watch吗?
是的。实际上,在打开飞行模式后你可以打开Bluetooth和Wi-Fi来实现交互,并继续使用你的Apple Watch。
当Apple Watch不能同它匹配的iPhone通信时,我的app将会怎样?
简单的说,你的APP不会运行,而且如果它正在运行的话也将会挂起。
在WatchKit扩展中,didDeactivate()会在当前的interface controller下被调用,你将有机会在这里做任何必要的清理工作。一个红色的iPhone图标会展示在手表上的状态栏以指示连接丢失,而且这个界面将会停留在屏幕上,但并不会交互。用户可以要么修复连接要么退出app。
Watch app怎么同它匹配的iPhone app 通信?
这里有许多的技术你可能会用到,一个流行的技术就是你的watch app在一个共享的容器里写或更新数据,并且它负责通知iPhone app。之后,iPhone app可以从这个共享的容器中拉取变化。
另一个技术是通过一个字典传送数据给iPhone app,但是这只能由watch app发起。在WatchKit extension中有一个单一的API;调用WKInterfaceController类的类方法openParentApplication(userInfo:reply:),正如下面的代码块所展示的那样:
1
2
3
4
5
6
|
// Notify companion iPhone app of some changes in the shared container.
let kSharedContainerDidUpdate =
"com.rayWenderlich.shared-container.didUpdate"
let requestInfo: [NSObject: AnyObject] = [kSharedContainerDidUpdate:
true
]
WKInterfaceController.openParentApplication(requestInfo) { (replyInfo: [NSObject : AnyObject]!, error: NSError!) -> Void in
// Handle the reply from the companion iPhone app...
}
|
在userInfo 字典里,你简单地传一个flag或一些数据给匹配的iPhone app使用。为了接收这个通信,匹配的iPhone app必须在其app delegate里实现application(_:handleWatchKitExtensionRequest:reply:)方法。
1
2
3
4
5
6
7
|
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
let kSharedContainerDidUpdate =
"com.rayWenderlich.shared-container.didUpdate"
if
let isUpdate = userInfo[kSharedContainerDidUpdate] as? Bool {
// Process request, then call reply block
reply(...)
}
}
|
如果匹配的iPhone app被挂起或者被终止了,系统会在后台拉起它。根据交流的目的性,匹配的iPhone app可能会在回复block里返回信息以便watch app可以相应的做出处理。
iPhone app如何同它的watch app通信?
没有任何办法可以让一个iPhone app向它的扩展发起通信。除开在一个共享的容器中写数据,或者回应watch app的请求,iPhone app可以使用Darwin Notification Center通知WatchKit extension一个特定的事件- Darwin Notification Center是Core Foundation框架的一个API。
如果你决定使用Darwin Notification Center,有一些非常重要的事情需要牢记:
-
一个应用只有唯一一个Darwin Notification Center。
-
所有的Darwin通知都是系统级的。
-
为了能将通知发出, 主线程的run loop必须在一个常用的模式下运行,比如kCFRunLoopDefaultMode。
-
watch app和iPhone app都必须在前台运行,才能处理发送和接收Darwin 通知。
-
你不能通过Darwin 通知传送对象,因为他们只能携带一个名字和一个userInfo字典。
-
Darwin通知是非持久性的,相反是即时传送的。因此,如果一个观察者被挂起、终止或者放置在后台中,通知就会丢失。
如果我没有实现静态或动态通知界面会怎样?
即时你没有watch app,系统仍会为你的iPhone app在手表上展示通知。 但是,默认的通知界面没有定制化风格。如果你有交互性的通知,系统会在手表上隐藏这些操作。
setImage(_:) 和setImageNamed(_:)有什么不同?
如果你想展示的图片在手表上被缓存了或在watch app bundle里的asset catalog里,你应该使用setImageNamed(_:)方法。如果图片没有被缓存,可使用setImage(_:)方法,它将通过无线方式将图片数据传送到Apple Watch。
当满足以下条件之一时,图片就在手表上进行了缓存:
1.在工程中同watch app target捆绑在一起,意味着在工程中图片属于Watch App target的asset catalog。
2.事先通过 WKInterfaceDevice APIs:addCachedImage(_:name:) 或addCachedImageWithData(_:name:)其中之一在手表上明确地进行了缓存。
可以在watch App上使用iCloud吗?
是的。你可以在Apple Watch app中使用iCloud。Lister: A Productivity App是苹果提供的示例工程,演示了怎样在上下文中使用iCloud。
动画
我怎样在watch app中添加动画?
只有唯一一个方法可以在Apple Watch上展示动画: image sequences。 想要让一些内容看起来是动画的,你必须事先做好一系列的图片,然后像flip-book那样重覆循环他们。动画GIF的时代又回来了!
你可以在WKInterfaceImage对象里通过展示一系列静态图片来创建自定义动画。
1
2
3
4
5
6
|
@IBOutlet weak var image: WKInterfaceImage?
...
image?.setImageNamed(image1)
// Load the initial image using the required format
image?.startAnimating()
// Starts animating
...
image?.stopAnimating()
// Optional. Stops animating.
|
你也可以使用下面的代码片段让一部分的图片动画。
1
|
image?.startAnimatingWithImagesInRange(range, duration: 2, repeatCount: 1)
|
我可以通过编码为Apple Watch创建动画吗?
可以,但是可能不会是以你想的那样实现-就像我前面说的--目前没有Core Animation框架或类似框架。你可以使用Core Graphics将动画的每一帧渲染为离屏上下文,将其渲染为UIImage的实例,最后在Apple Watch上你将有一系列的可动画图片。
下面的代码片段展示了如何使用Core Graphics通过为每帧生成一个图像序列来创建一个移动的圆圈动画。(代码来自Jack Wu的WatchKit by Tutorials)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Create an offscreen context
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
let context = UIGraphicsGetCurrentContext()
for
centerX in 0..100 {
// Draw a circle at centerX.
// Create a snapshot.
let image = UIGraphicsGetImageFromCurrentImageContext()
// Write the image as a file
let data = UIImagePNGRepresentation(image)
let file =
"path\\image_\(centerX).png"
data.writeToFile(file, atomically:
true
)
}
// End the context.
UIGraphicsEndImageContext()
|
Apple Watch上的最大动画帧率是多少?
你不能在Apple Watch上设置动画帧率。但是,设置动画长度是可以的,让系统自动决定帧率。
如果图像系列是无线传输的,比如当图片没被缓存时,帧率可达10fps。如果图片已经在Apple Watch上通过图片缓存或者在WatchKit app bundle里的asset catalog 里缓存了,帧率可达30fps。
调试和单元测试
我如何用模拟器同时运行和调试iPhone app 和Apple Watch App?
-
构建并运行watch app;启动Apple Watch模拟器,运行watch app,并且运行调试器。
-
然后在iOS模拟器上,点击iPhone app 的图标运行它。
-
回到Xcode, 在顶部菜单选择Debug\Attach To Process ,然后选择相应的iPhone app。Xcode Debug Navigator里会增加一个新的进程,并连接iPhone app和调试器。
WatchKit扩展如何进行单元测试?
你可以用你为iPhone或iPad 应用写单元测试的方法来写你的watch应用单元测试;简单地为工程的WatchKit extension加一个新的单元测试target。但是,你不能将watch app指定为Host Application。
对于iPhone app target, iPhone应用展示为Host Application。
但是对于WatchKit Extension target而言是没有Host Application的。
你必须明确地为WatchKit extension单元测试target添加每个你想要测试的文件。
从WatchKit扩展里添加你想要测试的文件到单元测试的target。
Sharing Data(共享数据)
怎样在WatchKit扩展和它的containing iOS app间共享数据?
你需要使用App Groups; 它引用了扩展和containing iPhone app都可以访问的本地文件系统中的一个容器。你可以定义多个App Groups,然后不同的扩展都可以使用。
一旦App Groups启用,你可以根据需要使用以下任何一种技术:
-
直接的在一个共享的容器中读和写。通过调用NSFileManager来获得共享容器的URL,所用API为:
1
2
3
|
let kMyAppGroupName =
"com.raywenderlich.mywatchapp.container"
var sharedContainerURL: NSURL? = NSFileManager.defaultManager().
containerURLForSecurityApplicationGroupIdentifier(kMyAppGroupName)
|
-
使用共享的NSUserDefaults。想要在共享容器中创建默认的存储,你需要该用NSUserDefaults(suiteName:)方法初始化一个新的NSUserDefaults 实例,并且传递App Groups的唯一identifier,如下:
1
2
|
let kMyAppGroupName =
"com.raywenderlich.mywatchapp.container"
let sharedUserDefaults = NSUserDefaults(suiteName: kMyAppGroupName)
|
注意:当你想向一个共享的容器中读或写时,你必须做的非常协调以防止数据损坏,因为一个共享容器可以被不同的进程同时访问。推荐使用NSFilePresenter和NSFileCoordinator协调读和写。但是,我们还建议不要在app扩展中使用文件协调API,因为它们会导致死锁。
引起问题的原因是app扩展的生命周期。App extensions 只有3个状态:(a) running(运行);(b) suspended(挂起)以及(c) terminated(终止)。如果一个使用了文件协调API的app扩展在写入时被挂起了,它永远不会有机会让出所有权,因此其他进程就死锁了。
但是,一个iPhone或者iPad应用在后台时通过 app delegate 被通知,这时可以移除file presenters。当应用被带回到前台时,file presenters可以被重新添加。
替代方法是,你可以使用atomic safe-save 操作,比如NSData‘s writeToURL(_:atomically:)方法。SQLite和 Core Data也可以允许多个进程安全的共享同一个容器里的数据,即便其中一个在事务处理当中时被挂起。
你可以在Technical Note TN2408: Accessing Shared Data from an App Extension and its Containing App中学到更多。
怎样在Watch app和iPhone app中共享一个Core Data数据库?
想要共享一个Core Data 永久存储文件,你本质上使用跟共享数据示例一样的方法。下面的代码段展示了怎样实现该任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let kMyAppGroupName =
"com.raywenderlich.mywatchapp.container"
var sharedContainerURL: NSURL? = NSFileManager.defaultManager().
containerURLForSecurityApplicationGroupIdentifier(kMyAppGroupName)
if
let sharedContainerURL = sharedContainerURL {
let storeURL = sharedContainerURL.URLByAppendingPathComponent(
"MyCoreData.sqlite"
)
var coordinator: NSPersistentStoreCoordinator? =
NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
coordinator?.addPersistentStoreWithType(NSSQLiteStoreType,
configuration: nil,
URL: storeURL,
options: nil,
error: nil)
}
|
聊聊商业化的问题
我可以为Apple Watch 创建游戏吗?什么类型的游戏适合?
现在说什么样类型的游戏会成功和用户是否愿意在手表上玩游戏还太早。毫无疑问,你需要从不同角度来思考Apple Watch。还记得iPhone和iPad上的游戏同笔记本上的游戏相比时用户心理的不同吗?同样地,Apple Watch 要求一个独一无二的途径。
我们已经知道能做的很有限,因为还没有能在Apple Watch上访问硬件的API,或者支持手势识别,或者允许用户屏幕上自定义绘制。记住,你仅仅可以使用原始的界面元素。
但是不要让这些限制阻碍了你的想象力,要把它们当做是基本规则。
怎样通过Apple Watch apps 赚钱?
现在说这个有点言之过早。注意一件事:它不支持iAd,基于小屏幕尺寸和用户与Watch app交互时间有限来考虑,屏幕上的广告可能会使用户厌烦,以及不论以何种方式表现在收益上都不是那么值得。
同样,如果WatchKit 扩展被包含在app bundle里,你不能禁用它或者说阻止用户安装它,所以它不能作为通过IAP解锁某些功能的补充。
但是,仍有几条WatchKit extension货币化的方法:
-
如果你在App Store中有一个免费版本应用和一个付费版本应用,你可以仅仅在付费版本的应用中实现WatchKit extension。
-
如果你的一个应用使用了IAP,你可以在扩展中仅仅展示有限的信息,但是允许用户通过IAP解锁额外的功能。
这显然不是一个所有可能货币化WatchKit app的详尽方法列表, 但是有一点很清楚:你将会尽可能的发挥创造力来提高使app货币化,就像你开发watch apps本身一样。
有没有什么理由可以让开发者相信,仅仅为App Store开发watch apps 也能营生?
尽管Apple Watch确实是一个充满了机会的全新平台,但它不太可能创造一个类似App Store最初亮相时的黄金时代。
注意:Apple Watch是一个完全不同的命题。基于它的美学价值和价格标签,它更类似于一个可以同iPhone沟通的视频,而不是基本的设备。
不过,WatchKit扩展可以更容易让app脱颖而出。像当初原生iPad 应用比仅仅按比例伸缩匹配的iPhone应用更成功一样。一款深思熟虑的,设计优良的匹配iPhone app 的Apple Watch app可以促进销售。