美文网首页Apple W...
记一次 Apple Watch App 开发经历

记一次 Apple Watch App 开发经历

作者: 齐舞647 | 来源:发表于2020-05-14 15:20 被阅读0次

    前言:
    随着现在 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开发(系列)

    相关文章

      网友评论

        本文标题:记一次 Apple Watch App 开发经历

        本文链接:https://www.haomeiwen.com/subject/evconhtx.html