SceneKit + Haptic Feedback:我做了一个 3D 存钱罐 App 的技术复盘
起因:一个关于"坚持不下来"的需求
上个月帮朋友看她手机里的攒钱工具,全是表格、进度条、折线图。她说每次打开都像在看 KPI,存了两周就卸载了。这件事让我开始琢磨一个方向:攒钱的核心痛点其实不是"不会算",而是"坚持不下来"。
我想做的东西很简单,一个纯粹管"存了多少"的 App,不碰支出、不做预算,只在每次存钱的时候给用户一点正反馈。就像小时候往储蓄罐里扔硬币,听到叮当一声,觉得今天干了件好事。这就是「聚沙攒钱」这个项目的起点,目标用户是想攒钱但总半途而废的大学生和职场新人。
技术选型:为什么从 SwiftUI 3D 切到了 SceneKit
App 有三种攒钱模式:愿望模式(为某个目标攒)、定投模式(模拟定期定额)、自由模式(想存就存)。但不管哪种模式,存入动作的核心体验是统一的——一个 3D 存钱罐 + 硬币落入动效 + Haptic 触感反馈。
最初我用 SwiftUI 的 rotation3DEffect 做罐子旋转,视觉上还行,但硬币落入的物理效果怎么调都不自然。后来切到 SceneKit,用 SCNPhysicsBody 给硬币加重力和碰撞检测,效果立刻对了。这里分享一下核心的硬币投入实现:
func dropCoin(into jarNode: SCNNode) {
let coin = SCNCylinder(radius: 0.3, height: 0.05)
let coinNode = SCNNode(geometry: coin)
coinNode.position = SCNVector3(0, 2.5, 0)
let body = SCNPhysicsBody(type: .dynamic, shape: nil)
body.mass = 0.8
body.restitution = 0.3
coinNode.physicsBody = body
jarNode.parent?.addChildNode(coinNode)
let generator = UIImpactFeedbackGenerator(style: .medium)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
generator.impactOccurred()
}
}
这段代码里有几个参数是反复试出来的。restitution 设成 0.3,太高硬币弹得像乒乓球,太低又没有"叮"的感觉。触感反馈的延迟 0.6 秒是为了配合硬币落底的时间,但后来发现不同机型上表现有差异,最终改成监听 physicsWorld(_:didBegin:) 碰撞回调来触发 Haptic,时机才真正准确。这个坑花了我大概两个晚上。
隐私方案:不碰服务器
攒钱数据算敏感信息,我不想自己搭后端,所以数据全部走 iCloud CloudKit 私有库同步,服务端拿不到明文。App 内加了 Face ID 解锁和金额遮罩,通知中心 Widget 上只显示进度百分比,不显示具体数字。
用 LAContext 做生物认证时有个细节值得注意:如果用户没设置 Face ID,必须 fallback 到设备密码,不能直接把功能锁死。这个点我在审核时被 Apple 打回来过一次,提醒各位别踩同样的坑。
上线后的数据表现
上线两周,后台看到一个让我比较满意的数据:用户平均每天打开 1.4 次,七日留存 58%。同类攒钱工具普遍七日留存在 30% 左右,"仪式感"这个方向看起来是对的。有用户在评论区说"每天睡前存一笔已经变成习惯了",这基本就是我设计时想要的使用场景。
和市面上记账类 App 的区别在于,聚沙攒钱完全不管你花了多少,只关心"攒了多少"。这个减法做得比较狠,但对目标用户来说,少一个压力来源反而更容易坚持。
一点经验
如果你也在做需要"用户重复使用某个动作"的工具型 App,触感反馈 + 视觉正反馈这套组合值得尝试。不需要多复杂的动效,关键是让用户每次操作都能感知到"我做了一件事,它给了我回应"。SceneKit 在这类轻量 3D 场景下完全够用,不需要上 Unity。
App Store 搜索「聚沙攒钱」可以下载体验,欢迎同行交流实现细节。

浙公网安备 33010602011771号