iOS拨轮交互实现:UIScrollView吸附+Haptic Feedback,3秒录入血压数据
起因:给我爸做一个能用的血压记录工具
去年我爸确诊高血压,医生让每天记录。试了七八个 App,要么界面复杂老人不会用,要么每次录入要点太多下。有一次他直接拿纸笔记了,复诊时掏出一张皱巴巴的纸条递给医生。
我当时就决定自己做一个。做出来叫「健康手账」,iOS 上架快半年了,版本 1.1。核心就解决两件事:录入太慢、数据到了手机里医生看不到。
拨轮交互:UIScrollView + 吸附 + Haptic Feedback
这是我花时间最多的地方。数字键盘录入血压太慢了——高压、低压、心率三个值,每个都要点三四下,老人容易按错还得删。
我的方案是模拟物理转盘的阻尼感,用 UIScrollView 做环形滚动区域,通过自定义吸附逻辑实现「转到哪停到哪」:
funcscrollViewWillEndDragging(_scrollView:UIScrollView,withVelocity velocity:CGPoint,targetContentOffset:UnsafeMutablePointer<CGPoint>){letcellHeight:CGFloat=44.0lettargetY=targetContentOffset.pointee.yletsnappedY=round(targetY/cellHeight)*cellHeight targetContentOffset.pointee.y=snappedY// 触觉反馈,模拟刻度感UIImpactFeedbackGenerator(style:.light).impactOccurred()}``` 加了HapticFeedback之后,转动时有轻微震动,我爸说「跟拧收音机一样」。实测录一条血压(高压+低压+心率)大概3秒,比键盘快3-4倍。 试过三种方案才定下来:UIPickerView太丑定制性差;CollectionView横向滚动手感不对;最后用竖向ScrollView+吸附才满意。中间删了两千行代码。 ##PDF就医报告:UIGraphicsPDFRenderer+自动分页 这个功能的场景很具体——我爸每次复诊,医生问「最近血压怎么样」,他就说「还行」。我想让他直接递一张纸给医生,上面有趋势图和表格。 技术上用UIGraphicsPDFRenderer渲染,难点在分页——数据多了表格要自动换页,行不能从中间截断。核心判断逻辑大概是这样: ```swiftfuncrenderTableRows(_rows:[RowData],incontext:UIGraphicsPDFRendererContext){varcurrentY:CGFloat=pageMarginTopforrowinrows{letrowHeight=calculateHeight(for:row)// 剩余空间不够一整行,强制换页ifcurrentY+rowHeight>pageHeight-pageMarginBottom{context.beginPage()currentY=pageMarginTopdrawTableHeader(at:¤tY)// 新页重绘表头}drawRow(row,at:¤tY)}}``` 关键是换页后要重绘表头,不然医生翻到第二页看不懂列代表什么。上次复诊医生扫了一眼说「这个整理得清楚」,算是验证了方向。 ## 多人档案:CoreData用 parent entity 隔离数据 支持建多个档案,我手机里有三个:我爸、我妈、我自己。 数据层用CoreData,多档案隔离的方式是在 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,你们会选CoreData还是SQLite(比如GRDB/FMDB)? 我选CoreData主要因为和NSFetchedResultsController配合做列表刷新很省事,iCloud 同步将来也有现成方案。但也遇到了烦心事——轻量级迁移(lightweight migration)在加新 entity 时没问题,改字段类型就容易翻车,有一次测试阶段数据全丢了。如果用SQLite自己管 schema 版本,至少迁移逻辑是透明可控的。 你们怎么选的?有没有踩过类似的坑?