记一次 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

image

注意:在 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, activationDidCompleteWith activationState: WCSessionActivationState, 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开发(系列)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268