起因:给我爸做一个能用的血压记录工具
去年我爸确诊高血压,医生让每天记录。试了七八个 App,要么界面复杂老人不会用,要么每次录入要点太多下。有一次他直接拿纸笔记了,复诊时掏出一张皱巴巴的纸条递给医生。
我当时就决定自己做一个。做出来叫「健康手账」,iOS 上架快半年了,版本 1.1。核心就解决两件事:录入太慢、数据到了手机里医生看不到。
拨轮交互:UIScrollView + 吸附 + Haptic Feedback
这是我花时间最多的地方。数字键盘录入血压太慢了——高压、低压、心率三个值,每个都要点三四下,老人容易按错还得删。
我的方案是模拟物理转盘的阻尼感,用 UIScrollView 做环形滚动区域,通过自定义吸附逻辑实现「转到哪停到哪」:
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let cellHeight: CGFloat = 44.0
let targetY = targetContentOffset.pointee.y
let snappedY = round(targetY / cellHeight) * cellHeight
targetContentOffset.pointee.y = snappedY
// 触觉反馈,模拟刻度感
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
```
加了 Haptic Feedback 之后,转动时有轻微震动,我爸说「跟拧收音机一样」。实测录一条血压(高压+低压+心率)大概 3 秒,比键盘快 3-4 倍。
试过三种方案才定下来:UIPickerView 太丑定制性差;CollectionView 横向滚动手感不对;最后用竖向 ScrollView+吸附才满意。中间删了两千行代码。
## PDF 就医报告:UIGraphicsPDFRenderer + 自动分页
这个功能的场景很具体——我爸每次复诊,医生问「最近血压怎么样」,他就说「还行」。我想让他直接递一张纸给医生,上面有趋势图和表格。
技术上用 UIGraphicsPDFRenderer 渲染,难点在分页——数据多了表格要自动换页,行不能从中间截断。核心判断逻辑大概是这样:
```swift
func renderTableRows(_ rows: [RowData], in context: UIGraphicsPDFRendererContext) {
var currentY: CGFloat = pageMarginTop
for row in rows {
let rowHeight = calculateHeight(for: row)
// 剩余空间不够一整行,强制换页
if currentY + rowHeight > pageHeight - pageMarginBottom {
context.beginPage()
currentY = pageMarginTop
drawTableHeader(at: ¤tY) // 新页重绘表头
}
drawRow(row, at: ¤tY)
}
}
```
关键是换页后要重绘表头,不然医生翻到第二页看不懂列代表什么。上次复诊医生扫了一眼说「这个整理得清楚」,算是验证了方向。
## 多人档案:Core Data 用 parent entity 隔离数据
支持建多个档案,我手机里有三个:我爸、我妈、我自己。
数据层用 Core Data,多档案隔离的方式是在 schema 里加了一个 `Profile` entity 作为 parent,所有的 `BloodPressureRecord`、`WeightRecord`、`StatusStamp` 都通过 relationship 挂在某个 Profile 下面。查询的时候用 NSPredicate 按 profile 过滤:
`NSPredicate(format: "profile == %@", currentProfile)`
没用多个 persistent store 的方案——太重了,而且将来如果做导出功能,单个 SQLite 文件比较好处理。
所有数据存在本地,没有服务器没有账号。健康数据太敏感,我不想碰用户隐私,也不想维护后端。代价是没法跨设备同步,但对于「每天固定用一台手机记录」的场景够用。
## 状态印章:离散事件和时间序列数据的关联
除了数值记录,做了一组状态印章——吃药、运动、饮酒、熬夜。每天点一下,像盖章。
数据模型上,`StatusStamp` 是独立 entity,字段很简单:`type`(枚举)、`timestamp`、`profile`。它和 `BloodPressureRecord` 没有直接 relationship,关联展示是在趋势图渲染时按时间轴做的——取当天的所有 stamp,在折线图对应日期的 X 轴位置画小图标。
这样做的好处是 stamp 和数值记录完全解耦,将来加新的 stamp 类型不需要动血压/体重的 schema。坏处是查询「吃药日 vs 未吃药日的血压均值对比」时要在内存里做 join,数据量大了可能有性能问题——不过对于个人用户,几年的数据量也就几千条,目前没遇到瓶颈。
## 目前状态
- 2024 年上架,当前版本 1.1
- - 买断制,不搞订阅。慢病记录这种东西用户可能用好几年,订阅不公平
- - 下载量说实话不多,没怎么推广过
- - 日活不高但留存还行——毕竟是每天都要用的工具
- - 鸿蒙版在计划中,ArkUI 写数据录入界面应该挺合适
## 一个想讨论的问题
做这种长期记录类工具 App,你们会选 Core Data 还是 SQLite(比如 GRDB/FMDB)?
我选 Core Data 主要因为和 NSFetchedResultsController 配合做列表刷新很省事,iCloud 同步将来也有现成方案。但也遇到了烦心事——轻量级迁移(lightweight migration)在加新 entity 时没问题,改字段类型就容易翻车,有一次测试阶段数据全丢了。如果用 SQLite 自己管 schema 版本,至少迁移逻辑是透明可控的。
你们怎么选的?有没有踩过类似的坑?
675



被折叠的 条评论
为什么被折叠?



