1、前言
上一篇已经实现了AppleWatch实时获取用户心率数据,这一篇将在上一篇的基础上,实现心率数据传输到iOS APP中。
2、创建iOS APP
打开上一篇的最终项目,点击File -> New -> Target,选择iOS下的App,创建一个iOS App
注意:iOS APP和Bundle Identifer需要与Watch App保持一致,如示例中iOS APPBundle Identifer为com.ihunyu.MyHeartRate,Watch App中为com.ihunyu.MyHeartRate.watch
3、创建工具类Connectivity
在根目录创建新的文件夹Shared,并创建新的Swift文件,命名为Connectivity,作为数据传输的工具类。注意创建时Target同时勾选iOS App和Watch App,这样iOS和Watch两个App都可以用此工具类进行双向传输数据,如下图:
完整Connectivity.swift代码如下:
import Foundation
import WatchConnectivity
final class Connectivity: NSObject, ObservableObject {
static let shared = Connectivity()
@Published var heartRate:Int = 0
override private init() {
super.init()
#if !os(watchOS)
guard WCSession.isSupported() else {
return
}
#endif
WCSession.default.delegate = self
WCSession.default.activate()
}
public func send(value: Int) {
guard canSendToPeer() else { return }
let userInfo: [String: Int] = ["value": value]
WCSession.default.sendMessage(userInfo,replyHandler: nil,errorHandler: nil)
}
private func canSendToPeer() -> Bool {
guard WCSession.default.activationState == .activated else {
return false
}
#if os(watchOS)
guard WCSession.default.isCompanionAppInstalled else {
return false
}
#else
guard WCSession.default.isWatchAppInstalled else {
return false
}
#endif
return true
}
private func update(from dictionary: [String: Any]) {
guard let value = dictionary["value"] as? Int else {
return
}
DispatchQueue.main.async { [weak self] in
self?.heartRate = value
}
}
}
// MARK: - WCSessionDelegate
extension Connectivity: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
// If the person has more than one watch, and they switch,
// reactivate their session on the new device.
WCSession.default.activate()
}
#endif
// This method is called when a message is sent with failable priority
// *and* a reply was requested.
func session(
_ session: WCSession,
didReceiveMessage message: [String: Any],
replyHandler: @escaping ([String: Any]) -> Void
) {
update(from: message)
}
// This method is called when a message is sent with failable priority
// and a reply was *not* request.
func session(
_ session: WCSession,
didReceiveMessage message: [String: Any]
) {
update(from: message)
}
#if os(watchOS)
func session(_ session: WCSession, didReceive file: WCSessionFile) {
}
#endif
}
- 工具类使用单例,且定义了heartRate属性,用于保存和更新心率数据。
- 在init初始化方法中,设置WCSession代理并开始监听接受数据。
- 定义了发送数据的方法send(:),供外部调用。
- 定义了canSendToPeer()和update(from:)两个私有方法。
- 实现WCSessionDelegate代理方法,处理接受到的数据。
4、发送数据
打开HeartRateManager,在健康数据更新回调的方法中,添加发送数据的代码
Connectivity.shared.send(value: Int(self.heartRate))
Watch App只需添加上面一行代码即可,即修改后的方法为:
/// 数据更新回调
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
for type in collectedTypes {
if let quantityType = type as? HKQuantityType {
switch quantityType {
case HKQuantityType.quantityType(forIdentifier: .heartRate):
let statistics = workoutBuilder.statistics(for: quantityType)
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
DispatchQueue.main.async {
self.heartRate = Double(round(1 * value!) / 1)
}
// 发送数据
Connectivity.shared.send(value: Int(self.heartRate))
default:
return
}
}
}
}
5、完成App UI
打开iOS App中的ContentView文件,修改为:
import SwiftUI
struct ContentView: View {
@StateObject private var connectivity = Connectivity.shared
var body: some View {
VStack {
Image(systemName: "heart.circle.fill")
.imageScale(.large)
Text("\(connectivity.heartRate)")
.font(.largeTitle)
}
.padding()
}
}
#Preview {
ContentView()
}
Image(systemName: "")方法用了SF Sysmbols中的icon,具体可参考官方文档
6、查看效果
Scheme选择MyHeartRate Watch App,选择真机或者模拟器(如Apple Watch Series 7 via iPhone 15),运行后会同时打开iOS和Watch模拟器:
等待片刻,点击Watch上的开始按键,即可看到效果:
完整代码下载