示例功能
待办事项、首页、个人资料、推荐好友与权限引导——这些是供你替换的示例屏幕。
这些是应用的**示例(Example)**屏幕——它们接入了真实的仓储层,是可运行的功能,存在的 目的就是供你研究、复制,然后用你自己的产品替换掉。它们都不是承重结构:删掉待办事项, 应用照样能编译。每个屏幕都是对本文档其他页面所述模式的一次小而诚实的演示,请把它们当作 可直接复制的参考实现。
示例(Example) 与 可移除演示(Removable demo) 的区别:这里的屏幕是让你 替换的(你会构建自己的首页、自己的第一个功能);而功能演示 标签页是让你整体删除的。两者都非核心。
主壳(main shell)有四个标签页——首页、组件、功能演示、我的——外加一个侧边抽屉,
承载其余入口(上传、推荐、个人信息、账号与安全、通知)。待办、推荐、权限是被推入某个
标签页 NavigationStack 的屏幕,本身并不是标签页。
待办事项——首选参考功能
Features/Todos/ 是最该先复制的一个。它是一条端到端、"正确姿势"实现的完整 CRUD 功能,
因此贯穿了 数据层所述的每一层:Todo 模型 →
TodosRemoteDataSource → TodosRepository → TodosViewModel → TodosView。
它演示了:
- 完整 CRUD——针对 Supabase
todos表的新增、就地编辑(点按原位编辑,保存/取消)、 切换完成、删除,以及重新排序(上移/下移)。 - 筛选标签——全部 / 进行中 / 已完成,配以随筛选变化的空状态
(
todos.empty/todos.noActive/todos.noCompleted)。 - 清除已完成的确认——一个破坏性批量操作,前置一个确认对话框。
- 带回滚的乐观更新——切换、删除、重排、清除已完成都会立即改动本地状态,再与服务器 对账;失败时恢复到先前的列表并给出内联错误。这就是离线/延迟的处理方式:UI 从不因网络 而阻塞。
UiState<[Todo]>——视图对.idle/.loading/.data/.error分支求值,据此渲染 反馈三件套,并支持下拉刷新。- 未登录状态——没有会话时,待办展示登录提示而非空列表(反正 RLS 也会拒绝该查询)。
// 乐观切换:先本地翻转,再与服务器对账,失败则回滚。
func toggle(id: UUID) {
guard let existing = todos.first(where: { $0.id == id }) else { return }
var optimistic = existing
optimistic.completed.toggle()
upsert(optimistic)
Task {
await runInlineMutation {
let updated = try await repository.toggle(id: id)
upsert(updated)
} onError: {
self.upsert(existing) // 失败时恢复
}
}
}把待办复制成你自己的功能,正是 数据层 → 新增你自己的功能 中的走查。替换模型、表和仓储,保留视图模型的骨架。
首页——产品落地页
Features/Home/HomeView.swift 刻意不是通用的应用首页——在模板里它是面向"正在
评估这个 starter 的人"的产品推介页。它是最适合掏空并按你自己应用重建的屏幕。它由三部分
组成:
- 主视觉 + 技术栈徽章——产品标题、价值主张,以及一组会换行的
FlowLayout技术徽章 (SwiftUI、Supabase、RevenueCat…)。徽章刻意不做本地化(与不翻译 "Supabase" 同理)。 - 随状态变化的授权卡片——一个小而可测的状态机
(
HomeLicenseState.from(isConfigured:isSignedIn:isEntitled:))在"购买"CTA 与 "已拥有"卡片之间做选择。它读取的是env.entitlements.isEntitled——即共享的subscriptions读模型,而非 RevenueCat SDK。没有 RevenueCat 密钥时,它退回到"购买"CTA,而不是显示一个坏掉的"已拥有"状态。 - 快捷操作——一个跳转到组件画廊、待办和上传示例的网格。导航是回调驱动的
(
onOpenTodos、onOpenShowcase…),因此跨标签跳转由外壳掌管,首页也就与可移除的 Showcase 目录解耦。
替换首页时值得保留的模式是回调驱动的导航:视图接收 () -> Void 闭包,而不是反向
伸手去够外壳——正是这一点让 Showcase 能被删除而不必改动首页。
个人资料——我的、个人信息、账号与安全
Features/Profile/ 是账号中心,从我的标签页进入。它按关注点拆成三个屏幕:
| 屏幕 | 作用 |
|---|---|
MeView | 账号落地页:头像 + 显示名(ProfileDisplay)、进入下列各分区的入口,以及退出登录。未登录时显示登录 CTA。 |
PersonalInfoView | 编辑可修改的资料字段(显示名等)并查看只读的账号信息;经 ProfileRepository 写入。 |
AccountSecurityView | 敏感操作屏幕——更换邮箱(OTP 确认)、修改密码、切换生物识别应用锁、退出其他会话、管理订阅、注销账号。 |
每个破坏性或凭证类操作都经由 AuthRepository,并前置一个
确认对话框(AccountSecurityViewModel.Confirmation)。注销账号会调用
delete-account Edge Function,它会在删除认证用户之前先移除该用户的数据行与存储对象——
Apple 要求提供应用内账号注销,务必保持接线(见
App Store 发布)。
推荐好友
Features/Refer/ReferView.swift 是一个极简的分享流程:一个 ShareLink 包裹由
ReferralLinkProvider 生成的邀请链接。当设置了
LINKING_DOMAIN 时,链接为 https://<LINKING_DOMAIN>/,
否则退回 soar:///——与其他所有服务一样,在没有任何密钥时也能优雅降级。
它还与评分触发挂钩:当分享面板被打开、应用回到 .active 时,它会通过
env.rating.recordAction(...) 记录一次 referral_shared 行为,这是喂给 StoreKit
评价弹窗的信号之一。
权限引导
Features/Permissions/PermissionsView.swift 是从"我的"进入的引导(priming)
屏幕——它在原生系统对话框出现之前先说明每项权限的益处,这能切实提升授权率。它覆盖四个
PermissionId:notifications、photos、camera 和 tracking(ATT)。
每张卡片的行为:
- 尚未请求 → 一个触发原生弹窗的按钮。
- 已拒绝或受限(
requiresSettings)→ 按钮切换为打开设置,因为 iOS 不会再次弹出 用户已经关掉的对话框。 - 状态在出现时经
PermissionsViewModel.refresh()刷新。
ATT(App 跟踪透明度)管辖的是第三方、与 IDFA 关联的跟踪——而非第一方分析。PostHog 不依赖 ATT 授权;那张卡片是为将来的归因工作预留的。不要把 分析门槛压在它上面。
替换这些屏幕
开始构建你自己的应用时,一个务实的顺序:
- 把待办当作模板,复制它的分层骨架来构建你的第一个真实功能,然后删掉待办。
- 掏空首页——用你自己的落地内容替换主视觉、授权卡片和快捷操作;保留回调驱动的 导航骨架。
- 个人资料和权限基本原样保留——它们是你无论如何都会想要的通用账号基建(尤其是 Apple 要求的账号注销)。
- 当你不再需要演示目录时,删掉 Showcase。
