普通视图

发现新文章,点击刷新页面。
昨天以前首页

Tauri 应用首次上架 App Store 被驳回了 3 次(iOS)和 12 轮(macOS)的经历

作者 ssshooter
2026年5月26日 17:14

Mind Elixir 是一款基于 Tauri 框架的跨平台思维导图应用,支持 iOS、macOS、Linux、Windows、Android。免费用户最多创建 24 个思维导图,付费用户(订阅/终身许可)可解锁无限数量。付费功能通过官网购买,应用内登录账号解锁。

第一次把 Tauri 应用提交到 App Store,iOS 被驳回了 3 次,macOS 跟 Apple 来回沟通了 12 轮才通过。这篇文章记录整个过程中踩过的坑,希望能帮到同样在做跨平台付费应用的开发者。


iOS:3 次驳回,10 天通过

Round 1:登录体验 + 商业模式审查

4月15日收到驳回,涉及两个条款:

Guideline 4 — Design:应用把用户跳转到外部浏览器登录注册,Apple 认为体验不好。

当时我的登录逻辑是直接调起系统默认浏览器走 OAuth 流程。Apple 的要求是要么在应用内实现登录,要么用 ASWebAuthenticationSession 在应用内嵌浏览器完成。

Guideline 2.1(b) — Information Needed:Apple 看到应用有付费功能,但不确定商业模式,要求回答四个问题:

  1. 用户在哪里购买订阅?
  2. 用户能访问哪些已购内容?
  3. 有哪些付费内容不走 IAP?
  4. 用户如何注册账号?

我回复解释了跨平台架构——订阅通过官网购买,登录后解锁"无限思维导图"。

Round 2:IAP 产品没提交

4月17日再次驳回,条款 Guideline 2.1(b) — App Completeness。

这是个低级错误。我的应用里引用了 Annual Pass、Lifetime Pass 这些订阅产品,但我在 App Store Connect 里没有把这些 IAP 产品一起提交审核。Apple 直接说"我们没法继续审核,因为找不到这些 IAP"。

教训:提交应用审核的同时,必须把关联的 IAP 产品也一起提交,并且要提供审核截图。

Round 3:订阅信息不完整

4月20日第三次驳回,涉及两个条款:

Guideline 3.1.2(c) — Subscriptions:应用内缺少自动续期订阅的必要信息展示。Apple 要求必须展示:

  • 订阅服务标题
  • 订阅时长
  • 价格
  • Terms of Use 链接
  • Privacy Policy 链接

Apple 还建议使用 SubscriptionStoreView,可以一站式搞定这些信息展示。

Guideline 2.1(b) — Information Needed:还是找不到 IAP 产品。这次 Apple 明确说了,要在 sandbox 环境测试通过,并且确保 Paid Apps Agreement 已签署。

最终通过

4月21日第四次提交,4月23日通过。


macOS:12 轮拉锯战

macOS 的审核比 iOS 复杂得多。一方面是 macOS 的审核规则和 iOS 有差异,另一方面我用了 Tauri 框架打包,引入了一些 iOS 不存在的问题。

第一轮:三个条款同时命中

4月15日 / 16日收到驳回,一次性命中三个条款:

Guideline 2.4.5(vii) — Performance:应用包含了可能用于在 Mac App Store 之外更新应用的框架或 API。

这个是我用 Tauri 打包时带进来的。Tauri 默认会集成一些自动更新的依赖,即使我没有在代码里使用,打包后的二进制文件里仍然包含了这些框架。Mac App Store 要求所有更新必须通过 App Store 进行,不能有额外的更新检查机制。

Guideline 3.1.1 — In-App Purchase:Premium 会员可以通过官网购买,但应用内没有提供 IAP 购买选项。

Apple 的规则很明确:跨平台应用可以让用户访问在其他平台购买的内容,但前提是该内容也必须可以通过 IAP 购买(参考 Guideline 3.1.3(b))。不能只允许通过网站购买。

Guideline 4 — Design:和 iOS 一样的登录体验问题。

这个条款后来拉扯了好几轮,说起来挺搞笑的。我在 macOS 版已经用了 ASWebAuthenticationSession,但问题是 macOS 上的 ASWebAuthenticationSession 表现出来就是打开默认浏览器——这是 Apple 自己文档里写的行为,不是我的实现问题。但审核员看到"打开了浏览器",就直接判我没用 ASWebAuthenticationSession,反复驳回。

我后来把 Apple 官方文档里关于 macOS ASWebAuthenticationSession 行为的说明贴了回去,审核员才承认我确实用了,然后换了个理由继续驳回。笑死。

后续拉锯

这之后经历了多轮驳回和重新提交:

日期 动作 主要问题
4/21 Apple 驳回 继续追问细节
4/24 Apple 驳回 新问题出现
4/24 我回复 x2 提供更多信息
4/27 Apple 驳回 IAP 功能 bug
4/27 我回复 说明修复进展
4/29 Apple 驳回 订阅链接问题
4/30 我回复 补充元数据
5/2 Apple 驳回 最后几个问题
5/2 我最终回复 一次性解决所有问题

最终回复中解决的问题

  1. Guideline 2.1(b):修复了一个 bug——"升级"按钮在某些情况下会变灰无法点击
  2. Guideline 3.1.2(c):在 App Store 元数据的 app description 里添加了 Terms of Use 链接(https://desktop.mind-elixir.com/eula
  3. Guideline 4:详细解释了 Tauri 框架下使用 ASWebAuthenticationSession 的技术方案,说明这是 Tauri 应用做 OAuth 的标准做法
  4. Guideline 5.1.1(v):Apple 其实不推荐让用户必须登录后才能购买 IAP,他们认为这会增加购买摩擦。但我解释了跨平台场景下的实际需求——用户登录后购买,可以关联到账号,这样在其他平台(Linux、Windows、Web)也能直接使用,避免重复购买。Apple 最终接受了这个解释。

5月2日最终通过。


踩坑总结

IAP 相关

  • IAP 产品必须和应用一起提交。不要想着先提交应用再补 IAP,这会导致 Round 2 那种情况
  • 订阅页面必须展示完整信息。用 SubscriptionStoreView 可以省很多事
  • 跨平台应用必须同时提供 IAP 购买选项。即使你的主要销售渠道是官网,App Store 里也必须有 IAP

登录相关

  • 不要跳转外部浏览器登录。用 ASWebAuthenticationSession 在应用内完成
  • 支持注册的应用必须支持账号删除(Guideline 5.1.1(v))
  • Apple 不推荐"先登录再购买"。默认情况下,用户应该能直接购买 IAP,不需要先注册或登录。但如果你有合理的跨平台需求(比如购买需要关联账号以同步到其他平台),可以在回复中详细说明理由,Apple 会酌情考虑

macOS 专项

  • 检查打包依赖中是否包含更新框架。Tauri、Electron 等框架可能默认带入自动更新模块,需要在打包时排除
  • Mac App Store 禁止任何应用外更新机制

审核策略

  • 首次提交会收到更详细的反馈。Apple 会恭喜你加入开发者计划,同时可能一次性指出多个问题
  • 回复 Apple 消息时要详细、有条理。我最后一轮回复是一次性解决了四个条款的问题,之后就通过了
  • 准备好被驳回的心态。尤其是跨平台 + 付费模式的组合,审核会更严格
  • 每次回复都要把之前的问题重新说一遍。这是我踩的一个大坑——macOS 审核过程中,Apple 多次提出相同的问题,比如登录体验、IAP 覆盖等,明明我在之前的回复里已经解释过了,下一轮又会重新问。我不确定是每次 review 的审核员不同、上下文没延续,还是他们就希望你每次都确认。总之,不要假设审核员看过你之前的回复,每次提交补充信息时,把之前已回答的问题也一并带上,避免来回拉锯

审核时间线

iOS:
04/13  提交
04/15  ❌ 驳回(登录体验 + 商业模式)
04/17  ❌ 驳回(IAP 未提交)
04/20  ❌ 驳回(订阅信息不完整)
04/21  第四次提交
04/23  ✅ 通过

macOS:
04/15  提交
04/16  ❌ 驳回(更新机制 + IAP + 登录)
04/17 ~ 05/02  12 轮消息往来
05/02  ✅ 通过

整个过程最大的体会:跨平台应用上架 App Store,技术上要处理框架带来的隐式依赖(比如 Tauri 的更新模块),产品上要适配 Apple 的 IAP 生态(即使你的主力销售在官网),体验上要符合 Apple 的设计规范(登录流程不能跳外部浏览器)。三个维度缺一不可。

Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑

作者 ssshooter
2026年3月30日 23:06

很多开发者在把 Tauri 2 应用上架到 iOS(真机或模拟器)时,都会在文件保存这一步踩坑:明明代码代码在其他平台没问题,在 iOS 路径就返回 null,或者在「文件」App 里根本看不到自己的 App 文件夹。

下面我把最常见的几个坑总结成一份避坑科普文,帮你一次性避开这些“iOS 特色”问题。

坑 1:@tauri-apps/plugin-dialogsave() 在 iOS 上经常返回 null 或路径不可用

现象
调用 const path = await save({...}) 后,一个 0KB 的文件写入成功,但是 path 返回是 null

避坑方法

  • 不要过度依赖 dialog.save() 来实现“用户任意选择保存位置”。
  • 优先使用 直接写入 App 的 Documents 目录(见坑 3)。
  • capabilities 中确保开启 dialog:save 权限。

坑 2:文件明明写入了,但「文件」App 里完全看不到 “Mind Elixir” 文件夹

现象: 用了 BaseDirectory.Document 保存文件后,在「文件」App → 浏览 → On My iPhone 里找不到你的 App 文件夹。

原因: iOS 沙盒机制严格控制 App 的 Documents 目录是否对「文件」App 可见。Tauri 默认生成的 iOS 项目不会自动添加暴露文件夹的配置,就算你写再多文件,文件夹也不会出现。

避坑方法(最关键的一步): 在 Info.plist 中添加以下两个 key(必须同时添加):

<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

位置:通常在 src-tauri/gen/apple/ios/App/App/Info.plist(或你的项目对应路径),加在 <dict> 标签内,</dict> 之前。

添加后必须重新构建并安装 Appcargo tauri ios build 或用 Xcode 编译),然后:

  • 先执行一次写入操作(创建文件)。
  • 完全退出「文件」App(上滑关闭),重新打开并下拉刷新「On My iPhone」。

此时你应该能看到和 App 同名的文件夹(显示名称来自 productName 或 Xcode Display Name)。

注意:这两个 key 只控制可见性,不影响代码读写。

坑 3:iOS 上最好的保存方式其实不是 dialog,而是直接用 Documents 目录

推荐做法

import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'

await writeTextFile('my-note.md', '你的内容...', {
  dir: BaseDirectory.Document
})

优点:

  • 最稳定,几乎不会出现 0 字节文件的问题。
  • 用户可以在「文件」App 里直接看到和管理文件(添加上面两个 plist key 后)。
  • 无需处理复杂的 URI 和潜在的 fs bug。

如果你想让用户输入文件名,可以结合 prompt 或自定义输入框实现。

总结建议

在 Tauri 2 + iOS 开发中:

  1. 优先使用 BaseDirectory.Document 直接保存(最稳)。
  2. 必须在 Info.plist 添加 UIFileSharingEnabledLSSupportsOpeningDocumentsInPlace
  3. 谨慎使用 dialog.save() + writeFile,因为移动端兼容性还有待完善(官方 issue 仍在跟进)。
  4. 开发时多用控制台日志 + Safari/XCode 调试,遇到路径问题先检查 plist 和权限。

避开这几个坑后,你的 Mind Elixir(或其他 App)在 iOS 上的文件保存功能就会顺畅很多。iOS 的沙盒和文件系统规则和桌面差异很大,提前了解这些“Apple 特色”能省下大量调试时间。

(本文基于 Tauri 2 常见 issue 和实际开发经验总结,iOS 规则可能随系统版本微调,建议以 Apple 官方文档为准。)

❌
❌