示例功能

待办事项、首页、个人资料、推荐好友与权限引导——这些是供你替换的示例屏幕。

这些是应用的**示例(Example)**屏幕——它们接入了真实的仓储层,是可运行的功能,存在的 目的就是供你研究、复制,然后用你自己的产品替换掉。它们都不是承重结构:删掉待办事项, 应用照样能编译。每个屏幕都是对本文档其他页面所述模式的一次小而诚实的演示,请把它们当作 可直接复制的参考实现。

示例(Example)可移除演示(Removable demo) 的区别:这里的屏幕是让你 替换的(你会构建自己的首页、自己的第一个功能);而功能演示 标签页是让你整体删除的。两者都非核心。

主壳(main shell)有四个标签页——首页、组件、功能演示、我的——外加一个侧边抽屉, 承载其余入口(上传、推荐、个人信息、账号与安全、通知)。待办、推荐、权限是被推入某个 标签页 NavigationStack 的屏幕,本身并不是标签页。

待办事项——首选参考功能

Features/Todos/ 是最该先复制的一个。它是一条端到端、"正确姿势"实现的完整 CRUD 功能, 因此贯穿了 数据层所述的每一层:Todo 模型 → TodosRemoteDataSourceTodosRepositoryTodosViewModelTodosView

它演示了:

  • 完整 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,而不是显示一个坏掉的"已拥有"状态。
  • 快捷操作——一个跳转到组件画廊、待办和上传示例的网格。导航是回调驱动的 (onOpenTodosonOpenShowcase…),因此跨标签跳转由外壳掌管,首页也就与可移除的 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) 屏幕——它在原生系统对话框出现之前先说明每项权限的益处,这能切实提升授权率。它覆盖四个 PermissionIdnotificationsphotoscameratracking(ATT)。

每张卡片的行为:

  • 尚未请求 → 一个触发原生弹窗的按钮。
  • 已拒绝或受限(requiresSettings)→ 按钮切换为打开设置,因为 iOS 不会再次弹出 用户已经关掉的对话框。
  • 状态在出现时经 PermissionsViewModel.refresh() 刷新。

ATT(App 跟踪透明度)管辖的是第三方、与 IDFA 关联的跟踪——而非第一方分析。PostHog 依赖 ATT 授权;那张卡片是为将来的归因工作预留的。不要把 分析门槛压在它上面。

替换这些屏幕

开始构建你自己的应用时,一个务实的顺序:

  1. 把待办当作模板,复制它的分层骨架来构建你的第一个真实功能,然后删掉待办。
  2. 掏空首页——用你自己的落地内容替换主视觉、授权卡片和快捷操作;保留回调驱动的 导航骨架。
  3. 个人资料和权限基本原样保留——它们是你无论如何都会想要的通用账号基建(尤其是 Apple 要求的账号注销)。
  4. 当你不再需要演示目录时,删掉 Showcase

相关页面

On this page