記一次 Apple Watch App 開發經歷

前言:
隨着如今 Apple 生態圈的發展,愈來愈多的 App 會把本身的簡化版從 iOS 遷移至 WatchOS(支付寶、微信、手Q、頭條、QQ音樂、網易雲音樂等等,都有WatchApp)。
因而,我也是第一次嘗試了把咱們的組的 iOS App 遷移至 Apple Watch
從調研到實現,大概花了一週的時間。也踩了一些坑,記錄一下。
git


1、Apple Watch:

Apple Watch是蘋果公司主打 「健康」 概念的智能手錶。
於2014年發佈第一代Apple Watch 1,截至2020年,已發佈Apple Watch 5github

Apple Watch App分爲兩種:緩存

  • Watch App for iOS App:從iOS遷移過來的Watch App,可與iOS App通訊。
  • Watch App:獨立的Watch App,可獨立安裝在Apple Watch上。

大部分是第一種,Watch App for iOS App。本文也是以第一種狀況舉例。bash


準備工做:微信

新建一個watchOStargetsession

新建target

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

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

2、與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)
 
    // ...
}
複製代碼

3、iOS與WatchOS的通訊:

Apple在 WatchOS 2.0後發佈了 WatchConnectivity框架,用於iOSWatchOS之間的通訊。ide

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, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print(session)
    }
}
複製代碼

發消息、接收消息也與iOS端實現一致。
總結來講,就是經過WatchManager單例通訊,並經過代理回調接收消息。

4、一些「坑」與解決方案:

  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位的硬件設備。

5、特殊需求: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))
}
複製代碼

6、Watch相關參考學習資料

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

相關文章
相關標籤/搜索