利益相关声明:作者与文中产品有直接的利益相关(开发者、自家产品等)

我用 Vibe Coding 的方式,开发了一个 iOS 相册 App。它只有 700 KB 大小,功能简单到一句话就能说完——用手势浏览和整理照片。上下滑切换,右滑随机跳转,左滑删除,这就是主要功能。

 

Shuffle — 极简相册

 

但为了这 700 KB,我烧掉了 7 亿个 Token。

 

如果你不知道 Token 是什么——它是大语言模型处理文本的最小单位,约等于 3 / 4 个英文单词,或半个中文字。7 亿 Token 意味着我和 AI 之间进行了一场大规模的对话,对话的内容是:怎么写好这个 App。

 

你可能会问,值得吗?

 

其实 Shuffle 的想法很简单:我手机里有几万张照片,但 iOS 自带的照片 App 太笨重了。我想要一种更轻、更沉浸的浏览方式:像幻灯片一样看照片、像丢东西一样删照片,再来一点随机的不期而遇。而且,我需要手势操作和肌肉记忆,不是按钮、菜单、确认框。

 

所以 Shuffle 由手势驱动。上下滑浏览,右滑随机跳转,左滑删除。主界面没有工具栏,没有分享按钮,没有任何文字。只有一张照片、毛玻璃背景和五个圆形按钮——而且它们要等你滑动时才会出现。

 

手势删除

 

这听上去像是一个周末项目。实际上它花了一个月。

 

因为要做到“符合直觉”,意味着背后什么都要处理得滴水不漏。


120 赫兹的 Cover Flow

 

Shuffle 交互核心是 Cover Flow——中间照片占屏幕 92%,旁边两张缩到 33%再淡出。它的滚动不是机械的分页,而是 momentum 驱动的连续物理模拟:手指滑多远,Flow 就滚多远;手指离开,速度决定最终落点。

 

但在 iOS 26 SDK 上有个坑:SwiftUI 的 @State 批次更新被锁死在 60 帧。ProMotion 屏幕能跑 120 帧,但框架不给。所以我挂了一个 CADisplayLink,每次屏幕刷新时发一个 tick,绑定到视图的最外层 overlay——一个不可见的透明层,唯一的任务是强迫 SwiftUI 以 120 Hz 重新计算渲染树。换句话说,就是不断提醒 SwiftUI 这一帧也该重新算。

 

这就是为什么如此丝滑——因为每次响应都是满帧。

 

手势冲突

 

这可能是整个 App 最复杂的部分。一个手势同时承担三个职责:

  •  纯垂直滑动:Cover Flow 切换照片
  • 水平滑动:淡入淡出侧边按钮
  • 水平滑动 + 垂直滑动:手势选择侧边按钮

 

手势选择

 

方向检测在 onChanged 里实时判断,超过特定像素后就锁定轴差。方向锁定后,还有一个垂直基线的设计——锁的瞬间记录当前 y 坐标作为锚点,后续所有垂直偏移相对这个基线计算。不做这个,手势的微动会让按钮跳闪。

 

还有另一个坑:左右侧按钮如果用同一个 selectedButtonIndex,左滑选中的按钮会在右滑时短暂闪现。解决方式是拆成左右独立管理——简单,但发现它花了一晚上。

 

GIF 的幽灵

 

GIF 检测也麻烦。你不能在加载大图的时候判断它是 GIF,得另外针对当前照片发起一个 requestImageDataAndOrientation,检查 UTI 是不是 com.compuserve.gif。检测到了,用 CGImageSource 逐帧解码,存下帧数组和总时长。

 

但渲染 GIF 有一个隐藏的坑:Cover Flow 的静态图是用 .drawingGroup() 全体光栅化渲染的——这是为了在滑动时保证帧率。但光栅化会把前面的帧当残影留在图层里。解决方式是让 GIF 动画独立于 drawingGroup 之外,作为单独的 overlay 存在。只有当 Flow 完全静止时才显示,滑动时隐藏。

  

为了配合这个“静止检测”,你需要区分两种状态:flowPosition 是否落在整数上(即当前指向一张确定照片),以及是否正在拖拽。逻辑不复杂,但产出这套判断的对话轮次,不下 20 轮。

 

软删除与跨域安全

 

App 的删除:先软删除,移入待删除列表,你可以在这里恢复单张、批量恢复、或永久删除。

 

这时候如果你进入了特定一个相簿,事情就微妙了——你在「2024 年 6 月」里删掉了两张照片,然后切到「个人收藏」,那两张待删照片应该还在待删除列表里,但不能被你从「个人收藏」恢复——因为它们不属于当前列表。

 

所以跨域隔离逻辑考虑了五个边界情况,注释比代码还长。


消耗的 7 亿 Token

 

我不确定这个数字到底意味着什么。因为实际走的是 DeepSeek API,所以真实花费只有不到 70 元。

 

但 7 亿 Token 是一个很好的记录。它包含:

  • 那些被推翻的设计、架构
  • 尝试绕过 SwiftUI 的 60 帧限制
  • 各种边界情况
  • 148 项测试清单,覆盖权限、Cover Flow、手势、删除、作用域、GIF、视频、iCloud、前后台切换、本地化
  • 等等

 

每一次迭代都对应几十到几百条对话——几乎所有的问题都是“这个效果不够自然”和“有没有更好的做法”,或是修复不成熟解决方案引入的新问题。你需要大量对话才能让 AI 产出真正好的东西。

 

这是新型“手工”软件开发。大量计算、大量试错、大量推敲——只是不再是手敲代码,而是手敲提示词。工具变成了 AI,但审美、取舍、反复试错,仍然是人的。

 

最后得到的,也是一段 7 亿 Token 的手工劳动。

 

它不是为了证明 AI 能替我写 App,更像是在证明:当我知道自己想要什么时,AI 可以陪我把那个东西磨出来。


700KB 里的诚实

 

到今天,Shuffle 的所有源代码加起来是 2430 行,上架后约 700 KB。没有第三方框架,没有埋点,没有广告。它做的工作就是读图、显示、删图——就像一个幻灯机。

 

广告狂人 — 柯达放映机

 

Shuffle 最终商业上成不成功,我还不知道。

 

但有一点我很确定:我想用 Shuffle 翻照片。

 

这就够了。

 

(以上内容由 AI 生成,经本人编辑)