開心之餘,這也代表著在手機與手錶之間的溝通方式有了改變,而這正是我們要為大家所介紹的新功能Watch Connectivity。
首先,什麼是Watch Connectivity? Watch Connectivity為 watchOS 2中的一個新framework,他提供了裝置之間多種不同的溝通方式,有別於上一篇我們利用資料共享App Groups
的方式去做溝通,現在有了更快速且直接的方式來進行資料溝通。
openParentApplication
,詳見官方文件。Watch Connectivity Session – WCSession
在我們使用 Watch Connectivity 之前,我們必須為 App 建立 Watch Connectivity Session,WCSession
便提供了傳送、接收、監控狀態等函式供我們使用。
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
一開始,我們檢查這個iOS裝置,是否支援Session (Session在WatchOS上永遠有效),接著建立一個defaultSession()
,再將其session
委派,最後再啟動session
便設置完畢,別忘了記得繼承WCSessionDelegate
並import WatchConnectivity
。
不同的溝通方式
在Watch Connectivity中有兩類溝通方式,分別為:
- 即時訊息 Interactive Messaging – 適合立即性的訊息及必須為可傳遞的狀態(reachable)
- 背景傳輸 Background Transfers – 當傳送資料的App存在時,會持續傳輸。對應的App(另一端),不一定需要執行。 系統將會在適當的時機傳送內容。
即時訊息 Interactive Messaging
這種方式只能在兩個運行的App之間傳遞資料,且必須確認你的裝置為可傳遞的reachable
的狀態,你可以透過下列方式確認是否為可傳遞狀態。
if WCSession.defaultSession().reachable {
// 要做的事情
}
在確認完是否為可傳遞狀態後,Interactive Messaging提供了sendMessage()
和sendMessageData()
兩種方法讓我們使用,取決於傳輸資料的型態是否為dictionary
。
let dictData = // 建立一個dictionary型態的資料
WCSession.defaultSession().sendMessage(dictData,
replyHandler: { ([String : AnyObject]) -> Void in
})
errorHandler: { (NSError) -> Void in
});
let data = // 建立資料
WCSession.defaultSession().sendMessageData(data,
replyHandler: { ([String : AnyObject]) -> Void in
})
errorHandler: { (NSError) -> Void in
});
而在資料接收端我們則是使用session:didReceiveMessage:
或session:didReceiveMessage:replyHandler:
來處理。
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
}
背景傳輸 Background Transfers
在背景傳輸中,有三種不同方式,取決於想要傳送的資料種類,我們將會依依做介紹。
1. Application Context
首先是 Application Context。這種傳輸方式會用最新取得的資料,將原本在傳輸佇列中的舊資料做覆蓋,以保證取得的資料為最新的內容。在 App 存在的情況下,Application Context更新之後,系統將在適當的時機將資料進行傳輸。(applicationContext
的內容只能是dictionary
)
do {
let dictData = // 建立一個dictionary型態的資料
try WCSession.defaultSession().updateApplicationContext(dictData)
} catch {
}
透過委派之後使用session:didReceiveApplicationContext:
來取得我們所傳輸的資料。
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
}
2. User Info Transfer
相對於Application Context,這種方式會將所有內容依先進先出(FIFO)順序排入佇列中,因此不會有任何資料被覆蓋掉。(user info的內容只能是dictionary
)
let dictData = // 建立一個dictionary型態的資料
let dataTransfer = WCSession.defaultSession().transferUserInfo(dictData)
而搭配使用的接收資料方法為session:didReceiveUserInfo:
。
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject])
{
}
3. File Transfer
檔案傳輸這是相當直覺的,我們可以透過這個方法來傳輸你想要傳的檔案。
let fileURL = // 檔案路徑
let dictData = // 建立一個dictionary型態的資料
let fileTransfer = WCSession.defaultSession().transferFile(fileURL, metadata:dictData)
接收檔案的方法為session:didReceiveFile:
,也可以透過outstandingFileTransfers
來取得傳輸的狀態(例如:沒有被取消、失敗、或是已經被對應的App接收)。
func session(session: WCSession, didReceiveFile file: WCSessionFile)
{
}
範例實作
接下來,這個範例我們將會透過Apple Watch的介面去移動iPhone中的綠色小方塊。
你可以透過下載Starter Project來快速開始。下載完之後,打開Starter Project,你會發現我已為你建構好 Watch App 的介面並連結了IBAction
。
開啟InterfaceController.swift
,在開首加入import WatchConnectivity
和補上WCSessionDelegate
。
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
之後,我們宣告一個session
為WCSession.defaultSession()
,並於willActivate()
中建立先前提過的WCSession
。
let session = WCSession.defaultSession()
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if WCSession.isSupported() {
session.delegate = self
session.activateSession()
}
}
接著於buttonPressed()
函式中實作我們所需要用到的sendMessage()
用法,我們將四個按鈕加上了編號並且放入 data 這個dictionary
傳出去。
func buttonPressed(num : Int) {
print("You pressed \(num)")
if WCSession.isSupported() {
let data = ["buttonNum" : num]
session.sendMessage(data, replyHandler: { (content:[String : AnyObject]) -> Void in
print("Our counterpart sent something back.")
}, errorHandler: { (error ) -> Void in
print(error.domain)
})
}
}
完成後,我們來實作接收端,也就是ViewController.swift
,一樣記得import WatchConnectivity
和補上WCSessionDelegate
。
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
var greenBox: UIView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.greenBox = UIView()
self.greenBox!.backgroundColor = .greenColor()
self.greenBox!.frame = CGRectMake(CGRectGetMidX(self.view.frame) - 50, CGRectGetMidY(self.view.frame) - 50, 100, 100)
self.view.addSubview(self.greenBox!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
並且於viewDidLoad()
中建立WCSession
。
let session = WCSession.defaultSession()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
...
if WCSession.isSupported() {
session.delegate = self
session.activateSession()
}
}
最後我們來實作didReceiveMessage()
函式,將接收到的資料加以應用。根據按鈕的編號來去對greenBox
移動其位子,而dispatch_async
的意思就是將任務進行異步並行處理,不一定需要一個任務處理完後才能處理下一個。而當我們移動greenBox
後,應該更新畫面,從 queue 的概念去設計,就是要將更新畫面的動作放到main queue中,因為iOS裡面永遠是主執行緒來更新重畫UI,dispatch_get_main_queue()
函式就是返回主執行緒。
func session(session: WCSession, didReceiveMessage data: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if let pressNum = data["buttonNum"] as? Int {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
switch pressNum {
// Up
case 0: self.greenBox!.frame.origin.y -= 20
// Down
case 1: self.greenBox!.frame.origin.y += 20
// Left
case 2: self.greenBox!.frame.origin.x -= 20
// Right
case 3: self.greenBox!.frame.origin.x += 20
default:
print("Error!")
}
})
}
}
完成之後,Run起來看看,你是不是也可以透過Apple Watch去遙控iPhone中的綠色小方塊,很有趣吧!
你可在這裡下載完整示範程式,如有任何疑問,歡迎留言。