ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SwiftUI + SwiftData 实战:从 0 开发一款情绪日记与习惯打卡 App

SwiftUI + SwiftData 实战:从 0 开发一款情绪日记与习惯打卡 App 摘要本文以「心晴手记」为例拆解一款原生 iOS App 如何使用 SwiftUI、SwiftData、Swift Charts 和 UserNotifications完成每日情绪记录、习惯打卡、历史回顾、趋势统计与数据导出。重点不是堆砌代码而是分享从数据模型到产品闭环的实现思路。关键词SwiftUI、SwiftData、iOS 开发、Swift Charts、本地存储、情绪日记、习惯打卡很多 SwiftUI 教程会教我们做一个 Todo List。但当项目从“能增删一条数据”走向真正可使用的 App问题会迅速变多如何保证每天只有一条记录历史数据怎样按天聚合习惯归档后旧打卡要不要保留统计图表如何只读取指定日期范围用户又该怎样导出自己的数据为了把这些问题走完整我开发了心晴手记MoodMemoir——一款本地优先的情绪日记与习惯打卡 App。本文不贴满整屏业务代码而是拆解其中最值得复用的设计。一、先定义产品闭环再开始写 View这个 App 的核心链路只有五步选择今日心情 ↓ 添加情境标签和一句话日记 ↓ 完成今日习惯打卡 ↓ 在日历中回看历史 ↓ 查看 7 天 / 30 天趋势围绕这条链路第一版的数据实体也被控制在三个实体作用关键字段MoodEntry保存某一天的心情与日记日期、日期标识、心情、正文、标签Habit定义用户的习惯名称、图标、颜色、启用状态HabitCompletion保存某个习惯在某天的完成状态习惯 ID、日期、唯一键、完成状态先把数据关系画清楚可以避免后期为了一个统计页面反复修改模型。二、如何保证“每天只有一条心情记录”只保存Date并不够。同一天的上午 8 点和晚上 10 点是两个不同时间直接比较很容易产生重复记录。我的做法是给每条记录保存一个按本地日历生成的日期标识例如letdayIdentifierCalendar.current.moodBloomDayIdentifier(for:.now)保存时先查找当天记录iflettodayEntry{todayEntry.update(date:.now,mood:selectedMood,note:journalNote,tags:MoodTags.sortedStoredTags(from:selectedTags))}else{modelContext.insert(MoodEntry(date:.now,mood:selectedMood,note:journalNote,tags:MoodTags.sortedStoredTags(from:selectedTags)))}这样“再次保存”会变成更新而不是多插入一条数据。这个思路同样适用于每日体重、睡眠记录、学习总结等按天去重的场景。三、习惯与打卡为什么要拆成两个模型如果把“今天是否完成”直接写进Habit第二天状态应该如何重置过去的数据又如何保留因此习惯定义和每日完成记录必须分开Habit ├── 阅读 ├── 运动 └── 早睡 HabitCompletion ├── 阅读 2026-08-01 已完成 ├── 阅读 2026-08-02 未完成 └── 运动 2026-08-02 已完成每条完成记录使用“习惯 ID 日期”生成唯一键查询当天状态时无需遍历复杂对象关系。习惯归档后不再出现在今日列表但历史完成记录仍然可以被保留只有用户明确删除习惯时才同时处理相关数据。四、SwiftData 查询不应承担全部统计工作SwiftData 的Query很适合让界面随本地数据自动刷新Query(sort:\MoodEntry.date,order:.reverse)privatevarmoodEntries:[MoodEntry]但在数据量较小的本地 App 中7 天和 30 天统计可以先保持简单计算时间范围过滤范围内的记录按心情类型、标签或习惯聚合把结果转换成适合图表的数据结构。例如心情分布的核心逻辑可以概括为letstatsMoodType.allCases.map{moodinMoodCountData(mood:mood,count:entriesInRange.filter{$0.moodmood}.count)}不要让图表 View 自己理解数据库。先生成稳定的展示模型再交给 Swift Charts界面会清晰很多。五、Swift Charts 的价值不只是“画图”统计页面最危险的不是图表不好看而是文案过度解释。心情记录不等于医学数据所以 App 只展示最近记录了多少天哪种心情出现次数较多哪些标签更常见各习惯的完成率。它不会根据一段时间的记录对用户做心理诊断。技术能够计算什么和产品应该表达什么是两件不同的事。六、本地提醒应该在用户开启时再申请权限首次启动就弹通知权限用户通常不知道为什么要同意。更合理的流程是用户打开“每日提醒” ↓ 解释提醒用途 ↓ 请求系统通知权限 ↓ 按用户选择的时间创建本地通知关闭开关时立即取消待发送通知修改时间时先清理旧通知再重新安排。这既符合用户预期也让权限请求更有上下文。七、数据导出是本地 App 不该忽略的能力“数据保存在本地”不意味着数据应该被锁在 App 里。心晴手记提供两种导出JSON包含情绪、日记、习惯和打卡记录适合完整备份CSV包含心情与日记记录适合用表格查看。文件生成后通过 iOS Share Sheet 交给用户自行保存。这一步会显著提升用户对本地优先产品的信任也为未来的数据迁移留下空间。八、一个可上线的 SwiftUI App还需要哪些细节业务页面完成后还有一批容易被忽略的工作空状态与错误状态深色模式与不同尺寸 iPhone键盘收起与输入焦点Face ID / Touch ID 应用锁隐私政策与支持入口多语言本地化删除数据前的二次确认App Store 截图和审核说明。真正的产品开发往往有一半时间花在这些“不属于主功能却决定能不能交付”的细节上。写在最后SwiftUI 与 SwiftData 已经足以支撑一款结构完整的本地 App。真正困难的不是 API而是能否把数据模型、交互路径、隐私边界和上架要求同时考虑清楚。心晴手记目前没有后台、账号和第三方分析核心功能全部在设备本地完成。后续我还会继续分享它的架构取舍与 App Store 上线过程。如果你正在学习 SwiftUI不妨把练习目标从“做一个页面”升级为“完成一条可以真实使用的产品闭环”。体验入口下载或申请体验心晴手记
返回列表