健康界面与数据可视化
本文介绍 iOS-JL_Health 工程中 TWS(真无线立体声耳机)健康功能的界面实现与健康数据可视化方案,涵盖 TwsHealthViewController 的状态展示区、13 项健康操作列表、TwsHealthViewModel 的 RxSwift 数据流,以及底层基于 JL_BLEKit 框架 JLTwsHealthManager 的心率、血氧、计步、日常心率等健康数据的采集与展示链路。
Purpose and Scope
本页聚焦于「健康界面与数据可视化」这一能力:即 SDKTestHelper 演示工程中用于展示设备健康能力的 UI 层(TwsHealthViewController)与其 ViewModel 数据管道(TwsHealthViewModel),以及它们所消费的 JL_BLEKit 健康数据模型(JLTwsHealthConfig、JLTwsHealthHeartRateModel、JLTwsHealthManager)。
以下内容属于其他目录页面的边界,本页仅作引用、不展开:
- 表盘市场(Dial Market):
DialMarketViewController、DialMallViewController等界面及DialMarketHttp.swift中的/health/v1/api服务端接口——该接口路径中的 "health" 是云端服务名,与本文 TWS 健康传感器数据无关。 - 设备连接与 BLE 通信底层:JL_BLEKit 框架的整体架构(
JLTwsHealthManager之上层、设备配对/连接流程)。 - 音频单元与工具链:
JLAudioUnitKitDemo、JLAudioToolbox等。
如需上述主题,请参阅对应的目录页面。
Overview
健康界面是 SDKTestHelper 演示工程中的一个 TwsHealthViewController 页面,核心目标是向开发者直观展示 TWS 耳机健康传感器的能力与实时数据。它的设计要点:
- 能力探测:读取设备的健康能力配置(
JLTwsHealthConfig),让开发者确认设备支持哪些传感器。 - 传感器状态检查:查询心率、血氧、计步传感器的开关状态以及佩戴检测(in-ear)状态。
- 实时数据可视化:通过 RxSwift
BehaviorSubject把 BLE 回调数据流式投递到 UI,实时更新步数、心率等数值。 - 操作验证入口:以列表形式提供 13 个可触发动作(开始/停止心率、血氧、计步、日常心率同步、实时步数查询、轮询等),方便集成测试。
该页面位于 SDKTestHelper 工程而非主 App JL_Health 工程内,属于 SDK 集成验证工具;它直接依赖 JL_BLEKit 框架公开的健康管理类 JLTwsHealthManager(头文件位于主工程 JL_Health/Frameworks/JL_BLEKit.xcframework 中)。数据可视化采用「订阅 + 文本渲染」模式:每个健康数据源对应一个 BehaviorSubject,UI 层用 observe(on: MainScheduler.instance) 切换到主线程后写入 UILabel。
Architecture
flowchart TD
subgraph sg_UI["UI 层 (TwsHealthViewController)"]
NavBar["navigationView (标题)"]
StatusStack["statusStack (垂直 UIStackView)"]
HealthLabel["healthInfoLabel (健康能力)"]
SensorLabel["sensorStatusLabel (传感器状态)"]
StepLabel["realTimeStepLabel (实时步数)"]
HeartLabel["dailyHeartLabel (日常心率)"]
Table["tableView (13 项功能列表)"]
StatusStack --> HealthLabel
StatusStack --> SensorLabel
StatusStack --> StepLabel
StatusStack --> HeartLabel
end
subgraph sg_VM["ViewModel 层 (TwsHealthViewModel)"]
VM["TwsHealthViewModel<br/>(JLTwsHealthManagerDelegate)"]
CfgSub["healthConfigSubject"]
SensorSub["sensorStatusSubject"]
FeedSub["opFeedbackSubject"]
StepSub["stepSubject"]
HeartSub["dailyHeartSubject"]
end
subgraph sg_SDK["SDK 层 (JL_BLEKit)"]
Manager["JLTwsHealthManager"]
CfgModel["JLTwsHealthConfig"]
HeartModel["JLTwsHealthHeartRateModel"]
end
subgraph sg_Device["BLE 设备"]
Device["TWS 耳机 (心率/血氧/计步传感器)"]
end
NavBar --> StatusStack
Table -->|"didSelectRowAt 分发"| VM
VM --> CfgSub
VM --> SensorSub
VM --> FeedSub
VM --> StepSub
VM --> HeartSub
CfgSub -->|"observe on MainScheduler"| HealthLabel
SensorSub -->|"observe on MainScheduler"| SensorLabel
StepSub -->|"observe on MainScheduler"| StepLabel
HeartSub -->|"observe on MainScheduler"| HeartLabel
FeedSub -->|"showToast"| UI["Toast 提示"]
VM -->|"调用 API"| Manager
Manager -->|"回调 (Delegate)"| VM
Manager --> CfgModel
Manager --> HeartModel
Manager <-->|"BLE 指令/数据"| Device
架构说明:
- UI 层采用典型的 UIKit 手写布局(
SnapKit约束),上部为statusStack状态展示区(4 个UILabel),下部为UITableView功能操作列表。两区块通过navigationView下方的约束衔接。 - ViewModel 层是数据可视化核心:
TwsHealthViewModel实现JLTwsHealthManagerDelegate协议,把 SDK 的异步回调转换成 5 个 RxSwiftSubject,形成「设备事件 → Subject → UI 订阅」的单向数据管道。每个 Subject 独立负责一类健康数据,互不阻塞。 - SDK 层由 JL_BLEKit 框架提供
JLTwsHealthManager及数据模型JLTwsHealthConfig、JLTwsHealthHeartRateModel,是健康数据采集能力的来源;UI 与 ViewModel 只依赖公开头文件,不接触 BLE 细节。 - 数据流方向:用户点击列表 → ViewModel 调用 Manager API → 设备返回 → Delegate 回调 → Subject 发射 → UI 主线程更新标签。整个过程为单向依赖,便于单元测试与替换实现。
Main Content
1. 界面布局(initUI)
TwsHealthViewController 继承自 BaseViewController,在 initUI() 中构建两个核心区域:
- 状态展示区:一个垂直
UIStackView(statusStack,间距 8),包含 4 个UILabel,均设置numberOfLines = 0与 14pt 系统字体,用于展示健康能力、传感器状态、实时步数、日常心率。 - 操作列表区:
UITableView(.insetGrouped样式),注册UITableViewCell,数据源为Item.allCases(13 项),每行带disclosureIndicator。
布局上,statusStack 顶部对齐 navigationView 底部(偏移 8),左右内边距 16;tableView 顶部对齐 statusStack 底部(偏移 8),其余方向填满父视图。这样保证状态区固定、操作列表滚动,长文本(如完整健康能力描述)不会挤压表格。
override func initUI() {
super.initUI()
navigationView.title = R.localStr.twsHealth()
statusStack.axis = .vertical
statusStack.spacing = 8
view.addSubview(statusStack)
[healthInfoLabel, sensorStatusLabel, realTimeStepLabel, dailyHeartLabel].forEach { lbl in
lbl.numberOfLines = 0
lbl.font = .systemFont(ofSize: 14)
statusStack.addArrangedSubview(lbl)
}
statusStack.snp.makeConstraints { make in
make.top.equalTo(navigationView.snp.bottom).offset(8)
make.left.right.equalToSuperview().inset(16)
}
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.top.equalTo(statusStack.snp.bottom).offset(8)
make.left.right.bottom.equalToSuperview()
}
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
bindVM()
}
设计意图:把「状态展示」与「操作入口」分离——上方是只读的实时数据看板,下方是交互式功能验证列表,两者解耦后,新增健康操作只需扩展 Item 枚举与 switch 分发,无需改动状态区。
Main Content(续)
2. 功能清单与操作分发(Item 枚举)
页面将全部健康操作建模为 Item: CaseIterable 枚举,共 13 项,每项提供本地化标题(通过 R.localStr 国际化资源)。枚举与 ViewModel 方法一一对应,表格点击后按 switch 分发:
| 枚举项 | 本地化标题(示意) | ViewModel 调用 |
|---|---|---|
readConfig | 读取健康能力 | vm.readHealthConfig() |
checkSensors | 检查传感器状态 | vm.checkSensorStatus() |
startHeart / cancelHeart | 开始/取消心率 | vm.startHeartRate() / vm.cancelHeartRate() |
startSpO2 / cancelSpO2 | 开始/取消血氧 | vm.startSpO2() / vm.cancelSpO2() |
startStep / cancelStep | 开始/取消计步 | vm.startStep() / vm.cancelStep() |
startDailyHeart / endDailyHeart | 开始/结束日常心率同步 | vm.startDailyHeartSync() / vm.endDailyHeartSync() |
queryRealStep | 查询实时步数 | vm.queryRealStep() |
loopRealStep | 设置步数轮询间隔 | vm.loopQuery(intervalSec:)(需弹窗输入) |
cancelLoopStep | 取消步数轮询 | vm.cancelLoopQuery() |
enum Item: CaseIterable {
case readConfig
case checkSensors
case startHeart
case cancelHeart
case startSpO2
case cancelSpO2
case startStep
case cancelStep
case startDailyHeart
case endDailyHeart
case queryRealStep
case loopRealStep
case cancelLoopStep
var title: String {
switch self {
case .readConfig: return R.localStr.readHealthCapability()
case .checkSensors: return R.localStr.checkSensorStatus()
case .startHeart: return R.localStr.startHeartRate()
case .cancelHeart: return R.localStr.cancelHeartRate()
case .startSpO2: return R.localStr.startBloodOxygen()
case .cancelSpO2: return R.localStr.cancelBloodOxygen()
case .startStep: return R.localStr.startStepCount()
case .cancelStep: return R.localStr.cancelStepCount()
case .startDailyHeart: return R.localStr.startDailyHeartSync()
case .endDailyHeart: return R.localStr.endDailyHeartSync()
case .queryRealStep: return R.localStr.queryRealTimeStep()
case .loopRealStep: return R.localStr.setStepPollingInterval()
case .cancelLoopStep: return R.localStr.cancelStepPolling()
}
}
}
列表点击后的分发逻辑如下——queryRealStep 直接查询,loopRealStep 则先弹出输入框让用户指定轮询间隔(推荐 300 秒内,示例 240 秒),再调用 loopQuery:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch items[indexPath.row] {
case .readConfig: vm.readHealthConfig()
case .checkSensors: vm.checkSensorStatus()
case .startHeart: vm.startHeartRate()
case .cancelHeart: vm.cancelHeartRate()
case .startSpO2: vm.startSpO2()
case .cancelSpO2: vm.cancelSpO2()
case .startStep: vm.startStep()
case .cancelStep: vm.cancelStep()
case .startDailyHeart: vm.startDailyHeartSync()
case .endDailyHeart: vm.endDailyHeartSync()
case .queryRealStep: vm.queryRealStep()
case .loopRealStep:
promptInterval { [weak self] sec in
self?.vm.loopQuery(intervalSec: sec)
}
case .cancelLoopStep: vm.cancelLoopQuery()
}
}
设计意图:CaseIterable + switch 的组合让「功能清单」成为单一事实来源——新增一项能力只需加一个枚举 case、一个本地化标题和一行分发,表格行数、标题、点击行为自动保持一致,避免列表数组与分发逻辑不同步的常见缺陷。
3. 数据绑定与可视化(bindVM)
数据可视化完全由 RxSwift 驱动。bindVM() 订阅 ViewModel 暴露的 5 个 BehaviorSubject,全部通过 observe(on: MainScheduler.instance) 切回主线程后再写 UI,从机制上保证不会出现后台线程直接触碰 UIKit 的崩溃:
private func bindVM() {
vm.healthConfigSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] cfg in
guard let self = self, let cfg = cfg else { return }
let msg = R.localStr.deviceHealthCapability() + ": " + "\(cfg)"
self.healthInfoLabel.text = msg
}).disposed(by: bag)
vm.sensorStatusSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] st in
guard let self = self, let st = st else { return }
let msg = "\(R.localStr.heartRate()): \(st.heart ? R.localStr.on() : R.localStr.off())\n\(R.localStr.spo2()): \(st.spO2 ? R.localStr.on() : R.localStr.off())\n\(R.localStr.stepCount()): \(st.step ? R.localStr.on() : R.localStr.off())\n\(R.localStr.inEar()): \(st.inEar ? R.localStr.insideTheEar() : R.localStr.outsideTheEar())"
self.sensorStatusLabel.text = R.localStr.sensorStatus() + ":\n" + msg
}).disposed(by: bag)
vm.opFeedbackSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] text in
self?.showToast(text)
}).disposed(by: bag)
vm.stepSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] step in
if let self = self {
let msg = R.localStr.realTimeStep() + ": " + R.localStr.stepCount() + "\(step)"
self.realTimeStepLabel.text = msg
}
}).disposed(by: bag)
vm.dailyHeartSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] model in
if let self = self {
let msg = R.localStr.dailyHeartRate() + ": " + "\(model)"
self.dailyHeartLabel.text = msg
}
}).disposed(by: bag)
}
各 Subject 的可视化语义:
- healthConfigSubject → healthInfoLabel:把
JLTwsHealthConfig的能力描述直接渲染为文本(\(cfg)依赖模型自定义CustomStringConvertible输出,具体格式定义在 JL_BLEKit 头文件中)。 - sensorStatusSubject → sensorStatusLabel:用多行文本可视化 4 个布尔状态(心率、血氧、计步开关 + 佩戴检测),
on/off、inside/outside全部走本地化资源。 - opFeedbackSubject → showToast:操作级反馈(如「开始心率成功」)以 Toast 形式提示,不复用状态标签,避免覆盖实时数据。
- stepSubject → realTimeStepLabel:实时步数为纯数值,直接拼接单位文本。
- dailyHeartSubject → dailyHeartLabel:日常心率同步结果整体输出
JLTwsHealthHeartRateModel的文本描述。
每个订阅都使用 [weak self] + guard let self 避免闭包持有控制器造成循环引用,这是 RxSwift 订阅的标准防护模式。
4. 轮询交互与提示组件
loopRealStep(步数轮询)需要用户输入间隔秒数,页面通过 UIAlertController 弹窗收集输入:数字键盘、占位符 example240(示例 240 秒)、提示「推荐 300 秒以内」,只有解析成功且 v > 0 才会回调完成闭包——非法输入被静默忽略,不会调用 SDK:
private func promptInterval(completion: @escaping (TimeInterval) -> Void) {
let alert = UIAlertController(title: R.localStr.setPollingInterval(), message: R.localStr.recommendedWithin300Seconds(), preferredStyle: .alert)
alert.addTextField { tf in
tf.keyboardType = .numberPad
tf.placeholder = R.localStr.example240()
}
alert.addAction(UIAlertAction(title: R.localStr.cancel(), style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: R.localStr.oK(), style: .default, handler: { _ in
if let text = alert.textFields?.first?.text, let v = TimeInterval(text), v > 0 {
completion(v)
}
}))
present(alert, animated: true)
}
此外,页面内置了自绘 showToast 组件(半透明黑底圆角标签 + 两段 UIView.animate 淡入淡出,1.5 秒后移除),不依赖第三方 Toast 库:
private func showToast(_ text: String) {
let label = UILabel()
label.text = text
label.textAlignment = .center
label.textColor = .white
label.backgroundColor = UIColor.black.withAlphaComponent(0.7)
label.numberOfLines = 0
label.layer.cornerRadius = 8
label.clipsToBounds = true
view.addSubview(label)
label.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20)
make.width.lessThanOrEqualTo(view.snp.width).multipliedBy(0.8)
}
UIView.animate(withDuration: 0.25, animations: {
label.alpha = 1
}) { _ in
UIView.animate(withDuration: 0.25, delay: 1.5, options: [], animations: {
label.alpha = 0
}) { _ in
label.removeFromSuperview()
}
}
}
Core Flow
一次完整的「查询实时步数」操作在系统中按如下时序流动。该时序是页面所有操作的通用模板:UI 事件 → ViewModel 转发 → SDK 指令 → BLE 设备 → Delegate 回调 → Subject 发射 → 主线程渲染:
sequenceDiagram
participant U as 用户
participant V as TwsHealthViewController
participant VM as TwsHealthViewModel
participant SDK as JLTwsHealthManager (JL_BLEKit)
participant D as TWS 设备
U->>V: 点击「查询实时步数」cell
V->>V: didSelectRowAt → deselectRow
V->>VM: vm.queryRealStep()
VM->>SDK: 调用实时步数查询 API
SDK->>D: 发送 BLE 读取指令
D-->>SDK: 返回步数数据
SDK-->>VM: delegate 回调 (step 数值)
VM->>VM: stepSubject.onNext(step)
VM-->>V: stepSubject 发射(后台线程)
V->>V: observe(on: MainScheduler) 切主线程
V->>V: realTimeStepLabel.text = 本地化拼接
V-->>U: 界面实时刷新步数
对于「设置步数轮询」操作,流程在进入 ViewModel 前多一步:didSelectRowAt 先调用 promptInterval 弹窗,用户输入秒数并确认后,loopQuery(intervalSec:) 才被调用;此后 SDK 按间隔持续回调,stepSubject 持续发射,UI 无需额外处理即自动刷新。
数据模型与 SDK 契约
健康界面的数据来源是 JL_BLEKit 框架公开的三个健康相关类型(头文件位于主工程 Frameworks 目录):
| 类型 | 头文件 | 职责 |
|---|---|---|
JLTwsHealthManager | JLTwsHealthManager.h | TWS 健康管理入口,提供心率/血氧/计步/日常心率等 API,并通过 JLTwsHealthManagerDelegate 回调结果 |
JLTwsHealthConfig | JLTwsHealthConfig.h | 设备健康能力配置(哪些传感器可用),渲染到 healthInfoLabel |
JLTwsHealthHeartRateModel | JLTwsHealthHeartRateModel.h | 日常心率数据模型,渲染到 dailyHeartLabel |
TwsHealthViewModel 声明为 JLTwsHealthManagerDelegate 的实现者,并暴露首个数据管道 healthConfigSubject:
class TwsHealthViewModel: NSObject, JLTwsHealthManagerDelegate {
let healthConfigSubject = BehaviorSubject<JLTwsHealthConfig?>(value: nil)
}
注:ViewModel 中
sensorStatusSubject、opFeedbackSubject、stepSubject、dailyHeartSubject以及各操作方法的完整实现位于同一文件后续部分,本次文档编写时未展开读取,具体签名以源码为准。
API Reference
TwsHealthViewController
公开交互入口(均为内部实现,供页面自身使用):
| 成员 | 类型/签名 | 说明 |
|---|---|---|
Item | enum Item: CaseIterable | 13 项健康操作清单,含本地化 title |
initUI() | override func initUI() | 构建状态区 + 列表区,并调用 bindVM() |
bindVM() | private func bindVM() | 订阅 ViewModel 的 5 个 Subject,主线程更新 UI |
tableView(_:didSelectRowAt:) | UITableViewDelegate 回调 | 按 Item 分发到对应 ViewModel 方法 |
promptInterval(completion:) | private func (TimeInterval) -> Void | 弹窗收集轮询间隔,v > 0 才回调 |
showToast(_:) | private func (String) | 自绘 Toast,1.5 秒后自动消失 |
TwsHealthViewModel
数据管道(已从源码确认):
healthConfigSubject: BehaviorSubject<JLTwsHealthConfig?>— 健康能力配置流,初始值nil。
页面调用的方法(依据 TwsHealthViewController 的调用点归纳,签名细节以 ViewModel 源码为准):readHealthConfig()、checkSensorStatus()、startHeartRate()、cancelHeartRate()、startSpO2()、cancelSpO2()、startStep()、cancelStep()、startDailyHeartSync()、endDailyHeartSync()、queryRealStep()、loopQuery(intervalSec:)、cancelLoopQuery()。
JLTwsHealthManagerDelegate(SDK 契约)
TwsHealthViewModel 遵循该协议以接收健康数据回调;协议方法把 SDK 异步结果写入对应 Subject。协议完整方法列表定义于 JLTwsHealthManager.h。
配置选项
| 选项 | 类型 | 默认/推荐值 | 说明 |
|---|---|---|---|
步数轮询间隔(loopRealStep) | TimeInterval | 推荐 ≤ 300 秒;示例 240 秒 | 由用户弹窗输入,必须 > 0,否则忽略 |
| 状态标签字体 | UIFont | .systemFont(ofSize: 14) | 4 个状态标签统一字号,numberOfLines = 0 允许多行 |
| Toast 展示时长 | TimeInterval | 1.5 秒(delay: 1.5) | 淡入 0.25s、停留后淡出 0.25s |
| 列表样式 | UITableView.Style | .insetGrouped | iOS 分组列表样式 |
以上选项均硬编码于 TwsHealthViewController.swift,未提供外部配置入口;开发者可直接修改常量或抽取为参数。
Failure Modes, Edge Cases & Concurrency
以下结论基于已读取的 TwsHealthViewController 源码;ViewModel/SDK 内部错误处理以对应源码为准。
- 线程安全(并发):所有 UI 更新都经过
observe(on: MainScheduler.instance),SDK 回调线程与主线程之间的数据传递完全由 RxSwift 调度器隔离。这是 Rx 模式相对直接回调方案的核心优势——Delegate 回调可能在任意队列触发,但 UI 永远只在主线程被触碰。 - 循环引用防护:所有订阅闭包均使用
[weak self],bindVM()的订阅由DisposeBag(bag)持有;控制器释放时bag随之释放,自动取消全部订阅,避免「控制器已销毁但 SDK 仍回调」导致的悬垂引用与内存泄漏。 - 初始值语义:
BehaviorSubject自带初始值(如healthConfigSubject初始为nil),订阅建立后立即回放最近值。UI 层用guard let cfg = cfg对nil做空值过滤,因此页面加载瞬间标签保持为空、不显示异常内容。 - 非法输入边界:轮询间隔弹窗要求
v > 0;0、负数、非数字文本会被静默丢弃(不弹错误提示、不调用 SDK)。推荐上限 300 秒仅作为文案提示,源码未做硬性上限校验——集成方若担心设备负载,需在 ViewModel 层补充校验。 - 设备能力缺失:页面假设设备支持对应传感器;若
JLTwsHealthConfig指示某项能力不可用,界面仍会渲染该能力描述文本,但操作是否报错取决于 SDK 实现。集成时建议先readHealthConfig()再按能力裁剪列表项。 - 操作反馈通道:异步操作(开始/取消心率等)的成功/失败通过
opFeedbackSubject以 Toast 呈现,与数据流(状态标签)分离——失败不会污染实时数据,但也不会自动恢复 UI 状态(如按钮态),属于演示级反馈。 - 重复操作竞态:列表允许用户快速连续点击(如重复
startHeart),页面未做互斥/防抖;若 SDK 不支持重入,可能出现重复指令。演示场景可接受,正式产品建议在 ViewModel 中加状态机保护。
Performance & Operational Notes
- 轮询频率控制:
loopQuery(intervalSec:)的间隔由用户指定,界面提示「推荐 300 秒以内」。过短的间隔会持续占用 BLE 通道、增加耳机功耗,运维/集成时应结合设备功耗预算选择间隔。 - 渲染开销:UI 更新仅为 4 个
UILabel的文本赋值,无列表刷新、无布局重算(约束固定),性能开销可忽略;即使步数轮询高频回调,主线程负载也极低。 - Toast 生命周期:
showToast每次创建新UILabel并在动画结束后removeFromSuperview;高频触发 Toast 时旧实例可能未完全移除(依赖动画 completion 回调),演示场景无碍。 - 本地化成本:所有文案走
R.localStr资源表,新增语言只需补充资源,无需改动代码逻辑。
Extension Points
- 新增健康操作:在
Item枚举加 case → 补title本地化 →didSelectRowAtswitch 中加一行调用 → ViewModel 增加对应方法与 Subject(如需展示数据)。四个触点协同扩展,结构清晰。 - 替换数据管道:UI 只依赖 ViewModel 的 Subject 契约;若换用非 JL_BLEKit 数据源,只需重写 ViewModel(保持同名 Subject 与回调方法),ViewController 完全不需要改动——这是「UI 与 SDK 解耦」的设计红利。
- 数据模型渲染:
JLTwsHealthConfig、JLTwsHealthHeartRateModel以\(model)字符串形式渲染,展示格式由模型的CustomStringConvertible决定;需要更精致的可视化(图表、仪表盘)时,可订阅同一 Subject 并替换为自定义视图组件。 - 国际化:
R.localStr资源表集中管理全部文案,是扩展多语言支持的标准入口。
Tests
健康界面位于 SDKTestHelper 演示工程(code/SDKTestHelper/SDKTestHelper/JLSDKTest/Controllers/DefaultSetViewControllers/TwsHealth/),该工程本质上承担 SDK 集成验证职责:TwsHealthViewController 的 13 项操作覆盖了健康 SDK 的读配置、传感器状态、心率、血氧、计步、日常心率、实时步数、轮询全部公开能力,可视为手动集成测试清单。未发现针对该页面的自动化单元测试文件;ViewModel 与 SDK 的交互可通过真机/模拟器 + 蓝牙设备手动验证。开发者可基于 TwsHealthViewModel 的 Subject 契约编写 Mock 驱动的单元测试(注入假 Delegate 回调即可断言 UI 文本)。
Related Links
- TwsHealthViewController.swift(界面与数据可视化实现)
- TwsHealthViewModel.swift(ViewModel 数据管道)
- JLTwsHealthManager.h(SDK 健康管理契约)
- JLTwsHealthConfig.h(健康能力配置模型)
- JLTwsHealthHeartRateModel.h(日常心率模型)
- 相关页面:表盘市场与云端健康服务接口(
DialMarketHttp.swift的/health/v1/api)、设备连接与 SDK 框架总览