記一次 Apple Watch App 開發經歷
前言:
隨著現在Apple
生態圈的發展,越來越多的App
會把自己的簡化版從iOS
遷移至WatchOS
(支付寶、微信、手Q、頭條、QQ音樂、網易雲音樂等等,都有Watch
版App
)。
於是,我也是第一次嘗試了把我們的組的iOS App
遷移至Apple Watch
。
從調研到實現,大概花了一週的時間。也踩了一些坑,記錄一下。
一、Apple Watch:
Apple Watch
是蘋果公司主打 “健康” 概念的智慧手錶。
於2014年釋出第一代Apple Watch 1
,截至2020年,已釋出Apple Watch 5
。
Apple Watch App
分為兩種:
-
Watch App for iOS App
iOS
遷移過來的Watch App
,可與iOS App
通訊。 -
Watch App:獨立的
Watch App
,可獨立安裝在Apple Watch
上。
大部分是第一種,Watch App for iOS App
。本文也是以第一種情況舉例。
準備工作:
新建一個watchOS
的target
。
這時,會出現兩個target:Apple Watch
、Apple Watch Extension
。
注意:在 WatchOS
中,無法像 iOS
那樣依賴 UIKit
寫出各種複雜的介面。目前,只能依賴 storyboard
搭建出一些簡單的UI介面與介面跳轉邏輯。
二、與iOS的主要區別:
- 只能用
storyboard
- 簡單佈局,預設是垂直佈局。可通過巢狀
Group
來完成縱向佈局需求。 - 介面之間的傳值,需要依賴
contextForSegue
方法。
在storyboard
中設定segueIdentifier
。
同時在下一級controller的awake(withContext context: Any?)
方法接收解析context
。
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
if segueIdentifier == "" {
// ...
return "A"
} else {
// ...
return "B"
}
}
override func contextForSegue(withIdentifier segueIdentifier: String,in table: WKInterfaceTable,rowIndex: Int) -> Any? {
if segueIdentifier == "" {
if rowIndex == 0 {
return "A"
} else {
return "B"
}
} else {
return "C"
}
}
--------------------------------------------
// 下一級controller中,通過context物件接收。
override func awake(withContext context: Any?) {
super.awake(withContext: context)
let item = context as? String // 上一級傳遞的資料
print(item)
// ...
}
複製程式碼
三、iOS與WatchOS的通訊:
Apple在 WatchOS 2.0
後釋出了 WatchConnectivity
框架,用於iOS
與WatchOS
之間的通訊。
1.iOS端實現:
實現一個單例WatchManager
。用於給Watch
端發訊息、接收Watch
的訊息。
App啟動後,在合適時機呼叫startSession
。初始化WCSession
回話。
import UIKit
import WatchConnectivity
class WatchManager: NSObject,WCSessionDelegate {
static let manager = WatchManager()
var session: WCSession?
private override init() {
super.init()
}
func startSession() {
if WCSession.isSupported() {
session = WCSession.default
session?.delegate = self
self.session?.activate()
}
}
func session(_ session: WCSession,activationDidCompleteWith activationState: WCSessionActivationState,error: Error?) {
print(session)
}
func sessionDidBecomeInactive(_ session: WCSession) {
print("?sessionDidBecomeInactive")
}
func sessionDidDeactivate(_ session: WCSession) {
print("?sessionDidDeactivate")
}
}
複製程式碼
同時,在需要給手錶發訊息的地方呼叫sendMessage
、sendMessageData
、transferFile
方法。
可以傳遞 Dictionary
、Data
、file
型別的資料。
注意:這裡有個坑,
sendMessage
方法的replyHandler
、errorHandler
引數不能直接傳nil
,不然訊息可能會發不出去。
if TDWatchManager.manager.session?.isReachable == true { //判斷是否可達
TDWatchManager.manager.session?.sendMessage(["key": "value"],replyHandler: { (dict) in
print(dict)
},errorHandler: { (error) in
print(error)
})
}
複製程式碼
2.Watch端實現:
同樣,實現一個單例WatchSessionManager
。用於接收iOS
端的訊息,給iOS
端發訊息。
在App啟動後,呼叫startSession
,初始化session
物件。
import WatchKit
import WatchConnectivity
class WatchSessionManager: NSObject,WCSessionDelegate {
static let manager = WatchSessionManager()
var session: WCSession?
private override init() {
super.init()
}
func startSession() {
if WCSession.isSupported() {
session = WCSession.default
session?.delegate = self
self.session?.activate()
}
}
// 資料來源:
func session(_ session: WCSession,didReceiveUserInfo userInfo: [String : Any] = [:]) {
print("收到iPhone端的userInfo")
}
func session(_ session: WCSession,didReceive file: WCSessionFile) {
print("收到iPhone端的file")
}
func session(_ session: WCSession,didReceiveMessage message: [String : Any]) {
print("收到iPhone端的message")
}
func session(_ session: WCSession,error: Error?) {
print(session)
}
}
複製程式碼
發訊息、接收訊息也與iOS
端實現一致。
總結來說,就是通過WatchManager
單例通訊,並通過代理回撥接收訊息。
四、一些“坑”與解決方案:
-
使用
WatchConnectivity
通訊,需要iOS App
和Watch App
端同時存活,同時處於Reachable
狀態。
只要一端不線上,就無法通訊。
如果對資料的實時性有要求,Watch
端就不能依賴iOS
端的資料了。 -
Apple Watch 4
及以下的裝置是32
位的硬體與系統,無法解析64
位的資料。(Apple Watch 5
開始是64
位的硬體與系統)
解決方案:
-
對於問題一,好在
Watch
端可連線WiFi
,支援NSURLSession
。
可以使用AFNetworking
/Alamofire
主動發動請求。這樣就保證的資料的實時性。
但請求裡的登入態(token
校驗等等)怎麼辦呢?
目前的方案是,先通過WatchConnectivity
通訊從iOS端獲取使用者資料(token
等等),並快取在Watch本地用於請求。(為了防止token
失效等問題,只要iOS
端和Watch
端同時線上時,更新並快取最新的token
。) -
對於問題二,如果請求裡含有
64
位資料(比如Int64
),那麼可能需要服務端配合處理一下了。
給Watch
端的資料不要包含64
位的資料。
目前沒想到更好的解決方法,畢竟是32
位的硬體裝置。
五、特殊需求:Apple Watch生成二維碼
這裡感謝:《QRCode.generate()! —— BiliBili》這篇部落格。
博主推薦了一個用Swift
寫的強大的二維碼三方庫:EFQRCode。
支援: iOS,macOS,watchOS and tvOS.
-
匯入:
pod 'EFQRCode/watchOS'
-
使用:在
Watch
端生成二維碼。
let cgImage = EFQRCode.generate(content: "https://github.com/EFPrefix/EFQRCode")
if let cgImage = cgImage {
ImageView.setImage(UIImage(cgImage: cgImage))
}
複製程式碼