1. 程式人生 > IOS開發 >記一次 Apple Watch App 開發經歷

記一次 Apple Watch App 開發經歷

前言:
隨著現在 Apple 生態圈的發展,越來越多的 App 會把自己的簡化版從 iOS 遷移至 WatchOS(支付寶、微信、手Q、頭條、QQ音樂、網易雲音樂等等,都有WatchApp)。
於是,我也是第一次嘗試了把我們的組的 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。本文也是以第一種情況舉例。


準備工作:

新建一個watchOStarget

新建target

這時,會出現兩個target:Apple WatchApple Watch Extension

注意:在 WatchOS 中,無法像 iOS 那樣依賴 UIKit 寫出各種複雜的介面。目前,只能依賴 storyboard 搭建出一些簡單的UI介面與介面跳轉邏輯。

二、與iOS的主要區別:

  1. 只能用storyboard
    拖拽相應控制元件,搭建基本UI。
  2. 簡單佈局,預設是垂直佈局。可通過巢狀Group來完成縱向佈局需求。
  3. 介面之間的傳值,需要依賴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框架,用於iOSWatchOS之間的通訊。

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")
    }
}
複製程式碼

同時,在需要給手錶發訊息的地方呼叫sendMessagesendMessageDatatransferFile方法。
可以傳遞 DictionaryDatafile型別的資料。

注意:這裡有個坑,sendMessage 方法的 replyHandlererrorHandler引數不能直接傳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單例通訊,並通過代理回撥接收訊息。

四、一些“坑”與解決方案:

  1. 使用WatchConnectivity通訊,需要iOS AppWatch App端同時存活,同時處於Reachable狀態。
    只要一端不線上,就無法通訊。
    如果對資料的實時性有要求,Watch端就不能依賴iOS端的資料了。

  2. Apple Watch 4及以下的裝置是32位的硬體與系統,無法解析64位的資料。(Apple Watch 5開始是64位的硬體與系統)

解決方案:

  1. 對於問題一,好在Watch端可連線WiFi,支援NSURLSession
    可以使用AFNetworking/Alamofire主動發動請求。這樣就保證的資料的實時性。
    但請求裡的登入態(token校驗等等)怎麼辦呢?
    目前的方案是,先通過WatchConnectivity通訊從iOS端獲取使用者資料(token等等),並快取在Watch本地用於請求。(為了防止token失效等問題,只要iOS端和Watch端同時線上時,更新並快取最新的token。)

  2. 對於問題二,如果請求裡含有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))
}
複製程式碼

六、Watch相關參考學習資料

官方檔案
Apple Watch開發入門(系列)
Apple Watch開發(系列)