卡片漂流簿(Postcard Journey)iOS 原生应用
Codex 完整开发规格与执行计划
文档版本:1.1
制定日期:2026-07-26
目标平台:iPhone / iOS 17 及以上
开发方式:SwiftUI 原生应用 + SwiftData + Apple 原生框架
设计语言:Apple 原生 UI;iOS 26 及以上使用原生 Liquid Glass
产品属性:离线、本地优先、无账号、无主动网络请求、中文与英文双语
0. Codex 执行总则
本文件是项目的唯一产品和工程规格来源。Codex 在实现过程中必须遵守以下规则:
- 先阅读本文件和仓库根目录的
AGENTS.md,再修改任何代码。 - 不得擅自增加用户未要求的业务字段。
- 不得加入邮票、邮资、币种、邮戳、收藏估值、完整地址、联系人姓名、评分或社交功能。
- 不得调用 Postcrossing 接口,不得登录 Postcrossing,不得抓取网页。
- 核心功能必须在飞行模式下完整可用。
- 不得在 App 内使用
URLSession、WKWebView、在线地图瓦片、远程字体、远程图片、广告 SDK 或分析 SDK。 - “关于”页面中的作者链接属于用户主动点击后交由系统浏览器打开的外部链接;App 本身不得预加载或请求这些网页。
- 优先使用 Apple 原生组件,不自制与系统组件重复的导航栏、标签栏、搜索框、菜单、日期选择器和分享面板。
- 所有版本优先使用对应系统的 Apple 原生 UI;iOS 26 及以上可在导航、工具栏、浮动操作和关键交互层使用原生 Liquid Glass,低版本不得用自制模糊层仿制。
- 每一阶段结束必须运行构建和测试,并报告:完成内容、修改文件、测试结果、已知问题、下一阶段建议。
- 不得用“以后再补”掩盖当前阶段验收失败。无法验证的内容必须明确说明。
- 所有用户可见文本必须进入 String Catalog,不得在业务视图中硬编码中文或英文。
- 所有图标、国旗、地图和品牌图形必须打包在 App 内,本地读取。
- 不要一次性重写整个工程。严格按照本文阶段逐步实施。
1. 产品定义
1.1 产品名称
中文名称:
卡片漂流簿
英文名称:
Postcard Journey
中文副标题:
记录每一张明信片的旅程
英文副标题:
Track every postcard journey
1.2 产品定位
“卡片漂流簿”是一款原生 iOS 离线明信片记录应用,适用于 Postcrossing 和其他明信片交换场景。
应用只做四件核心事情:
- 记录收到和寄出的明信片。
- 自动计算明信片旅行天数。
- 在离线世界地图上展示明信片的大致来源地或目的地。
- 生成现代、美观、适合社交平台分享的单卡分享图和年度报告。
1.3 非官方声明
在首次使用说明和“关于”页面展示:
中文:
本应用是独立开发的非官方明信片记录工具,与 Postcrossing 官方无隶属、合作或授权关系。
英文:
This is an independently developed, unofficial postcard logging app and is not affiliated with, endorsed by, or authorized by Postcrossing.
1.4 产品原则
- 轻量记录:表单字段少,用户应在几十秒内完成一条记录。
- 离线优先:记录、图片、地图、统计、主题和报告全部本地完成。
- 原生体验:使用 Apple 原生 UI、手势、导航和系统反馈。
- 内容优先:照片和明信片旅程是主体,玻璃只是功能层。
- 现代美观:统计图表、地图、分享卡和年度报告要具备成品设计感。
- 隐私友好:默认隐藏完整 ID,不上传明信片照片和留言。
- 可长期使用:数据模型、备份格式和迁移方案必须稳定。
2. 功能范围
2.1 必须实现
- 新建收到的明信片记录。
- 新建寄出的明信片记录。
- 编辑、删除、复制记录。
- 明信片 ID。
- 国家或地区。
- 寄出日期。
- 收到日期或对方登记日期。
- 自动计算旅行天数。
- 距离,单位为公里,可选。
- 寄出明信片的对方回复留言。
- 预设标签和用户自定义标签。
- 简短备注。
- 正面照片一张。
- 背面照片一张。
- 离线世界地图。
- 国家默认大致坐标。
- 用户手动微调地图标记。
- 搜索、筛选、排序。
- 全局统计和年度统计。
- 精美单张分享卡。
- 年度总结和年度报告。
- 中文和英文。
- iOS 26 及以上使用 Apple 原生 Liquid Glass;低于 iOS 26 的受支持版本使用对应系统版本的原生 SwiftUI UI。
- 预设主题和用户自定义主题。
- 浅色、深色、跟随系统外观。
- 完整本地备份和恢复。
- “关于”页面和作者信息。
2.2 明确不实现
- 邮资。
- 币种。
- 邮票信息。
- 邮戳信息。
- 特殊卡片分类。
- 首发国家卡片。
- 完整地址。
- 收件人或寄件人真实姓名。
- 联系人管理。
- 物流追踪。
- Postcrossing 登录。
- Postcrossing API。
- 自动登记卡片。
- 自动发送回复。
- 自动抓取官方留言。
- 在线地图。
- 账号系统。
- 社区或关注系统。
- 广告。
- 内购。
- 云端后台。
- AI 文案生成。
- OCR 识别。
- 邮票收藏或邮戳收藏。
3. 平台与技术基线
3.1 最低版本
- 最低系统:iOS 17.0。
- 开发工具:包含 iOS 26 SDK 的最新稳定 Xcode,或更新且兼容的稳定版本。
- 编译 SDK:优先使用最新稳定 SDK。
- 部署目标:必须保持为 iOS 17.0,不得为了减少适配工作擅自提高。
- 语言:最新稳定 Swift。
- UI:SwiftUI。
- 数据:SwiftData。
- 图表:Swift Charts。
- 图片选择:PhotosPicker。
- 分享图:SwiftUI
ImageRenderer。 - 本地文件导入导出:FileImporter、FileExporter、Transferable 或系统文档选择器。
- 本地化:String Catalog(
.xcstrings)。 - 测试:XCTest、Swift Testing 或 Xcode 当前推荐的原生测试框架;不得依赖第三方测试库。
3.2 系统版本与向下兼容策略
本项目使用最新版本的 Xcode 和 iOS SDK 开发,但最低系统版本设置为 iOS 17,不再仅支持 iOS 26。
在 iOS 26 及以上系统中,应用应优先使用 Apple 原生 Liquid Glass 设计。标准 SwiftUI 导航栏、标签栏、工具栏和系统控件会自动采用当前系统的 Liquid Glass 外观;需要强化视觉层级的少量自定义控件,可以使用 glassEffect、GlassEffectContainer、.buttonStyle(.glass) 和 .buttonStyle(.glassProminent) 等系统 API。
在 iOS 17、iOS 18 以及其他低于 iOS 26 的受支持系统中,应用继续使用对应系统版本提供的原生 SwiftUI 导航、标签栏、按钮、菜单、表单、材质和动画效果。低版本界面不需要复制 iOS 26 的视觉表现,也不得通过自制模糊层、叠加高光、伪折射动画或第三方组件模拟 Liquid Glass。
所有仅适用于 iOS 26 及以上系统的 API,都必须通过可用性判断进行隔离,例如:
@ViewBuilder
private var adaptivePrimaryAction: some View {
if #available(iOS 26.0, *) {
Button("action.save", action: save)
.buttonStyle(.glassProminent)
} else {
Button("action.save", action: save)
.buttonStyle(.borderedProminent)
}
}
低版本中的 .regularMaterial、.thinMaterial、.bordered 或 .borderedProminent 等效果,只能作为该系统原生设计的一部分自然使用,不能刻意仿制 Liquid Glass。各系统应呈现其自身原生且一致的 Apple UI:
- iOS 26 及以上:使用系统原生 Liquid Glass。
- 低于 iOS 26 的受支持版本:使用对应系统版本的原生 SwiftUI 外观、控件和材质。
- 所有版本:共享相同的数据、功能、页面结构、主题配置、隐私规则和无障碍能力。
- 不得因系统版本不同而删减核心功能。
- 不得在低版本中调用仅限 iOS 26 的 API。
- 不得为了逃避兼容工作提高最低系统版本。
背景样式、主题色和用户自定义主题需要在所有受支持版本中生效,但具体材质表现应交由系统决定。应用应跟随浅色模式、深色模式、降低透明度、增强对比度和减少动态效果等系统设置。
3.3 跨版本工程规则
- 将版本差异集中封装在 DesignSystem 或小型自适应组件中,不复制整套业务页面。
- 优先使用
TabView、NavigationStack、toolbar、Form、Menu、Picker等标准组件,让系统自动呈现对应版本的视觉效果。 glassEffect、GlassEffectContainer、.glass和.glassProminent只能出现在 iOS 26 可用性分支内。- 禁止在模型层、业务服务层和数据层引入系统外观分支。
- 主题数据结构不得依赖 Liquid Glass API,确保同一主题可在所有受支持版本读取。
- 新增界面组件时,必须同时说明 iOS 26 及以上表现和低于 iOS 26 的受支持版本表现。
- 发布前至少验证一个 iOS 17 或 iOS 18 模拟器,以及一个 iOS 26 或以上模拟器;若运行时不可用,必须明确报告未验证项。
3.4 禁止依赖
除非用户之后明确批准,否则:
- 不添加 Swift Package 第三方依赖。
- 不添加 CocoaPods。
- 不添加 Carthage。
- 不添加 Firebase。
- 不添加 Realm。
- 不添加 Kingfisher、SDWebImage。
- 不添加第三方图表库。
- 不添加第三方地图 SDK。
- 不添加第三方日志和埋点 SDK。
4. Apple 原生 UI、Liquid Glass 与跨版本适配规范
4.1 总体原则
界面分为两层:
内容层
用于展示明信片照片、列表、地图、统计和报告内容。
内容层在所有系统版本中使用:
- 当前主题背景。
- 对应系统的背景色和语义色。
- 必要时使用系统材质。
- 低对比度描边。
- 普通圆角内容卡片。
- 清晰的文字层级。
功能层
用于导航、工具栏、筛选器、主要按钮、浮动控件和临时操作。
所有系统版本都必须优先使用标准 SwiftUI 导航、标签栏、工具栏、菜单、Sheet 和按钮。具体视觉由系统版本决定:
- iOS 26 及以上:标准组件自动呈现原生 Liquid Glass;少量自定义关键控件可以使用受保护的 Liquid Glass API。
- 低于 iOS 26 的受支持版本:使用对应系统原生导航、工具栏、按钮样式和系统材质,不模仿 Liquid Glass。
4.2 版本适配矩阵
| 能力 | iOS 26 及以上 | 低于 iOS 26 的受支持版本 |
|---|---|---|
| TabView、NavigationStack、Toolbar | 使用系统原生 Liquid Glass 表现 | 使用对应系统的原生 SwiftUI 表现 |
| 主要按钮 | 可使用 .glassProminent |
使用 .borderedProminent 或系统默认主按钮样式 |
| 次要按钮 | 可使用 .glass |
使用 .bordered、.plain 或系统默认样式 |
| 浮动操作组 | 可使用 GlassEffectContainer |
使用普通 HStack、VStack、Toolbar 或系统菜单 |
| 临时功能面板 | 可使用 glassEffect |
使用系统 Sheet、Popover 或自然的系统材质 |
| 内容卡片 | 普通主题卡片,通常不使用玻璃 | 普通主题卡片 |
| 动画 | 可使用系统玻璃形变和过渡 | 使用系统标准过渡和淡入淡出 |
版本不同只能改变视觉呈现,不得改变业务字段、功能范围、数据结果或隐私行为。
4.3 禁止滥用和伪造玻璃
不得执行以下设计:
- 每一张列表卡片都使用 Liquid Glass。
- 表单每个输入框都套一层玻璃。
- 地图国家轮廓使用玻璃。
- 图表柱形和折线使用玻璃。
- 在复杂照片上叠加大量透明玻璃文字块。
- 在低于 iOS 26 的受支持版本上自行模拟高斯模糊、折射、高光、畸变或果冻形变来冒充 Liquid Glass。
- 使用第三方库复刻 Liquid Glass。
- 为了统一截图而强制低版本呈现 iOS 26 外观。
4.4 推荐使用位置
以下位置在 iOS 26 及以上可使用原生 Liquid Glass;在低于 iOS 26 的受支持版本应使用对应系统原生控件:
- 底部
TabView。 - 顶部导航和 Toolbar。
- 首页的“记录寄出”“记录收到”快捷操作组。
- 地图悬浮筛选器。
- 统计页面年份切换器。
- 分享模板切换器。
- 年度报告底部操作栏。
- 图片全屏查看器的关闭、分享和正反面切换按钮。
- 关键确认按钮。
4.5 自适应组件代码准则
主操作按钮应通过小型自适应组件统一处理:
struct AdaptivePrimaryAction<Label: View>: View {
let action: () -> Void
private let label: Label
init(
action: @escaping () -> Void,
@ViewBuilder label: () -> Label
) {
self.action = action
self.label = label()
}
var body: some View {
if #available(iOS 26.0, *) {
Button(action: action) {
label
}
.buttonStyle(.glassProminent)
} else {
Button(action: action) {
label
}
.buttonStyle(.borderedProminent)
}
}
}
相邻操作组应集中处理版本差异:
@ViewBuilder
func adaptiveActionGroup<Content: View>(
@ViewBuilder content: () -> Content
) -> some View {
if #available(iOS 26.0, *) {
GlassEffectContainer(spacing: 12) {
content()
}
} else {
HStack(spacing: 12) {
content()
}
}
}
要求:
- iOS 26 专用符号必须位于明确的可用性分支内。
- 玻璃着色只能用于强调选中状态或主操作。
- 同一页面不应出现多种互相冲突的玻璃色。
- 优先让系统导航组件自动获得当前系统外观,不对系统栏额外叠加自制材质。
- 自定义玻璃必须支持“减少透明度”和“增强对比度”。
- iOS 26 中可使用
GlassEffectContainer合并相邻玻璃控件;低版本使用普通布局,不模拟形变。 - 使用匹配几何、玻璃过渡或普通转场时,不得影响 VoiceOver 和 Reduce Motion。
- 不得为了复用视觉效果而让业务视图直接散落大量
#available;应优先封装到 DesignSystem。
4.6 原生组件要求
必须优先使用:
TabViewNavigationStackNavigationLinktoolbarsearchableFormSectionPickerDatePickerTextFieldTextEditorMenuConfirmationDialogAlertPhotosPickerShareLink或系统UIActivityViewControllerContentUnavailableViewProgressViewChartssensoryFeedback
禁止自制仿系统的底部标签栏、日期选择器、键盘、分享面板和警告框。
5. 主题与背景系统
5.1 主题目标
用户可以在设置中更改:
- 主题预设。
- 强调色。
- 背景样式。
- 背景主色。
- 渐变颜色。
- 外观模式:跟随系统、浅色、深色。
- 动态背景开关。
- 恢复默认主题。
主题只改变视觉,不改变数据和功能。
5.2 内置主题预设
至少提供以下主题:
1. 系统原生 / System
- 使用系统背景。
- 强调色使用系统蓝。
- 最克制、最原生。
2. 航空信封 / Air Mail
- 暖白背景。
- 邮政红强调色。
- 航空蓝辅助色。
- 红蓝元素仅用于局部装饰,不铺满界面。
3. 海岸 / Coast
- 浅蓝到青色柔和渐变。
- 深海蓝强调色。
- 适合地图和旅行主题。
4. 日落 / Sunset
- 暖橙、珊瑚和淡紫渐变。
- 橙红强调色。
5. 森林 / Forest
- 柔和绿色背景。
- 深绿色强调色。
6. 薰衣草 / Lavender
- 淡紫色背景。
- 紫罗兰强调色。
7. 石墨 / Graphite
- 中性灰和深灰背景。
- 低饱和蓝灰强调色。
- 适合深色模式。
8. 自定义 / Custom
用户可自行设置:
- 强调色。
- 背景纯色,或两色/三色渐变。
- 渐变方向。
- 背景动态强度。
5.3 主题数据模型
struct AppTheme: Codable, Equatable, Sendable {
var preset: ThemePreset
var accent: CodableColor
var backgroundStyle: BackgroundStyle
var backgroundColors: [CodableColor]
var gradientDirection: GradientDirection
var appearance: AppAppearance
var animatedBackgroundEnabled: Bool
var animationIntensity: Double
}
enum ThemePreset: String, Codable, CaseIterable {
case system
case airMail
case coast
case sunset
case forest
case lavender
case graphite
case custom
}
enum BackgroundStyle: String, Codable, CaseIterable {
case system
case solid
case linearGradient
case radialGradient
case softMesh
}
enum AppAppearance: String, Codable, CaseIterable {
case system
case light
case dark
}
5.4 颜色存储
不得直接把 SwiftUI Color 当作持久化模型。
使用可编码 RGBA:
struct CodableColor: Codable, Equatable, Sendable {
var red: Double
var green: Double
var blue: Double
var alpha: Double
}
5.5 ThemeManager
建立:
@MainActor
@Observable
final class ThemeManager {
var theme: AppTheme
func applyPreset(_ preset: ThemePreset)
func updateAccent(_ color: Color)
func updateBackground(...)
func resetToDefault()
}
存储方式:
- 小型主题设置使用
@AppStorage或一个 Codable 配置文件。 - 不需要写入 SwiftData。
- App 启动时立即恢复主题,避免默认色闪烁。
5.6 主题预览
设置页面提供实时预览卡,展示:
- 背景。
- 玻璃按钮。
- 普通内容卡。
- 标题和正文。
- 标签。
- 图表颜色。
- 地图标记颜色。
用户修改后即时预览,点击“完成”保存,点击“取消”恢复进入页面前的主题。
5.7 可读性规则
- 根据背景亮度自动选择主要文字色。
- 玻璃按钮文字不得与背景混淆。
- 图表颜色至少达到可辨识对比度。
- 主题色不能成为唯一的信息区分方式,必须搭配形状、标签或图标。
- “增强对比度”开启时增加卡片边界和文字对比度。
- “减少透明度”开启时,玻璃控件使用更不透明的系统替代样式。
6. 信息架构与导航
底部导航使用系统 TabView,包含五项:
- 首页 / Home
- 记录 / Records
- 地图 / Map
- 统计 / Statistics
- 设置 / Settings
推荐 SF Symbols:
| 页面 | SF Symbol |
|---|---|
| 首页 | house.fill |
| 记录 | rectangle.stack.fill |
| 地图 | map.fill |
| 统计 | chart.bar.xaxis |
| 设置 | gearshape.fill |
每个 Tab 内部使用独立 NavigationStack,保留各自导航路径。
7. 数据模型
7.1 核心模型
只设置一个业务主模型:
@Model
final class PostcardRecord {
@Attribute(.unique) var uuid: UUID
var directionRawValue: String
var postcardID: String
var countryCode: String
var sentDate: Date
var receivedDate: Date?
var distanceKilometers: Int?
var replyMessage: String?
var note: String?
var latitude: Double
var longitude: Double
var locationWasAdjusted: Bool
var frontImageFilename: String?
var backImageFilename: String?
var createdAt: Date
var updatedAt: Date
@Relationship(deleteRule: .nullify)
var tags: [PostcardTag]
}
7.2 方向
enum PostcardDirection: String, Codable, CaseIterable {
case sent
case received
}
7.3 标签模型
@Model
final class PostcardTag {
@Attribute(.unique) var id: UUID
var key: String?
var customName: String?
var isBuiltIn: Bool
var createdAt: Date
}
内置标签使用稳定 key,通过本地化文件显示名称。自定义标签保存用户输入的名字。
7.4 字段规则
收到的明信片
direction = received- 明信片 ID:建议填写,允许为空。
- 国家或地区:必填。
- 寄出日期:必填。
- 收到日期:必填。
- 距离:可选。
- 回复留言:隐藏且不可填写。
- 标签:可选,多选。
- 备注:可选。
- 正面图片:可选。
- 背面图片:可选。
- 大致坐标:默认国家中心点,可调整。
寄出的明信片
direction = sent- 明信片 ID:建议填写,允许为空。
- 目的国家或地区:必填。
- 寄出日期:必填。
- 对方收到日期:可选;为空表示仍在旅行。
- 距离:可选。
- 对方回复留言:可选。
- 标签:可选,多选。
- 备注:可选。
- 正面图片:可选。
- 背面图片:可选。
- 大致坐标:默认国家中心点,可调整。
7.5 不允许增加的字段
Codex 不得擅自增加:
- 邮资。
- 货币。
- 邮票数量。
- 邮票面值。
- 邮票主题。
- 邮戳。
- 购买价格。
- 卡片发行商。
- 卡片评级。
- 喜爱程度。
- 联系人。
- 地址。
- 电话。
- 邮编。
- “首发国家”。
- “特殊卡片”。
- 收藏状态。
7.6 派生属性
以下内容实时计算,不写入数据库:
extension PostcardRecord {
var direction: PostcardDirection { ... }
var travelDays: Int? { ... }
var isInTransit: Bool { ... }
var isCompleted: Bool { ... }
var maskedPostcardID: String { ... }
}
旅行天数规则:
旅行天数 = 收到日期 - 寄出日期
- 按自然日计算,不按小时。
- 同一天寄出和收到,显示 0 天。
- 缺少收到日期,寄出记录显示“旅行中 X 天”。
- 收到日期早于寄出日期时禁止保存,除非用户修改日期。
7.7 ID 重复规则
- ID 非空时执行大小写不敏感重复检查。
- 保存前提示已有相同 ID 的记录。
- 用户可选择查看已有记录或仍然保存。
- 不把空 ID 当作重复。
8. 国家、地区、国旗和坐标
8.1 本地数据文件
建立:
Resources/Countries/countries.json
结构:
{
"code": "DE",
"nameKey": "country.de",
"continent": "EU",
"centroidLatitude": 51.0,
"centroidLongitude": 10.0,
"flagAsset": "flag_de"
}
国家显示名称通过 String Catalog 本地化,不把中英文都硬编码进 JSON。
8.2 搜索
国家选择器支持搜索:
- 中文名称。
- 英文名称。
- ISO 两字母代码。
例如:
德国GermanyDE
均能找到德国。
8.3 国旗资源
- 使用本地小尺寸 SVG 国旗。
- 放入
Assets.xcassets/Flags或独立资源目录。 - 保留矢量数据。
- 列表中统一显示为固定比例和圆角矩形。
- 不通过网络加载。
8.4 中国台湾固定规则
内部代码仍使用 TW,以兼容世界地图轮廓和坐标。
显示规则必须固定:
| 项目 | 规则 |
|---|---|
| 简体中文名称 | 中国台湾 |
| 英文名称 | Taiwan, China |
| 国旗资源 | flag_cn |
| 地图位置 | 使用台湾地区的大致中心坐标 |
| 地图轮廓 | 可单独命中和标记,但不得展示其他旗帜 |
必须为此规则建立自动化测试,防止后续修改破坏。
8.5 国家资源校验
建立测试确保:
- 每个国家代码唯一。
- 每个国家都有中英文名称。
- 每个国家都有中心坐标。
- 每个国家对应的国旗资源存在。
- 坐标在合法范围。
TW使用flag_cn。
9. 预设标签与自定义标签
9.1 内置标签
至少包含:
| Key | 中文 | English |
|---|---|---|
| landscape | 风景 | Landscape |
| animals | 动物 | Animals |
| architecture | 建筑 | Architecture |
| transport | 交通 | Transport |
| trains | 火车 | Trains |
| travel | 旅行 | Travel |
| photography | 摄影 | Photography |
| cities | 城市 | Cities |
| nature | 自然 | Nature |
| maps | 地图 | Maps |
| illustration | 插画 | Illustration |
| art | 艺术 | Art |
| food | 食物 | Food |
| festivals | 节日 | Festivals |
| museums | 博物馆 | Museums |
| universities | 大学 | Universities |
| ocean | 海洋 | Ocean |
| mountains | 山川 | Mountains |
9.2 标签交互
- 多选。
- 支持搜索。
- 最近使用标签优先。
- 支持创建自定义标签。
- 自定义标签允许重命名和删除。
- 删除自定义标签时只解除关联,不删除明信片记录。
- 内置标签不能删除,可在设置中隐藏。
- 表单中最多默认显示 8 个常用标签,其余通过“更多”展开。
10. 页面详细规格
10.1 首页
主要内容
- 应用标题和简短文案。
- 本年度快速概览。
- 累计寄出。
- 累计收到。
- 正在旅行。
- 涉及国家或地区数量。
- 快速记录寄出。
- 快速记录收到。
- 年度报告入口。
- 最近记录。
- 可选的备份提醒。
首页设计
- 背景为当前主题背景,边到边显示。
- 内容可滚动到导航和标签栏下方。
- 顶部统计内容使用标准内容卡片,不使用大量玻璃。
- 两个快速添加按钮使用统一的自适应操作组:iOS 26 及以上使用
GlassEffectContainer,低于 iOS 26 的受支持版本使用原生按钮样式和普通布局。 - 年度报告入口使用大尺寸视觉卡片,可展示年度照片拼贴或地图缩略图。
- 无记录时显示系统
ContentUnavailableView风格的空状态。
动画
- 统计数字在首次出现时平滑递增。
- 卡片依次淡入和轻微上移。
- 快速按钮出现轻微玻璃形变。
- Reduce Motion 开启时取消数字滚动和形变,仅淡入。
10.2 记录列表
顶部功能
- 系统搜索框。
- 筛选菜单。
- 排序菜单。
- 添加按钮。
记录范围
- 全部。
- 寄出。
- 收到。
- 旅行中。
使用系统 Picker 或合适的原生筛选模式,不自制复杂标签栏。
搜索范围
- 明信片 ID。
- 国家中文名。
- 国家英文名。
- 标签。
- 备注。
- 寄出记录的回复留言。
筛选
- 年份。
- 国家或地区。
- 标签。
- 方向。
- 是否旅行中。
- 是否有正面图片。
- 是否有背面图片。
排序
- 最近创建。
- 最早创建。
- 寄出日期从新到旧。
- 收到日期从新到旧。
- 旅行天数最长。
- 距离最远。
- 国家名称。
单条列表内容
- 正面缩略图或占位图。
- ID;为空时显示“未填写 ID”。
- 国家名称和国旗。
- 寄出/收到方向。
- 日期。
- 旅行天数或旅行中天数。
- 不显示备注全文和回复留言全文。
交互
- 点击进入详情。
- 左滑删除。
- 右滑编辑或快速标记到达。
- 长按打开上下文菜单:编辑、复制、分享、删除。
10.3 新建与编辑记录
使用原生 Form 和 Section。
第一部分:方向
- 寄出。
- 收到。
创建后允许更改方向,但更改时要清理仅适用于寄出的回复留言字段,并要求确认。
第二部分:基本信息
- 明信片 ID。
- 国家或地区。
- 寄出日期。
- 收到日期或对方收到日期。
- 实时显示旅行天数。
- 距离。
第三部分:地图位置
- 显示国家大致位置的小地图预览。
- 选择国家后自动设置国家中心点。
- 点击“调整位置”进入离线地图选择页面。
- 用户可拖动标记或点击地图。
- 显示“这是大致位置,不代表精确地址”。
第四部分:内容
- 标签。
- 备注。
- 寄出记录显示“对方回复留言”。
- 回复留言提供系统粘贴按钮,但不得后台读取剪贴板。
第五部分:照片
- 正面照片。
- 背面照片。
- 每面最多一张。
- 支持选择、拍照、替换、删除和预览。
保存规则
- 国家和寄出日期必填。
- 收到记录的收到日期必填。
- 寄出记录的收到日期可空。
- 收到日期不得早于寄出日期。
- 距离必须为大于 0 的整数。
- 保存时检查重复 ID。
- 保存后提供轻量触觉反馈。
10.4 记录详情
顶部照片
- 正面照片作为主视觉。
- 可左右切换正面和背面。
- 无照片时显示现代占位图。
- 点击进入全屏查看器。
信息
- 明信片 ID。
- 国家和国旗。
- 寄出日期。
- 收到日期。
- 旅行天数。
- 距离。
- 标签。
- 回复留言。
- 备注。
- 小地图和大致标记。
操作
- 编辑。
- 分享。
- 复制回复留言。
- 删除。
工具栏始终使用系统原生 Toolbar;iOS 26 及以上由系统呈现 Liquid Glass,低于 iOS 26 的受支持版本使用对应版本的原生外观。
10.5 离线地图
详见第 12 章。
10.6 统计
详见第 13 章。
10.7 设置
分组:
外观
- 主题预设。
- 自定义主题。
- 外观模式。
- 动态背景。
- 分享图默认风格。
记录
- 默认方向。
- 默认首页年份。
- 内置标签显示管理。
- 自定义标签管理。
- 我的默认位置。
数据
- 导出备份。
- 导入备份。
- 备份提醒。
- 存储占用。
- 清理未引用图片。
- 清空全部数据。
语言
- 跟随系统。
- 简体中文。
- English。
关于
- 作者信息。
- 软件版本。
- 隐私说明。
- 非官方声明。
- 开源许可和第三方资源说明。
11. 图片系统
11.1 图片数量
每条记录只允许:
- 正面一张。
- 背面一张。
不得添加“邮票局部”“邮戳局部”“其他图片”等额外槽位。
11.2 选择方式
PhotosPicker。- 系统相机,可选实现。
- 应用不申请整个照片库读取权限。
- 只处理用户明确选择的照片。
11.3 存储
数据库只保存相对文件名:
frontImageFilename
backImageFilename
文件保存到:
Application Support/PostcardJourney/Images/<record-uuid>/front.heic
Application Support/PostcardJourney/Images/<record-uuid>/back.heic
Application Support/PostcardJourney/Thumbnails/<record-uuid>/front.jpg
Application Support/PostcardJourney/Thumbnails/<record-uuid>/back.jpg
11.4 压缩规则
- 修正 EXIF 方向。
- 原图最长边默认限制在 2400 px。
- 缩略图最长边 480 px。
- 优先 HEIF;不支持时使用 JPEG。
- 压缩放在后台任务中执行。
- 页面保存前显示真实进度,不显示假加载动画。
- 图片写入成功后再提交数据库引用。
11.5 删除规则
- 删除记录同时删除正面、背面和缩略图。
- 替换图片时,在新图写入成功后删除旧文件。
- 失败时保留旧图。
- 设置中提供“清理未引用图片”,先扫描再确认。
11.6 隐私
- 分享卡默认只使用正面照片。
- 背面照片可能含地址和私人文字,分享前默认不可选。
- 全屏查看背面时显示隐私提示,但不反复打扰。
12. 完全离线世界地图
12.1 技术方案
不得使用在线地图瓦片。
使用:
- 本地简化 GeoJSON。
- SwiftUI Canvas 或 Shape 渲染。
- 本地国家轮廓。
- 本地国家中心坐标。
- 自定义投影工具。
- SwiftUI 手势实现缩放、拖动和点选。
可使用 MapKit 的纯几何类型进行坐标计算,但不得展示会联网加载的 Map 底图。
12.2 地图文件
Resources/Map/world-simplified.geojson
Resources/Map/country-centroids.json
要求:
- 轮廓经过简化,控制包体和渲染开销。
- 保留国家代码映射。
- 提供资源来源和许可记录。
- 不在运行时下载。
12.3 地图模式
- 综合。
- 寄出。
- 收到。
- 指定年份。
- 全部年份。
12.4 标记逻辑
- 新建记录选择国家后,自动使用该国中心点。
- 用户可点击或拖动标记到国家内的大致区域。
- 只记录经纬度,不记录精确地址。
- 坐标选择页显示国家边界并限制或提示越界。
- 用户仍可选择边界附近位置,但要弹出轻量提示。
12.5 我的默认位置
设置中允许用户设置一个“我的默认位置”:
- 默认在中国的大致中心。
- 用户可在地图上调整。
- 只用于路线视觉和年度报告。
- 不记录地址。
- 不请求定位权限。
- 不自动读取 GPS。
12.6 地图展示
国家填色
按照记录数量分级:
- 0:未点亮。
- 1:一级。
- 2–5:二级。
- 6–10:三级。
- 11–20:四级。
- 21 以上:五级。
颜色由主题系统生成,但不能只依赖颜色表达数量;点击后显示具体数字。
标记
- 寄出:纸飞机或向外箭头。
- 收到:信封或向内箭头。
- 综合视图:使用方向形状区分。
- 密集区域使用聚合标记。
路线
- 默认不绘制全部路线,避免混乱。
- 点击单条记录时绘制从“我的默认位置”到目标点的弧线。
- 年度报告中可绘制简化路线集合。
12.7 地图动效
- 国家点亮渐变出现。
- 标记使用轻微弹性落点。
- 单条路线逐段绘制。
- 缩放和拖动保持 60fps 目标。
- Reduce Motion 时取消弹跳和逐段绘制。
12.8 地图验收
- 飞行模式下可用。
- 无任何网络错误日志。
- 500 条记录可正常聚合。
- 地图缩放拖动流畅。
- 选择国家后标记正确。
- 保存自定义坐标后重启仍存在。
- 中国台湾显示名称和国旗规则正确。
13. 统计系统
13.1 统计架构
建立独立服务:
struct StatisticsService {
func makeOverview(records: [PostcardRecord], year: Int?) -> OverviewStatistics
func makeMonthlySeries(...)
func makeCountryRanking(...)
func makeTagRanking(...)
func makeTravelTimeBuckets(...)
func makeDistanceStatistics(...)
func makeAnnualSummary(...)
}
页面不得在 body 中执行复杂聚合。
13.2 总览统计
- 累计寄出数量。
- 累计收到数量。
- 正在旅行数量。
- 已完成旅程数量。
- 涉及国家或地区数量。
- 累计旅行距离。
- 平均旅行天数。
- 最短旅行天数。
- 最长旅行天数。
- 最远的一张明信片。
13.3 统计规则
- 旅行中记录不参与“已完成平均旅行天数”。
- 缺少距离的记录不参与距离平均值。
- 收到记录和寄出已到达记录均可参与旅行时间统计。
- 0 天旅程是合法值。
- 数据不足时显示友好空状态,不显示 NaN、Infinity 或空图表。
13.4 图表
使用 Swift Charts:
- 月度寄出与收到趋势。
- 月度完成数量。
- 国家排行。
- 标签排行。
- 旅行天数区间。
- 距离区间。
旅行天数区间:
- 0–7 天。
- 8–14 天。
- 15–30 天。
- 31–60 天。
- 61–90 天。
- 90 天以上。
13.5 统计页面布局
顶部:
- 全部 / 年度。
- 年份选择。
内容:
- 核心数字。
- 月度趋势。
- 国家分布。
- 标签分布。
- 旅行速度。
- 距离。
- 旅行之最。
- 生成年度报告。
13.6 统计设计
- 图表背景使用内容层标准材质或轻量卡片。
- 图表颜色来自主题系统。
- 选中状态可以使用玻璃浮层显示详情。
- 支持深色模式。
- 支持 Dynamic Type。
- VoiceOver 能读出图表摘要和关键数据。
13.7 动画
- 柱状图由下向上生长。
- 折线图逐段出现。
- 数字平滑变化。
- 年份切换交叉淡化。
- Reduce Motion 时使用即时更新或简短淡入。
14. 单张明信片分享卡
14.1 导出规格
默认输出:
1080 × 1440 px,3:4
可选:
- 1080 × 1080。
- 1080 × 1920。
优先导出 PNG,以保持文字清晰。
14.2 默认隐私
默认:
- 明信片 ID 部分隐藏。
- 不显示背面照片。
- 不显示回复留言。
- 不显示备注。
- 不显示精确坐标。
ID 示例:
CN-1234567 → CN-123****
分享设置中允许用户主动开启完整 ID,但每次新建分享默认保持隐藏。
14.3 分享模板
至少提供四套:
A. Modern Journey / 现代旅程
- 大幅正面照片。
- 现代无衬线排版。
- 国家名称和国旗。
- 旅行天数大数字。
- 距离和日期。
- 主题色柔和渐变。
- 少量玻璃控制只出现在预览界面,不渲染进最终分享图。
B. Air Mail / 航空信封
- 暖白纸张背景。
- 克制的红蓝边线。
- 卡片照片轻微倾斜。
- 日期和路线以航空信封方式排版。
- 不使用伪造真实邮戳。
C. Photo Focus / 照片画廊
- 照片占据主要画面。
- 极少文字。
- 适合摄影、风景、建筑卡片。
- 支持浅色和深色标题区。
D. Map Journey / 地图旅程
- 离线世界地图或区域轮廓。
- 起点和终点标记。
- 旅程弧线。
- 旅行天数、距离和国家。
- 照片作为局部拼贴。
14.4 分享编辑
允许:
- 切换模板。
- 切换主题配色。
- 调整照片裁切位置。
- 开关显示国旗。
- 开关显示标签。
- 选择是否显示完整 ID。
- 选择中文或英文排版。
- 保存到照片。
- 调用系统分享面板。
不得允许:
- 直接把回复留言全文放入默认模板。
- 自动使用背面照片。
- 生成含完整地址的图片。
14.5 实现方式
- 分享卡作为独立 SwiftUI View。
- 使用固定设计尺寸布局。
- 使用
ImageRenderer渲染。 - 不使用屏幕截图。
- 预览和导出使用同一数据模型。
- 导出时注入明确 locale、主题和隐私配置。
- 建立快照或像素尺寸测试。
15. 年度总结与年度报告
15.1 目标
年度报告是本应用的重要核心功能,必须具备完整设计感,适合保存为图片并直接分享至小红书、Instagram 等平台。
报告输出为多张 1080 × 1440 PNG,而不是长 PDF。
15.2 页面结构
第 1 页:年度封面
- 年份。
- “我的明信片旅行”。
- Postcard Journey。
- 年度照片或年度地图背景。
- 当前主题色。
第 2 页:年度数字
- 寄出数量。
- 收到数量。
- 已完成旅程。
- 涉及国家数量。
- 累计距离。
- 平均旅行天数。
第 3 页:月度轨迹
- 12 个月寄出和收到趋势。
- 最活跃月份。
- 最安静月份。
第 4 页:年度地图
- 点亮国家。
- 寄出和收到标记。
- 简化路线。
- 最远目的地或来源地。
第 5 页:旅行之最
- 最快的一张。
- 最慢的一张。
- 最远的一张。
- 最接近全年平均旅行时间的一张。
第 6 页:国家与标签
- 最常寄往的国家。
- 最常收到的国家。
- 最常见标签。
- 年度内容类型分布。
第 7 页:年度照片墙
- 默认 3 × 3,最多九张正面照片。
- 用户可手动选择和调整。
- 图片不足时自动使用 1、2、4、6 张的适配布局。
第 8 页:结束页
中文:
感谢每一张穿越山海而来的明信片。
英文:
Thank you to every postcard that crossed the world to arrive here.
15.3 动态页面规则
- 年度记录少于 3 条:生成封面、数字、地图、照片页或结束页。
- 缺少距离:隐藏距离相关模块,不显示 0 km 作为误导。
- 缺少照片:使用地图、主题背景和排版生成,不显示空白相框。
- 某类统计无数据:自动重新排版,不保留空卡片。
- 最快、最慢只有一条记录时允许指向同一条,但文案要自然。
15.4 报告预览
- 横向分页浏览。
- 页面缩略图。
- 单页保存。
- 全部保存。
- 系统分享。
- 重新选择照片墙。
- 切换报告主题。
- 切换中文或英文报告。
15.5 报告主题
- 与 App 当前主题一致。
- 允许临时切换 Air Mail、Coast、Sunset、Graphite 等报告样式。
- 临时切换不修改 App 全局主题。
15.6 性能
- 报告计算和图片渲染放在异步任务中。
- 显示真实页数和渲染进度。
- 支持取消。
- 大图渲染时避免同时保留所有中间位图。
- 全部渲染完成后再调起系统分享。
16. 动画、加载与过渡
16.1 动画原则
- 动画服务于层级、状态和空间关系。
- 不做无意义的长开屏动画。
- 不因数据本地加载很快而制造虚假加载过程。
- 0.18–0.35 秒为大多数微交互的目标时长。
- 支持 Reduce Motion。
16.2 页面动画
首页
- 数字递增。
- 卡片依次出现。
- 快速操作玻璃组自然展开。
列表
- 新记录插入淡入和轻微缩放。
- 删除平滑收起。
- 筛选结果交叉淡化。
详情
- 列表缩略图与详情大图使用匹配过渡。
- 正反面切换使用克制的翻转或滑动。
- Reduce Motion 下改为淡入淡出。
地图
- 标记落点。
- 路线绘制。
- 国家填色。
统计
- 图表进入动画。
- 年份切换动画。
跨版本功能层动效
- iOS 26 及以上的相关控制可以使用
GlassEffectContainer和系统玻璃形变。 - 低于 iOS 26 的受支持版本使用标准 SwiftUI 过渡、缩放和淡入淡出,不手动模拟玻璃或果冻形变。
- Reduce Motion 开启时,所有版本都应改用克制的淡入淡出或无动画更新。
16.3 加载状态
只在真实异步任务中显示:
- 图片压缩。
- 大型备份导入。
- 完整备份导出。
- 年度报告渲染。
- 大量记录统计重建。
本地列表首次显示若低于约 200ms,不显示 ProgressView。
17. 中英文双语
17.1 本地化方式
使用:
Resources/Localizable.xcstrings
支持:
- 简体中文。
- English。
17.2 语言选择
设置中提供:
- 跟随系统。
- 简体中文。
- English。
实现时优先遵循 Apple 当前推荐的 App 内语言策略。如果强制语言切换需要重启视图树,提供平滑提示和正确刷新。
17.3 本地化范围
- 页面标题。
- 按钮。
- 表单字段。
- 国家名称。
- 内置标签。
- 日期格式。
- 距离单位。
- 旅行天数。
- 图表辅助描述。
- 空状态。
- 错误信息。
- 分享卡。
- 年度报告。
- 关于页面。
- 隐私说明。
17.4 日期与数字
- 使用
Date.FormatStyle。 - 使用
Measurement和MeasurementFormatter或现代 FormatStyle。 - 中文可显示“23 天”“8,214 公里”。
- 英文可显示“23 days”“8,214 km”。
- 不拼接本地化字符串。
- 正确处理单复数。
18. 关于页面与作者信息
18.1 页面结构
“设置 → 关于”展示:
- App 图标。
- 卡片漂流簿 / Postcard Journey。
- 版本号和 Build 号。
- 简介。
- 作者署名。
- 社交与主页链接。
- 隐私说明。
- 非官方声明。
- 资源许可和开源说明。
18.2 作者信息
必须使用以下内容:
- 昵称(作者):十七号旅客
- 小红书:https://www.xiaohongshu.com/user/profile/60d40af7000000000101f612
- GitHub:https://github.com/Hercules-Zhaoziyi
- 主页:https://ziyis.cn/
- Instagram:https://www.instagram.com/ithercules
英文页面作者名仍显示“十七号旅客”,可在副标题显示:
Developer & Creator
18.3 链接交互
使用系统 Link 或 openURL:
- 用户点击后交给系统处理。
- 优先打开对应 App 的通用链接;无法打开时进入 Safari。
- 不在 App 内嵌网页。
- 不预加载网页。
- 不追踪点击。
- 长按可复制链接。
这些链接是用户主动触发的外部导航,不属于核心功能的主动网络请求。
18.4 平台矢量图标
必须提供对应平台的本地矢量图标:
Assets.xcassets/BrandIcons/brand_xiaohongshu.svg
Assets.xcassets/BrandIcons/brand_github.svg
Assets.xcassets/BrandIcons/brand_website.svg
Assets.xcassets/BrandIcons/brand_instagram.svg
要求:
- 保持矢量。
- 不运行时下载。
- 在浅色和深色模式下可见。
- 图标大小和视觉重量统一。
- 保留品牌基本轮廓,不做误导性改造。
- 使用相应官方品牌素材或许可允许的矢量资源。
- 将来源和许可写入
ThirdPartyNotices.md。 - 若仓库初始阶段缺少获授权的矢量文件,Codex 必须建立明确的资产占位名称和校验测试,不得擅自从不明网站下载文件;在发布前由维护者放入已确认授权的 SVG。
brand_website 可使用本地矢量地球图标,优先使用 SF Symbol globe;如果统一要求全部为资源文件,则导出为本地矢量资源。
18.5 行样式
每个作者链接行包含:
- 平台图标。
- 平台名称。
- 用户名或域名。
- 系统向外箭头。
示例:
[小红书图标] 小红书 十七号旅客 ↗
[GitHub图标] GitHub Hercules-Zhaoziyi ↗
[地球图标] 个人主页 ziyis.cn ↗
[Instagram] Instagram @ithercules ↗
18.6 关于页文案
中文:
卡片漂流簿是一款本地优先的明信片旅程记录工具。所有记录和照片默认保存在当前设备中。应用不会登录 Postcrossing,也不会主动上传明信片数据。
英文:
Postcard Journey is a local-first app for logging postcard journeys. Records and photos remain on this device by default. The app does not sign in to Postcrossing or upload postcard data.
19. 隐私与离线要求
19.1 网络限制
产品代码中不得出现:
URLSession。WKWebView。- 在线地图 URL。
- 远程资源 URL。
- 自动联网请求。
- 后台同步。
- 埋点上传。
唯一允许的外部 URL 是“关于”页面中用户主动点击的作者链接。
19.2 数据位置
- SwiftData 数据库存放在 App 容器。
- 图片存放在 Application Support。
- 主题和小型偏好存放在 AppStorage 或设置文件。
- 不使用 CloudKit。
- 不使用 iCloud Drive 自动同步。
19.3 权限
- 照片选择通过 PhotosPicker。
- 保存分享图到照片时按系统要求申请添加权限。
- 如实现相机,按需申请相机权限。
- 不请求定位权限。
- 不请求通讯录权限。
- 不请求麦克风权限。
- 不请求通知权限,除非未来用户明确要求提醒功能。
19.4 分享隐私
- ID 默认脱敏。
- 背面默认不分享。
- 回复留言默认不分享。
- 备注默认不分享。
- 不展示完整坐标。
20. 备份、恢复和数据迁移
20.1 备份格式
使用自定义扩展名:
.postcardjourney
示例:
PostcardJourney-Backup-2026-07-26.postcardjourney
内部可使用 ZIP 容器:
manifest.json
records.json
tags.json
settings.json
images/
thumbnails/
20.2 Manifest
{
"formatVersion": 1,
"appVersion": "1.0.0",
"createdAt": "2026-07-26T10:00:00Z",
"recordCount": 120,
"sentCount": 70,
"receivedCount": 50,
"imageCount": 180,
"language": "zh-Hans"
}
20.3 导出
- 先验证数据库和图片引用。
- 显示进度。
- 写入临时文件。
- 完成后再显示系统文件导出面板。
- 用户取消时清理临时文件。
20.4 导入预览
导入后先显示:
- 备份日期。
- 数据版本。
- 记录数量。
- 寄出数量。
- 收到数量。
- 图片数量。
- 是否有损坏文件。
用户选择:
- 合并。
- 覆盖。
- 取消。
20.5 合并规则
- 以记录 UUID 为主键。
- UUID 相同保留
updatedAt较新的版本。 - 不同 UUID 但 ID 相同,不自动删除;导入完成后显示重复提醒。
- 自定义标签按 UUID 和标准化名称合并。
20.6 原子恢复
- 在临时数据库或临时目录验证完整性。
- 全部成功后再切换。
- 失败不得破坏现有数据。
- 覆盖恢复前自动创建临时安全备份。
20.7 数据迁移
建立版本化迁移方案:
struct PostcardJourneyMigrationPlan: SchemaMigrationPlan { ... }
即使 1.0 只有一个 Schema,也要为未来迁移预留结构。
21. 设计系统
21.1 目录
DesignSystem/
├── Theme/
│ ├── AppTheme.swift
│ ├── ThemeManager.swift
│ ├── ThemePreset.swift
│ └── ThemeEnvironment.swift
├── Tokens/
│ ├── Spacing.swift
│ ├── CornerRadius.swift
│ ├── Typography.swift
│ ├── Shadows.swift
│ └── Motion.swift
├── Components/
│ ├── GlassActionGroup.swift
│ ├── PostcardThumbnail.swift
│ ├── StatCard.swift
│ ├── EmptyStateView.swift
│ ├── TagChip.swift
│ ├── CountryRow.swift
│ └── LoadingOverlay.swift
└── Backgrounds/
├── ThemeBackgroundView.swift
└── SoftMeshBackground.swift
21.2 语义颜色
不得在业务页面散落固定 RGB。
定义:
appBackgroundcontentSurfaceelevatedSurfaceprimaryTextsecondaryTextseparatoraccentsentDirectionreceivedDirectionsuccesswarningdestructive
21.3 圆角与间距
遵循硬件和系统的同心圆角感。
建议:
- 小控件:10–12。
- 内容卡:18–24。
- 大型图片:24–30。
- 玻璃浮动条:胶囊或系统自动形状。
具体值集中管理,不在页面中魔法数字泛滥。
22. App 图标和启动体验
22.1 App 图标
使用 Apple Icon Composer 制作多层图标,方向:
- 一张简化明信片。
- 一条旅行轨迹。
- 一个小型纸飞机或邮戳感圆环,但不得仿冒真实邮政标志。
- 主题色采用邮政红、航空蓝或用户品牌色的克制组合。
- 支持浅色、深色和有色外观。
22.2 启动
- 使用系统启动屏。
- 不添加长动画 Logo 页。
- 首次启动进入简短引导。
- 非首次启动直接进入首页。
22.3 首次引导
最多四页:
- 记录寄出与收到的明信片。
- 自动计算旅行天数。
- 在离线地图上点亮世界。
- 数据保存在本机,请定期备份。
最后提供:
- 开始使用。
- 导入备份。
23. 工程架构
23.1 推荐架构
使用 Feature-first + Repository + Services:
- SwiftUI View 只负责展示和轻量交互。
- Feature Model 或 ViewModel 负责页面状态。
- Repository 负责 SwiftData 访问。
- Service 负责统计、图片、备份、分享和地图计算。
- 不创建过度复杂的 Clean Architecture 层级。
23.2 工程目录
PostcardJourney/
├── App/
│ ├── PostcardJourneyApp.swift
│ ├── AppEnvironment.swift
│ ├── RootTabView.swift
│ └── AppRoute.swift
│
├── Core/
│ ├── Models/
│ │ ├── PostcardRecord.swift
│ │ ├── PostcardDirection.swift
│ │ ├── PostcardTag.swift
│ │ ├── Country.swift
│ │ └── CodableColor.swift
│ │
│ ├── Persistence/
│ │ ├── ModelContainerFactory.swift
│ │ ├── RecordRepository.swift
│ │ ├── TagRepository.swift
│ │ ├── SchemaV1.swift
│ │ └── PostcardJourneyMigrationPlan.swift
│ │
│ ├── Services/
│ │ ├── ImageStorageService.swift
│ │ ├── ImageCompressionService.swift
│ │ ├── StatisticsService.swift
│ │ ├── BackupService.swift
│ │ ├── ShareCardRenderer.swift
│ │ ├── AnnualReportBuilder.swift
│ │ ├── CountryDataService.swift
│ │ ├── MapGeometryService.swift
│ │ └── LocalizationService.swift
│ │
│ ├── Utilities/
│ │ ├── TravelDayCalculator.swift
│ │ ├── PostcardIDMasker.swift
│ │ ├── RecordValidator.swift
│ │ ├── CoordinateProjection.swift
│ │ └── FileIntegrityChecker.swift
│ │
│ └── Resources/
│ ├── Countries/
│ ├── Map/
│ └── Tags/
│
├── DesignSystem/
│ ├── Theme/
│ ├── Tokens/
│ ├── Components/
│ └── Backgrounds/
│
├── Features/
│ ├── Onboarding/
│ ├── Home/
│ ├── Records/
│ ├── RecordEditor/
│ ├── RecordDetail/
│ ├── ImageViewer/
│ ├── Map/
│ ├── Statistics/
│ ├── ShareCard/
│ ├── AnnualReport/
│ ├── Settings/
│ ├── ThemeSettings/
│ ├── Backup/
│ └── About/
│
├── Resources/
│ ├── Assets.xcassets/
│ ├── Localizable.xcstrings
│ ├── PrivacyInfo.xcprivacy
│ └── ThirdPartyNotices.md
│
├── PostcardJourneyTests/
├── PostcardJourneyUITests/
└── AGENTS.md
23.3 并发
- UI 更新在 MainActor。
- 图片压缩、备份、报告渲染使用结构化并发。
- 服务类型尽量满足 Sendable。
- 不使用未管理的 detached task 逃避隔离。
- 任务取消要被正确处理。
24. 无障碍
必须支持:
- VoiceOver。
- Dynamic Type。
- Bold Text。
- Increase Contrast。
- Reduce Transparency。
- Reduce Motion。
- Differentiate Without Color。
- 深色模式。
具体要求:
- 国旗图片必须有国家名称 accessibility label。
- 图表提供汇总描述。
- 地图标记可通过 VoiceOver 聚焦。
- 图片正反面切换有明确标签。
- 玻璃控件在减少透明度时保持边界可见。
- 超大字体下表单和统计数字不截断。
- 可点击区域至少符合系统推荐尺寸。
25. 错误处理和日志
25.1 用户错误
使用本地化、可理解文案:
- 日期顺序错误。
- 距离格式错误。
- ID 重复。
- 图片处理失败。
- 备份损坏。
- 存储空间不足。
- 报告生成被取消。
25.2 日志
- 使用 Apple
Logger。 - 不记录明信片回复留言和备注全文。
- 不记录照片内容。
- Release 构建不输出敏感信息。
- 日志只用于本地调试,不上传。
26. 性能目标
- 500 条纯文字记录列表流畅。
- 200 条带正反面图片记录可正常使用。
- 列表使用缩略图,不直接加载原图。
- 地图 500 个点使用聚合。
- 统计结果可缓存并按数据库变化失效。
- 首页首屏避免同步解码大图。
- 年度报告渲染可取消。
- 内存告警时释放非必要图片缓存。
27. 测试计划
27.1 单元测试
必须覆盖:
- 同日旅行天数。
- 跨月旅行天数。
- 跨年旅行天数。
- 闰年。
- 收到日期为空。
- 收到日期早于寄出日期。
- ID 脱敏。
- ID 大小写重复。
- 国家中文搜索。
- 国家英文搜索。
- 国家代码搜索。
- 中国台湾名称映射。
- 中国台湾国旗映射。
- 主题序列化。
- 统计总数。
- 平均旅行天数。
- 距离统计。
- 月度统计。
- 国家排行。
- 标签排行。
- 年度报告动态页面规则。
- 备份 manifest 校验。
- 备份合并策略。
- 图片孤儿检测。
27.2 UI 测试
必须覆盖:
- 首次引导。
- 新建收到记录。
- 新建寄出记录。
- 寄出记录标记收到。
- 编辑记录。
- 删除记录。
- 重复 ID 提示。
- 添加和替换正面照片。
- 添加和删除背面照片。
- 搜索。
- 筛选。
- 地图调整位置。
- 切换主题。
- 自定义主题色。
- 切换中英文。
- 生成单张分享图。
- 生成年报。
- 导出备份。
- 恢复备份。
- 关于页面链接存在。
27.3 视觉测试
建立可重复预览或快照基准,至少覆盖:
- 首页浅色和深色。
- 记录列表。
- 地图。
- 统计。
- 四套分享卡。
- 年度报告主要页面。
- 中文和英文。
- 大字体。
- 高对比度。
不强制引入第三方快照测试库;可以使用原生渲染并校验尺寸、非空像素和关键布局。
27.4 离线检查
建立脚本扫描产品源码:
URLSession
WKWebView
http://
https://
MapKit.Map
Firebase
Analytics
允许白名单:
- “关于”页面常量中的作者链接。
ThirdPartyNotices.md和文档中的来源链接。
产品运行代码中不得发起请求。
28. 根目录 AGENTS.md
Codex 在阶段 0 必须创建以下内容,并在后续阶段持续遵守:
# Postcard Journey Development Instructions
## Product
Postcard Journey / 卡片漂流簿 is a local-first native iOS app for
recording sent and received postcards, displaying approximate offline map
locations, and generating share cards and annual reports.
This app is unofficial and is not affiliated with Postcrossing.
## Platform
- Native SwiftUI application.
- Minimum deployment target: iOS 17.0.
- Build with the latest stable Xcode that includes the iOS 26 SDK, or a newer
compatible stable SDK, while preserving the iOS 17.0 deployment target.
- Use SwiftData for persistence.
- Use Swift Charts for charts.
- Use Apple frameworks only unless the user explicitly approves a dependency.
- No CloudKit.
- No account system.
- No analytics SDK.
- No advertising SDK.
- Core features, data fields, localization, themes, backup, map, statistics,
sharing, and annual reports must remain available on every supported iOS
version. Do not remove a core feature merely because Liquid Glass is
unavailable.
## Adaptive Apple UI and Liquid Glass
- Use standard SwiftUI navigation, tab, toolbar, menu, search, form, sheet,
picker, alert, and share components on every supported system version.
- On iOS 26 and later, allow standard system components to adopt the native
Liquid Glass design automatically.
- On iOS 26 and later, custom Liquid Glass APIs such as `glassEffect`,
`GlassEffectContainer`, `.buttonStyle(.glass)`, and
`.buttonStyle(.glassProminent)` may be used sparingly for navigation,
floating actions, filter controls, and other functional controls.
- Every iOS 26-only API must be isolated with `if #available(iOS 26.0, *)` or a
narrowly scoped `@available` declaration. Never call these APIs from an
unguarded code path.
- On supported versions below iOS 26, use the native SwiftUI appearance and controls of
that system version, such as standard navigation, toolbars, bordered button
styles, and system materials where naturally appropriate.
- Do not build custom blur, refraction, highlight, distortion, or pseudo-glass
layers to imitate Liquid Glass on older systems.
- Do not raise the deployment target to avoid implementing compatibility.
- Prefer reusable adaptive components that switch presentation by availability
instead of duplicating entire feature screens.
- Do not use Liquid Glass as the background of every content card.
- Respect Reduce Transparency, Increase Contrast, Reduce Motion, Dynamic Type,
and VoiceOver on every supported version.
## Product Scope
A postcard record may contain only:
- UUID.
- Direction: sent or received.
- Postcard ID.
- Counterparty country or region.
- Sent date.
- Received date, optional only for sent records that are still traveling.
- Distance in kilometers, optional.
- Reply message, sent records only.
- Built-in and custom tags.
- Note.
- Front image.
- Back image.
- Approximate latitude and longitude.
- Created and updated timestamps.
Do not add postage, currency, stamps, postmarks, addresses, contact names,
ratings, favorites, prices, special-card categories, or first-country fields.
## Offline and Privacy
- The core app must work in airplane mode.
- Do not use URLSession, WKWebView, online map tiles, remote fonts, remote images,
analytics, telemetry, or background network requests.
- External author links in the About screen may open only after an explicit user tap
through the system openURL environment.
- Do not preload author pages.
- Never upload postcard data or images.
- Do not request location permission.
- Mask postcard IDs in share images by default.
- Do not include notes, reply messages, back images, or precise coordinates in
exported share images unless the user explicitly opts in where supported.
## Country Rules
- Bundle country data and simplified world GeoJSON locally.
- Bundle small vector flags locally.
- TW must display as 中国台湾 in Simplified Chinese.
- TW must display as Taiwan, China in English.
- TW must use the Chinese national flag asset, flag_cn.
- Keep TW as the internal geometry code for map compatibility.
- Add automated tests for these rules.
## Images
- Support exactly one front image and one back image per record.
- Store image files in Application Support.
- Store only relative filenames in SwiftData.
- Create thumbnails.
- Compress imported images off the main thread.
- Delete related files when a record is deleted.
- Do not use the back image in a share card by default.
## Map
- The map must work fully offline.
- Do not use online map tiles.
- Render bundled simplified GeoJSON with SwiftUI Canvas or native Shapes.
- Default a new record marker to the selected country's centroid.
- Allow users to adjust the approximate point.
- Do not request GPS access.
## Themes
- Provide System, Air Mail, Coast, Sunset, Forest, Lavender, Graphite, and Custom
presets.
- Allow users to change accent color and background colors.
- Store colors as Codable RGBA values, not SwiftUI Color objects.
- Apply colors through semantic design tokens.
- Preserve contrast and accessibility on iOS 17 and later.
- Theme data and theme choices must not depend on Liquid Glass availability.
## Localization
- Support Simplified Chinese and English.
- Use Localizable.xcstrings.
- Do not hard-code user-facing strings in feature views.
- Localize dates, numbers, units, accessibility labels, share cards, and reports.
## About Screen
Display the author name 十七号旅客 and local vector icons for:
- Xiaohongshu: https://www.xiaohongshu.com/user/profile/60d40af7000000000101f612
- GitHub: https://github.com/Hercules-Zhaoziyi
- Website: https://ziyis.cn/
- Instagram: https://www.instagram.com/ithercules
The links must open only after a user tap. Do not fetch their content in the app.
## Architecture
- Use feature-first folders.
- Keep views focused on presentation.
- Use repositories for SwiftData access.
- Use services for images, backup, statistics, map geometry, share rendering, and
annual report generation.
- Use structured concurrency.
- Avoid unnecessary abstractions and global singletons.
- Centralize availability handling in small adaptive design-system components
where practical.
## Quality
- Build after each task.
- Run relevant unit and UI tests after each task.
- Add tests for calculation, country mapping, statistics, backup, themes,
privacy masking, and version-adaptive presentation logic where testable.
- Compile with the deployment target set to iOS 17.0.
- Check that iOS 26-only symbols are not reachable from unguarded paths.
- Test at least one iOS 17 or iOS 18 simulator and one iOS 26 or later simulator
before release when those runtimes are available.
- Do not modify unrelated files.
- Do not leave dead code, placeholder lorem ipsum, or untracked generated files.
- If a test cannot run, report the exact command and reason.
- Finish each task with a concise summary of changed files and validation results.
29. Codex 开发阶段和任务提示词
以下任务必须按顺序执行。不要把所有阶段一次性交给 Codex。
阶段 0:创建工程骨架
Codex 提示词
阅读仓库中的完整产品规格和 AGENTS.md,然后创建 PostcardJourney 原生 iOS 工程。
要求:
1. 最低支持 iOS 17.0,并使用包含 iOS 26 SDK 的最新稳定 Xcode 或更新兼容版本。
2. 使用 SwiftUI App 生命周期。
3. 使用 SwiftData 空容器。
4. 创建五个原生 Tab:首页、记录、地图、统计、设置。
5. 每个 Tab 使用独立 NavigationStack。
6. 创建 feature-first 目录结构、DesignSystem 和 Core 目录。
7. 创建 Localizable.xcstrings,提供基础中文和英文页面标题。
8. 配置浅色、深色和跟随系统外观基础能力。
9. 建立跨版本 UI 基础:iOS 26 专用 API 必须使用可用性判断,低于 iOS 26 的受支持版本使用系统原生控件。
10. 不实现业务功能,不添加第三方依赖,不添加网络代码。
11. 创建根目录 AGENTS.md,内容严格按照规格。
12. 创建 README,说明项目定位、离线原则和构建要求。
13. 运行 xcodebuild 验证工程可编译,并确认部署目标仍为 iOS 17.0。
完成后列出:工程结构、构建命令、构建结果、未实现内容。
验收
- 可在模拟器启动。
- 五个 Tab 可切换。
- 导航和标签栏使用系统原生外观;iOS 26 及以上呈现原生 Liquid Glass,低于 iOS 26 的受支持版本呈现对应版本的原生 UI。
- 中文英文基础文本存在。
- 无第三方依赖。
- 无网络代码。
阶段 1:设计系统、主题和跨版本原生 UI 基础
Codex 提示词
实现项目的设计系统和主题系统,不实现业务页面。
要求:
1. 创建 AppTheme、ThemePreset、CodableColor、BackgroundStyle、AppAppearance。
2. 实现 System、Air Mail、Coast、Sunset、Forest、Lavender、Graphite、Custom 主题。
3. 实现 ThemeManager,并持久化用户选择。
4. 创建 ThemeBackgroundView,支持系统背景、纯色、线性渐变、径向渐变和轻量 Soft Mesh。
5. 创建语义颜色、间距、圆角、字体和动效 token。
6. 创建可复用 AdaptiveActionGroup、AdaptivePrimaryAction、StatCard、TagChip、CountryRow 和 EmptyStateView。
7. iOS 26 及以上的 Liquid Glass 只用于功能层;内容卡片不得普遍使用玻璃。
8. 低于 iOS 26 的受支持版本使用对应系统原生按钮、工具栏、菜单和材质,不得模拟 Liquid Glass。
9. 所有 iOS 26 专用 API 必须通过 `#available(iOS 26.0, *)` 或窄范围 `@available` 隔离。
10. 支持 Reduce Transparency、Increase Contrast、Reduce Motion。
11. 设置中先创建可运行的主题预览页面,并在高低版本分支中保持同一主题数据。
12. 添加主题序列化、恢复默认、颜色对比和可用性封装基础测试。
13. 构建并运行测试,确认部署目标仍为 iOS 17.0。
验收
- 主题可即时切换。
- 重启后保持。
- 自定义颜色可保存。
- 深色模式可用。
- iOS 26 玻璃按钮与背景有清晰层级;低版本原生按钮与背景同样清晰。
- Reduce Transparency 有替代外观,低于 iOS 26 的受支持版本不出现伪 Liquid Glass。
阶段 2:数据模型与持久化
Codex 提示词
实现明信片核心数据模型和 SwiftData 持久化。
严格只允许规格中的字段,不得增加邮资、币种、邮票、邮戳、完整地址、联系人、收藏、评分或特殊卡片字段。
要求:
1. 创建 PostcardRecord、PostcardDirection、PostcardTag。
2. 创建 SchemaV1 和版本化 MigrationPlan。
3. 创建 RecordRepository 和 TagRepository。
4. 实现旅行天数计算、旅行中状态、完成状态和 ID 脱敏。
5. 实现日期验证、距离验证和重复 ID 检查。
6. 建立内存 ModelContainer 测试。
7. 添加完整单元测试。
8. 构建并运行测试。
验收
- CRUD 正常。
- 重启数据不丢失。
- 日期计算正确。
- 重复 ID 可检测。
- 派生状态不冗余存储。
阶段 3:国家、国旗和标签资源
Codex 提示词
实现离线国家和标签数据。
要求:
1. 创建 countries.json,包含 ISO 代码、名称 key、洲、中心坐标和国旗资源名。
2. 将国家中英文名称加入 String Catalog。
3. 实现中文、英文和代码搜索。
4. 创建本地矢量国旗资源结构和资源校验。
5. TW 中文显示中国台湾,英文显示 Taiwan, China,国旗必须使用 flag_cn。
6. 添加内置标签和中英文翻译。
7. 实现自定义标签的创建、重命名、删除。
8. 添加资源完整性和 TW 特殊规则测试。
9. 构建并运行测试。
若仓库中暂时没有全部已授权国旗 SVG,先建立清晰资源清单、导入脚本接口和缺失资源测试;不要运行时下载。
验收
- 国家搜索正确。
- 所有国家有坐标。
- 资源缺失会在测试中失败。
- 台湾显示和国旗规则固定。
阶段 4:记录列表、表单和详情
Codex 提示词
实现完整的明信片记录流程。
要求:
1. 首页建立基础统计占位和快速记录入口。
2. 实现记录列表:全部、寄出、收到、旅行中。
3. 实现系统 searchable、筛选和排序。
4. 实现收到记录表单。
5. 实现寄出记录表单。
6. 寄出记录支持粘贴对方回复留言;不得后台读取剪贴板。
7. 实现国家选择、标签多选、自定义标签、日期、距离和备注。
8. 选择国家后自动写入中心坐标。
9. 实现详情页、编辑、复制和删除。
10. 删除使用系统确认交互。
11. 添加日期异常和重复 ID 提示。
12. 使用系统原生 UI 和 Toolbar;iOS 26 及以上由系统呈现 Liquid Glass,低于 iOS 26 的受支持版本使用对应版本原生外观。
13. 添加 UI 测试。
14. 构建并运行测试。
验收
- 一条记录可在 30–60 秒内完成。
- 不出现多余字段。
- 搜索和筛选正确。
- 旅行中和已完成状态正确。
- 双语无明显溢出。
阶段 5:图片系统
Codex 提示词
实现每条记录正面一张、背面一张的本地图片系统。
要求:
1. 使用 PhotosPicker。
2. 可选实现系统相机,但不得阻塞主要任务。
3. 修正图片方向。
4. 原图最长边 2400 px,生成 480 px 缩略图。
5. 优先 HEIF,必要时 JPEG。
6. 文件保存在 Application Support,SwiftData 只存相对文件名。
7. 支持选择、替换、删除和全屏查看。
8. 正反面切换使用原生过渡,并支持 Reduce Motion。
9. 删除记录时删除相关文件。
10. 替换失败时保留旧图。
11. 列表只加载缩略图。
12. 添加文件完整性和清理孤儿文件测试。
13. 构建并运行测试。
验收
- 每条记录最多两张图片。
- 数据库不保存图片 Blob。
- 删除无残留。
- 大列表滚动流畅。
阶段 6:完全离线地图
Codex 提示词
实现完全离线的世界地图,不使用在线瓦片和联网 Map 视图。
要求:
1. 解析本地简化 GeoJSON。
2. 使用 SwiftUI Canvas 或原生 Shape 渲染国家轮廓。
3. 支持缩放、拖动、点击国家。
4. 支持综合、寄出、收到和年份筛选。
5. 根据记录数量对国家分级填色。
6. 使用本地中心坐标创建默认标记。
7. 在记录编辑中提供地图位置调整页。
8. 支持点击或拖动标记,保存大致经纬度。
9. 设置中提供“我的默认位置”,默认在中国且不请求定位权限。
10. 点击单条记录时绘制简化旅程弧线。
11. 记录密集时聚合标记。
12. 支持主题色、深色模式和无障碍。
13. Reduce Motion 时移除弹跳和路线绘制动画。
14. 添加坐标投影、国家命中和性能测试。
15. 在飞行模式或网络不可用环境验证功能。
验收
- 地图完全离线。
- 500 条记录可用。
- 标记能调整并保存。
- 主题切换后颜色正确。
阶段 7:统计系统
Codex 提示词
实现 StatisticsService 和现代 Swift Charts 页面。
要求:
1. 实现全部和年度统计。
2. 核心数字:寄出、收到、旅行中、完成、国家数、总距离、平均/最快/最慢旅行天数和最远记录。
3. 实现月度趋势、国家排行、标签排行、旅行天数区间和距离区间。
4. 旅行中记录不参与完成旅程平均值。
5. 缺少距离的记录不参与距离平均值。
6. 数据为空或只有一条时不能崩溃或出现 NaN。
7. 图表使用主题语义色。
8. 为 VoiceOver 提供图表摘要。
9. 实现进入和年份切换动画,并支持 Reduce Motion。
10. 添加完整统计单元测试。
11. 构建并运行测试。
验收
- 统计结果可通过固定测试数据复算。
- 图表美观、现代、双语正确。
- 深色模式和高对比度可用。
阶段 8:分享卡
Codex 提示词
实现高质量明信片分享卡生成器。
要求:
1. 建立 Modern Journey、Air Mail、Photo Focus、Map Journey 四套模板。
2. 默认 1080×1440 PNG。
3. 使用 ImageRenderer,不使用屏幕截图。
4. 默认隐藏部分 ID。
5. 默认只使用正面照片。
6. 默认不显示备注、回复留言和精确坐标。
7. 支持模板、主题、照片裁切、国旗和标签开关。
8. 支持中文和英文模板。
9. 支持保存到照片和系统分享。
10. 无正面照片时也能生成完整设计。
11. 对导出像素尺寸、隐私字段和文本溢出添加测试。
12. 构建并运行测试。
验收
- 四套模板视觉明显不同。
- 输出清晰。
- 默认脱敏。
- 双语不溢出。
阶段 9:年度报告
Codex 提示词
实现 AnnualReportBuilder 和多页年度报告。
要求:
1. 页面包含封面、年度数字、月度趋势、年度地图、旅行之最、国家与标签、照片墙和结束页。
2. 每页 1080×1440 PNG。
3. 根据数据自动省略无内容页面或模块。
4. 少于 3 条记录时生成简化报告。
5. 照片墙支持 1、2、4、6、9 张自适应布局,并可手动替换。
6. 支持报告主题和中英文切换,且不修改全局主题。
7. 提供分页预览、单页保存、全部保存和系统分享。
8. 渲染显示真实进度并支持取消。
9. 避免同时保留全部高分辨率中间位图。
10. 添加年度报告页面生成规则和尺寸测试。
11. 构建并运行测试。
验收
- 无空白页。
- 无数据模块自动重排。
- 报告适合直接发布。
- 大量照片时内存稳定。
阶段 10:备份与恢复
Codex 提示词
实现 .postcardjourney 完整本地备份和恢复。
要求:
1. 备份包含 manifest、记录、标签、主题设置、图片和缩略图。
2. 导入先预览备份信息。
3. 支持合并和覆盖。
4. 合并按 UUID 和 updatedAt 处理。
5. 恢复必须原子化,失败不得破坏现有数据。
6. 覆盖前创建临时安全备份。
7. 检查缺失图片、损坏 JSON 和不支持的数据版本。
8. 显示真实进度并支持取消。
9. 添加备份、恢复、合并和损坏文件测试。
10. 构建并运行测试。
验收
- 恢复后记录和图片数量一致。
- 损坏备份不破坏现有数据。
- 合并规则稳定。
阶段 11:关于页面和品牌矢量图标
Codex 提示词
实现“关于”页面和作者信息。
作者:十七号旅客
小红书:https://www.xiaohongshu.com/user/profile/60d40af7000000000101f612
GitHub:https://github.com/Hercules-Zhaoziyi
主页:https://ziyis.cn/
Instagram:https://www.instagram.com/ithercules
要求:
1. 显示 App 图标、名称、版本和简介。
2. 显示作者名称。
3. 四个平台使用对应的本地矢量图标。
4. 图标资源名称使用 brand_xiaohongshu、brand_github、brand_website、brand_instagram。
5. 链接必须在用户点击后使用系统 openURL 打开,不得预加载或内嵌网页。
6. 支持长按复制链接。
7. 显示本地优先隐私说明和 Postcrossing 非官方声明。
8. 创建 ThirdPartyNotices.md,记录品牌图标、国旗和地图资源来源与许可。
9. 对链接常量、图标存在和中英文文案添加测试。
10. 构建并运行测试。
如果缺少已授权品牌 SVG,不要从不明来源下载;建立资源占位和发布阻断测试,并在报告中明确指出需要维护者提供正式素材。
验收
- 作者信息完全正确。
- 图标为本地矢量。
- 链接只在点击后打开。
- 关于页面支持双语和深色模式。
阶段 12:完整视觉、动画和无障碍打磨
Codex 提示词
对整个应用进行跨版本 Apple 原生 UI、iOS 26 原生 Liquid Glass、主题、动画和无障碍打磨。
要求:
1. 检查所有页面是否优先使用原生 NavigationStack、TabView、Toolbar、Form、Menu、Picker 和 Share UI。
2. 检查最低部署目标为 iOS 17.0,所有 iOS 26 专用符号都有正确可用性保护。
3. 移除不必要的自制玻璃、重复模糊背景和低版本伪 Liquid Glass。
4. iOS 26 及以上的 Liquid Glass 只保留在功能层;低于 iOS 26 的受支持版本使用对应系统原生表现。
5. 统一语义色、间距、圆角、字体和动效。
6. 完善首页、列表、详情、地图、统计、分享和年度报告的过渡动画。
7. 支持 Reduce Motion、Reduce Transparency、Increase Contrast、Dynamic Type 和 VoiceOver。
8. 修复中英文文本溢出。
9. 检查所有空状态、错误状态和加载状态。
10. 至少验证一个 iOS 17 或 iOS 18 模拟器,以及一个 iOS 26 或以上模拟器;运行时缺失必须明确报告。
11. 运行完整构建、单元测试和 UI 测试。
12. 输出发布前问题清单。
阶段 13:发布准备
Codex 提示词
完成 App Store 发布前工程检查。
要求:
1. 检查 Bundle Identifier、版本号和 Build 号配置位置。
2. 完善 PrivacyInfo.xcprivacy。
3. 检查照片和相机用途说明,仅保留实际需要权限。
4. 检查无 URLSession、WKWebView、在线地图和远程资源。
5. 检查所有外部 URL 仅存在于关于页面和许可文档白名单。
6. 检查应用图标、深色图标和有色图标资源。
7. 生成 App Store 中英文描述草稿、关键词、隐私说明和截图清单。
8. 运行 Release 构建和完整测试。
9. 生成最终 RELEASE_CHECKLIST.md。
10. 不自动上传 App Store,不修改签名证书。
30. Codex 工作流
30.1 每个任务开始前
Codex 必须:
- 阅读
AGENTS.md。 - 阅读本规格相关章节。
- 检查现有工程结构。
- 检查 Git 状态。
- 明确本阶段不做什么。
30.2 每个任务结束后
必须输出:
- 完成摘要。
- 修改文件列表。
- 数据模型变化。
- 运行的命令。
- 构建结果。
- 测试结果。
- 未验证内容。
- 已知问题。
30.3 推荐构建命令
先查看 Scheme:
xcodebuild -list -project PostcardJourney.xcodeproj
通用模拟器构建:
xcodebuild \
-project PostcardJourney.xcodeproj \
-scheme PostcardJourney \
-destination 'generic/platform=iOS Simulator' \
build
运行测试前先查找可用模拟器:
xcrun simctl list devices available
然后选择实际存在的设备,不得假设固定设备名称。
30.4 Git 要求
- 一阶段一个清晰提交或一组小提交。
- 不重写无关历史。
- 不提交 DerivedData。
- 不提交用户本地签名配置。
- 不提交临时导出图片和备份样本。
- 提交前保持工作区干净。
31. 发布前最终验收清单
功能
- [ ] 可新增收到记录。
- [ ] 可新增寄出记录。
- [ ] 寄出记录可后补收到日期和回复留言。
- [ ] 旅行天数正确。
- [ ] 搜索、筛选、排序正确。
- [ ] 正面和背面图片正常。
- [ ] 地图完全离线。
- [ ] 标记可自动生成和手动调整。
- [ ] 统计正确。
- [ ] 分享卡默认脱敏。
- [ ] 年度报告无空白页。
- [ ] 备份和恢复可靠。
- [ ] 中文英文完整。
- [ ] 关于页面作者信息正确。
设计
- [ ] 最低支持 iOS 17;iOS 26 及以上使用原生 Liquid Glass,低于 iOS 26 的受支持版本使用对应系统原生 UI。
- [ ] 内容层没有滥用玻璃,低版本没有自制伪 Liquid Glass。
- [ ] 主题预设可用。
- [ ] 用户可自定义主题和背景。
- [ ] 浅色和深色模式正常。
- [ ] 动画自然且可关闭。
- [ ] 分享图和年度报告达到可发布视觉质量。
隐私和离线
- [ ] 产品代码无主动网络请求。
- [ ] 无在线地图。
- [ ] 无远程字体和图片。
- [ ] 无账号。
- [ ] 无广告和埋点。
- [ ] 作者链接仅在用户点击后打开。
- [ ] 默认不分享背面、留言、备注和完整 ID。
稳定性
- [ ] 500 条记录列表流畅。
- [ ] 200 条带图记录可用。
- [ ] 删除记录无图片残留。
- [ ] 损坏备份不会破坏现有数据库。
- [ ] 报告渲染支持取消。
- [ ] 单元测试通过。
- [ ] UI 测试通过。
- [ ] Release 构建成功。
无障碍
- [ ] VoiceOver 可用。
- [ ] Dynamic Type 可用。
- [ ] Reduce Motion 可用。
- [ ] Reduce Transparency 可用。
- [ ] Increase Contrast 可用。
- [ ] 颜色不是唯一信息表达方式。
32. 1.0 版本完成定义
当且仅当以下条件全部满足,才能认为 1.0 完成:
- 所有“必须实现”功能通过验收。
- 没有出现被明确禁止的多余字段。
- 核心功能在飞行模式下可使用。
- 地图、国旗、品牌图标和国家数据全部本地化。
- 单张分享卡和年度报告达到可直接分享的视觉质量。
- 主题系统、跨版本原生 UI、iOS 26 Liquid Glass、深色模式和无障碍完整。
- 关于页面完整展示“十七号旅客”及所有指定链接。
- 备份与恢复经过真实设备或模拟器验证。
- 中英文所有关键路径无漏译。
- Release 构建和测试通过。
33. 官方设计与开发参考
实现时优先参考 Apple 和 OpenAI 官方资料:
-
Apple — Checking API availability(使用
#available和@available):
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/#Declaration-Attributes -
Apple Human Interface Guidelines — Materials:
https://developer.apple.com/design/human-interface-guidelines/materials -
Apple — Adopting Liquid Glass:
https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass -
Apple — Applying Liquid Glass to custom views:
https://developer.apple.com/documentation/SwiftUI/Applying-Liquid-Glass-to-custom-views -
Apple —
MARKDOWN_HASHd50c803178adfe0db454e5fc554924fdMARKDOWNHASH:
<https://developer.apple.com/documentation/swiftui/view/glasseffect(:in:)> -
Apple —
GlassEffectContainer:
https://developer.apple.com/documentation/swiftui/glasseffectcontainer -
Apple — Liquid Glass overview:
https://developer.apple.com/documentation/TechnologyOverviews/liquid-glass -
Apple — Landmarks: Building an app with Liquid Glass:
https://developer.apple.com/documentation/swiftui/landmarks-building-an-app-with-liquid-glass -
OpenAI — Introducing Codex / AGENTS.md guidance:
https://openai.com/index/introducing-codex/
34. 给 Codex 的最终总提示词
在创建好空仓库后,可以先把本文件和 AGENTS.md 放入仓库,再向 Codex发送:
你正在开发“卡片漂流簿 / Postcard Journey”。
请先完整阅读:
1. PostcardJourney_iOS_Codex_Development_Plan.md
2. AGENTS.md
本项目是最低支持 iOS 17 的原生 SwiftUI 应用。所有版本必须使用对应系统的 Apple 原生 UI;iOS 26 及以上使用原生 Liquid Glass,低于 iOS 26 的受支持版本不得模拟 Liquid Glass。项目使用 SwiftData、Swift Charts 和本地资源。核心功能完全离线,不得加入主动网络请求、在线地图、Postcrossing 登录、邮票邮资字段、完整地址或其他未在规格中列出的功能。
不要一次性完成全部项目。先只执行“阶段 0:创建工程骨架”。
执行要求:
- 在修改前说明计划。
- 完成后运行构建。
- 报告修改文件、构建命令、构建结果、未验证内容和下一步。
- 不得跳到阶段 1。
之后严格逐阶段提交对应提示词。
参与讨论