iOS和Apple Watch数据交换的框架WatchConnectivit
iOS和Apple Watch数据交换的框架WatchConnectivit

iOS和Apple Watch数据交换的框架WatchConnectivit

WatchConnectivity 是 Apple 提供的一个框架,允许 iOS 设备与 Apple Watch 之间进行通信和数据交换。它的主要作用是使得 iOS 应用 和 WatchOS 扩展 能够互相传递数据和消息。这对于实现 iPhone 和 Apple Watch 之间的同步和交互非常重要,尤其是对于需要双向通信的应用,如健康监测、通知、实时数据等。

首次出现在 iOS 8 和 WatchOS 2 中。它允许 iOS 应用与 WatchOS 应用之间交换数据和消息,例如传输文件、同步数据、发送实时消息等。通过 WatchConnectivity,让两者在设备之间保持通信和同步。

WatchConnectivity 的核心功能

1、即时消息传递

可以从 iPhone 发送即时消息到 Apple Watch,或反之。适用于实时数据更新或交互。例如,更新界面、发送通知等。

if WCSession.default.isReachable {
    WCSession.default.sendMessage(["key": "value"], replyHandler: { response in
        // 处理响应
    }, errorHandler: { error in
        // 处理错误
    })
}

2、数据传输

可以在两个设备之间传输文件或应用数据。比如,将用户数据从 iPhone 同步到 Apple Watch,或将日志、图片等文件传输到另一个设备。

3、应用状态同步

允许设备间同步应用状态,例如将 iOS 设备上的某个数据同步到 Apple Watch 或将 Watch 上的健康数据、提醒等同步到 iPhone。

4、背景数据传输

即使应用处于后台,WatchConnectivity 也能传输数据。系统会在后台处理消息和数据同步,前提是设备仍然保持连接。

5、Live Activity 支持

支持通过 Live Activity 更新实时信息。比如,可以从 iOS 向 Apple Watch 发送实时活动信息,例如计时器、位置等动态数据。

如何使用 WatchConnectivity

使用 WatchConnectivity 进行通信时,主要通过 WCSession 类来管理会话。需要在 iOS 应用 和 WatchOS 扩展中分别实现。

1、初始化 WCSession

在 iOS 和 WatchOS 应用中都需要初始化并激活 WCSession,才能开始通信。

if WCSession.default.isSupported() {
    WCSession.default.delegate = self
    WCSession.default.activate()
}

如果设备(如 iPhone)支持与 Apple Watch 之间进行数据通信(通过 WatchConnectivity),则返回 true,否则返回 false。

因为并非所有设备都支持 WatchConnectivity,比如没有配对 Apple Watch 的 iPhone,或者某些旧设备。通过检查 isSupported() 可以避免在不支持的设备上尝试建立连接,避免错误。

1)设置 WCSession 的代理

WCSession.default.delegate = self

设置 WCSession 的代理为当前对象(通常是视图控制器或其他类)。

例如

WCSession.default.delegate = WCSessionDelegateImpl()  // 设置 WCSession 的代理

通过设置代理,可以实现一些回调方法,处理 WatchConnectivity 会话的状态变化或接收到的数据。

常见代理方法

session(_:activationDidCompleteWith:):会话激活完成后的回调。

session(_:didReceiveMessage:):接收到消息时的回调。

session(_:didReceiveMessageData:):接收到数据时的回调。

代理可以处理来自 Apple Watch 或 iPhone 的信息和事件。通过实现代理方法,可以在设备连接、断开或收到消息时作出响应。

2)激活 WCSession

WCSession.default.activate()

通过调用 activate() 方法,告诉系统准备好进行 WatchConnectivity 的通信。这是建立连接的关键步骤。

在激活之后,设备可以开始与配对的 Apple Watch 进行数据交换。

2、代理方法

通过代理方法,可以监听连接状态的变化,处理收到的消息,或处理发生的错误。

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
    // 收到消息
}

func session(_ session: WCSession, didReceiveFile file: WCSessionFile) {
    // 收到文件
}

func sessionWatchStateDidChange(_ session: WCSession) {
    // 监听 Watch 状态变化(如连接、断开等)
}

3、发送消息

在应用之间发送消息时,可以使用 sendMessage 或 sendMessageData 方法。这是 WatchConnectivity 发送即时消息的核心功能。

if WCSession.default.isReachable {
    WCSession.default.sendMessage(["key": "value"], replyHandler: { response in
        // 处理响应
    }, errorHandler: { error in
        // 处理错误
    })
}

isReachable 属性用于检查 iPhone 和 Apple Watch 之间是否有有效的连接,并且 Apple Watch 是否处于可以接收消息的状态。

sendMessage 用来向 Apple Watch 发送消息。消息内容是一个字典,可以在字典中放入任何需要发送的数据。

replyHandler 在 Apple Watch 成功接收到消息并作出响应时被调用。

errorHandler 用来捕获并处理发送过程中出现的任何错误,比如连接断开等问题。

常见的 WCSession 状态

isReachable:表示设备是否可达。如果 iPhone 和 Apple Watch 之间的连接正常,isReachable 将为 true。如果设备没有连接,或者 WatchOS 不可用,则为 false。

isPaired:表示设备是否已经配对。即 iPhone 是否和 Apple Watch 配对并连接。

isCompanionAppInstalled:表示 iPhone 上的应用是否已安装并可用。

WatchConnectivity 的限制

1、需要设备间连接:iPhone 和 Apple Watch 必须通过蓝牙或 Wi-Fi 连接,才能实现 WatchConnectivity 的功能。如果设备不在同一范围或没有有效连接,则无法进行数据传输。

2、限制于同一 Apple ID:确保两个设备属于同一Apple ID 下,否则无法进行通信。

3、后台限制:即使 WatchConnectivity 支持后台数据传输,但还是会有一些限制。例如,后台传输时,传输的内容可能会被延迟,或者有时需要前台应用的交互才能触发某些操作。

WatchConnectivity 触发的场景

1、应用启动时:当 iOS 或 WatchOS 应用启动时,WCSession 会尝试建立连接并进行初始化。

2、应用之间的数据交换:当用户在 iPhone 或 WatchOS 应用中发起数据交换时(例如发送消息或同步数据)。

3、设备连接或断开时:当 iPhone 与 Apple Watch 之间的连接状态发生变化时,WatchConnectivity 会触发相关代理方法,进行处理。

实际应用

在《存钱猪猪》应用中,需要将存钱罐的数据从iOS传递给WatchOS。具体来说,在iOS主应用中获取存钱罐的数据(存钱罐名称、当前金额等字段),使用WCSession将这些数据发送到Apple Watch。

1、iOS和Watch端配置WatchConnectivity 框架

在Xcode中分别找到iOS应用和Watch应用,在General中找到“Frameworks, Libraries, and Embedded Content”,点击添加按钮。

找到“WatchConnectivity.framework”框架,点击“Add”按钮进行添加。

注意:iOS端和Watch端都需要添加这一框架,Watch端还有一个WatchKit.framework,可能也需要配置上。

2、在iOS中初始化WCSession

首先,在 iOS 主应用中,需要确保 WCSession 已经被初始化并激活。

import WatchConnectivity

@main
struct pigletApp: App {
    @State private var wcSessionDelegateImpl = WCSessionDelegateImpl()
    
    init() {
        if WCSession.isSupported() {
            print("当前设备支持 WCSession 。")
            WCSession.default.delegate = wcSessionDelegateImpl // 设置 WCSessionDelegate
            WCSession.default.activate()
        } else {
            print("当前设备不支持 WCSession.")
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .environment(wcSessionDelegateImpl)
    }
}

// 定义 WCSessionDelegateImpl 来处理 WatchConnectivity 的代理方法
@Observable
class WCSessionDelegateImpl: NSObject, WCSessionDelegate {
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        print("进入 WCSessionDelegateImpl 的 session 方法")
        // 处理从 WatchOS 接收到的消息
        if let piggyBanks = message["piggyBanks"] as? [[String: Any]] {
            print("接收到 \(piggyBanks.count) 个存钱罐数据")
            for bank in piggyBanks {
                if let name = bank["name"] as? String {
                    print("Received piggy bank: \(name)")
                }
            }
        }
    }
    
    // 激活完成后的处理
    func session(_ session: WCSession, activationDidCompleteWith state: WCSessionActivationState, error: Error?) {
        if state == .activated {
            print("Watch端 WCSession 激活成功")
        } else {
            print("Watch端 WCSession 激活失败: \(String(describing: error))")
        }
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        // 会话变为非活跃时的处理
        print("WCSession 会话变为非活跃.")
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        // 会话被停用时的处理
        print("WCSession 会话被停用.")
    }
}

WCSessionDelegateImpl 类

这个类是 WCSessionDelegate 协议的实现,用来处理与 WatchOS 的通信。

需要实现的主要方法包括

1)session(_:didReceiveMessage:):当接收到来自 WatchOS 的消息时,会被调用。

2)session(_:activationDidCompleteWith:error:):会话激活完成后的回调。

3)sessionDidBecomeInactive(_:):当会话进入非活跃状态时被调用。

4)sessionDidDeactivate(_:):当会话被停用时被调用。

WCSessionDelegateImpl也可以单独放在一个Swift文件中进行管理。

3、获取数据并发送到 WatchOS

在主视图中,获取存钱罐数据并将其发送到 WatchOS。可以在视图加载时或者通过某个操作(如按钮点击)来触发数据发送。

import SwiftUI
import WatchConnectivity

struct Home: View {
    @Query var allPiggyBank: [PiggyBank]

    var body: some View {
        VStack {
            // 主视图内容,显示存钱罐列表等
            Button("Send Data to Watch") {
                sendPiggyBankDataToWatch()
            }
        }
    }

    // 将 PiggyBank 数据发送到 WatchOS
    func sendPiggyBankDataToWatch() {
        print("进入:sendPiggyBankDataToWatch")
        // 确保 WCSession 激活
        if WCSession.default.isReachable {
            print("iOS与Watch链接成功")
            // 创建存钱罐数据字典
            let piggyBankData: [[String: Any]] = allPiggyBank.map { bank in
                return [
                    "name": bank.name,
                    "icon": bank.icon,
                    "amount": bank.amount,
                    "targetAmount": bank.targetAmount,
                    "isPrimary": bank.isPrimary
                ]
            }
            print("piggyBankData:\(piggyBankData)")
            print("发送数据到 WatchOS")
            // 将数组包装在字典中
            let wcSessionPiggyBanks: [String: Any] = ["piggyBanks": piggyBankData]
            WCSession.default.transferUserInfo(wcSessionPiggyBanks)
            print("Data sent: \(wcSessionPiggyBanks)")
        } else {
            print("iOS与Watch未连接,isReachable = false")
        }
    }
}

isReachable只有在iOS主应用和Watch应用同时打开并亮屏的情况下,才为true,否则为false。

在主视图中点击Button后,会将SwiftData中allPiggyBank数组(即所有存钱罐的数据)映射成多个字典,每个字典代表一个存钱罐的数据。每个字典包含以下键值对:

“name”: 存钱罐名称

“icon”: 存钱罐图标名称

“amount”: 当前金额

“targetAmount”: 目标金额

“isPrimary”: 是否为主要存钱罐

返回的结果

原始数组 piggyBank 是 [PiggyBank] 类型,即多个 PiggyBank 对象的数组。

每个 PiggyBank 对象会被转换成字典 [String: Any] 类型。

最终 piggyBankData 的类型是 [[String: Any]],即包含多个字典的数组。

transferUserInfo方法用于传输较小的键值对数据(通常是字典格式)。它将包含存钱罐数据的字典发送到 WatchOS。

需要注意的是,transferUserInfo期望传入的是一个字典类型 ( [ String: Any ] ),因此需要将piggyBankData 从数组转换为字典。

let wcSessionPiggyBanks: [String: Any] = ["piggyBanks": piggyBankData]
WCSession.default.transferUserInfo(wcSessionPiggyBanks)

4、在 WatchOS 中接收数据

在 WatchOS 中,确保在 Watch 应用中实现 WCSessionDelegate,并接收来自 iOS 主应用的数据。

import WatchConnectivity

@main
struct BankletWatch_Watch_AppApp: App {
    @State private var wcSessionDelegateImpl = WatchSessionDelegate()
    
    init() {
        // 激活 WCSession
        if WCSession.isSupported() {
            print("激活 WCSession")
            WCSession.default.delegate = wcSessionDelegateImpl  // 设置 WCSession 的代理
            WCSession.default.activate()  // 激活 WCSession
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .environment(wcSessionDelegateImpl)
    }
}

// 定义 WatchSessionDelegate 来接收 iOS主应用传递的数据
@Observable
class WatchSessionDelegate: NSObject, WCSessionDelegate {
    
    // 存储接收到的数据
    var piggyBanks: [PiggyBank] = []
    // 处理从 iOS 应用发送过来的消息
    func session(_ session: WCSession, didReceiveUserInfo wcSessionPiggyBanks: [String : Any]) {
        // 接收到的 userInfo 字典
        print("Received user info: \(wcSessionPiggyBanks)")
        if let piggyBank = wcSessionPiggyBanks["piggyBanks"] as? [[String: Any]] {
            print("piggyBank:\(piggyBank)")
            piggyBanks = piggyBank.compactMap {
                if let name = $0["name"] as? String,
                       let icon = $0["icon"] as? String,
                       let amount = $0["amount"] as? Double,
                       let targetAmount = $0["targetAmount"] as? Double,
                       let isPrimary = $0["isPrimary"] as? Bool {
                        return PiggyBank(name: name, icon: icon, amount: amount, targetAmount: targetAmount, isPrimary: isPrimary)
                    }
                    return nil  // 如果解包失败,则返回 nil
            }
        }
        print("piggyBanks:\(piggyBanks)")
    }
    
    // 激活完成后的处理
    func session(_ session: WCSession, activationDidCompleteWith state: WCSessionActivationState, error: Error?) {
        if let error = error {
            print("Watch端 WCSession 激活失败: \(error)")
        } else {
            print("Watch端 WCSession 激活成功,状态: \(state.rawValue)")  // 输出状态
        }
    }
}

struct PiggyBank: Codable {
    var name: String
    var icon: String
    var amount: Double
    var targetAmount: Double
    var isPrimary: Bool
}

BankletWatch_Watch_AppApp

WatchOS 应用的入口点(@main),它负责设置并启动应用。BankletWatch_Watch_AppApp 初始化时会执行以下步骤:

WCSession.default.activate():激活 WCSession,这是与 iOS 设备进行数据传输的必需步骤。通过激活 WCSession,WatchOS 可以与 iOS 应用建立连接并进行通信。

WCSession.default.delegate = wcSessionDelegateImpl:设置 WatchOS 的 WCSessionDelegate 为 wcSessionDelegateImpl,这个代理类负责处理来自 iOS 端的消息。

@State private var wcSessionDelegateImpl = WatchSessionDelegate():声明一个 wcSessionDelegateImpl 变量,用来管理 WatchOS 与 iOS 之间的连接。

WatchSessionDelegate

这是 WatchOS 应用的代理类,负责处理 WCSession 相关的回调。它实现了 WCSessionDelegate 协议,能够处理数据接收、激活和错误等事件。

session(_:didReceiveUserInfo:)

该方法是用来接收从 iOS 应用发送的用户信息数据。在这里,它将从 iOS 传递过来的 piggyBanks 数据解码,并存储到 piggyBanks 数组中。解码使用 compactMap 来确保只有成功解包的 PiggyBank 对象会被存储。

session(_:activationDidCompleteWith:)

当 WCSession 激活完成时,这个回调方法会被调用。在这个方法里,可以检查 WCSession 是否成功激活,并处理可能的错误。

PiggyBank 数据模型

PiggyBank 是用来表示存钱罐的数据模型。它符合 Codable 协议,意味着它可以被序列化为 JSON 格式,并且可以从 JSON 格式反序列化。模型包含以下属性:

name: 存钱罐的名称

icon: 存钱罐的图标

amount: 当前存款金额

targetAmount: 目标存款金额

isPrimary: 是否为主要存钱罐

5、在WatchOS显示内容

struct Home: View {
    @Environment(WatchSessionDelegate.self) var wcSessionDelegateImpl // 从环境中读取 Person
    
    var body: some View {
        VStack {
            if !wcSessionDelegateImpl.piggyBanks.isEmpty {
                Text("\(mainPiggyBank.name)")
                    .font(.footnote)
            } else {
                Image("emptyBox")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 100)
            }
        }
    }
}

从Enviroment中通过wcSessionDelegateImpl接收数据,在Text视图中将相关内容显示出来。

总结

WatchConnectivity 是 iOS 和 WatchOS 之间的数据传输桥梁,通过它,可以在这两种设备上实现信息同步、即时消息传递以及背景数据传输等功能。通过正确实现 WCSession,使得 iPhone 和 Apple Watch 应用之间的互动更为流畅和高效。

需要注意的是,WCSession.default.isReachable只有在iOS端和Watch端都打开的情况下,才可以实现数据的传递,因此并不是适用于只打开Watch端的情况下显示数据。

从WCSession中传递过来的数据仍然需要存储,因此如果是少量数据,还是建议使用App Group传递。

最后是在探索的过程中发现,AppWatch在接收iOS应用传递时,存在一个初始化的过程。iOS向Watch传递数据时,第一次很有可能传递失败,或者Watch在首次接收数据时会进行初始化,等待几秒后的第二次才能成功,因此在实际应用中还需要考虑首次初始化的情况,这一问题具体原因有待研究。

扩展知识

Xcode提示Connecting to 方君宇的Apple Watch

如果在连接Watch的过程中,存在提示:

Connecting to 方君宇的Apple Watch

可以参考《Xcode提示Connecting to 方君宇的Apple Watch》。

后台接受远程通知

如果Watch需要执行额外的任务,比如定期在后台获取IOS数据,更新应用内容,可以在Capabilities 中启用 Remote Notifications功能。

在 Xcode 中,确保 iOS Capabilities 配置正确。

打开 iOS 应用的 Target,进入 Signing & Capabilities 选项卡,确保以下内容:

Background Modes:启用 Background fetch 和 Remote notifications(如果需要后台通信)。

Push Notifications:如果需要推送通知,确保启用。

对于 WatchOS 应用,通常不需要额外的 Capabilities 配置。

具体的后台同步内容则需要根据自身的需求进行设计。

相关文章

Xcode提示Connecting to 方君宇的Apple Watch:https://fangjunyu.com/2025/02/19/xcode%e6%8f%90%e7%a4%baconnecting-to-%e6%96%b9%e5%90%9b%e5%ae%87%e7%9a%84apple-watch/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注