跳转至

iOS 上架流程与构建文档

适用工程open_h5(Vue3 + Vite + Capacitor 7 的 H5 主工程),iOS 外壳工程位于 open_h5/ios/App文档构成: - 第一部分:从零注册 Apple 开发者账号 → 申请证书/描述文件等认证文件 → 提交审核上架的完整流程; - 第二部分:迁移自原 open_h5/README.md 的 iOS 环境、构建、打包、调试操作文档; - 第三部分:常见坑速查(极简)。

重要说明 1(核实状态):第一部分涉及 Apple 平台的注册、费用、证书与设备配额、隐私清单等政策条款,已于 2026-09-03 联网逐条核实,来源为 developer.apple.com/cn/help/account/developer.apple.com/cn/support/ 系列官方页面,已核实的条目在文中标注「已核实」。核实过程改掉了此前两处实质性错误(分发证书数量、设备 100 台的口径),并新发现一条上架硬伤(Capacitor 属 Apple 强制隐私清单 SDK,见 6.4)。

本网络环境核实不到、仍保留「未验证」的项developer.apple.com/cn/app-store/review/ 抓取到的正文为空,故审核时长未取到官方值;/cn/support/upcoming-requirements//cn/support/icp//cn/help/account/membership/renew-your-membership//cn/help/account/provisioning-profiles/create-an-app-store-connect-provisioning-profile/ 四个页面返回无正文的空壳页,相关条目(描述文件有效期、即将生效的要求、ICP 说明)保留未验证;beian.miit.gov.cn 返回 HTTP 521。

重要说明 2本文第二部分的操作步骤整理自原 README.md 的既有记录(这些步骤是团队此前在 macOS 上实际跑通过的)。


目录


第一部分 上架完整流程

1. 流程总览

iOS 上架和 Android 最大的区别:Android 的签名文件是你自己在本地生成的,iOS 的签名文件必须由 Apple 签发。所以 iOS 一定是「先注册付费账号 → 再申请证书 → 才能装到真机 / 上架」,没有账号连真机调试都受限。

准备资质材料
   └─ 营业执照 / 身份证、D-U-N-S 编号(公司账号)、软件著作权、隐私政策、App 备案
        ↓
注册 Apple Developer Program 账号(付费,年费制)
        ↓
账号审核通过(个人较快;公司需核验 D-U-N-S 与法人授权)
        ↓
在 Mac 上用「钥匙串访问」生成 CSR 请求文件
        ↓
Apple Developer 后台申请四件套:
   ├─ Certificates    证书(Development 开发 / Distribution 发布)
   ├─ Identifiers     App ID(Bundle ID = com.example.app)
   ├─ Devices         测试设备 UDID(真机调试 / Ad Hoc 需要)
   └─ Profiles        描述文件(Development / Ad Hoc / App Store)
        ↓
Xcode「Signing & Capabilities」配置签名(Team + Bundle ID)
        ↓
构建 H5 产物 → 同步到 iOS → Xcode Archive
        ↓
Distribute App → 上传到 App Store Connect
        ↓
App Store Connect 填写元数据 / TestFlight 内部测试
        ↓
提交审核 → 通过 → 发布

2. 上架前需要准备的材料

材料 说明 个人账号 公司账号
身份证 / 护照 实名认证用 必须 法人 + 联系人
营业执照 公司主体资质 不需要 必须
D-U-N-S 编号 邓白氏编码,Apple 用来核验公司法人实体身份,申请公司账号的前置条件 不需要 必须(详见 3.2)
法人授权书 若申请人不是法人,Apple 可能要求提供 不需要 可能需要
软件著作权证书 中国大陆区上架游戏/部分品类需要;非游戏类通常不强制。已核实:Apple 官方文档未把软著列为必需项,国内同类平台(华为)也明确其为「非必选资质」,仅游戏品类必需(软著 + 版号) 视品类 视品类
App 备案号 中国工信部要求,中国大陆分发的 App 需完成备案;未备案会被要求下架。已核实:备案在云服务商(接入商)的备案系统办理,不在应用商店办;多个包名须逐个备案(详见 7.3) 需要 需要
隐私政策链接 必须是可公网访问的 URL,App Store Connect 里是必填项 必须 必须
应用图标 1024×1024 PNG,不能带 Alpha 通道、不能带圆角 必须 必须
应用截图 按 Apple 要求的机型尺寸提供(至少 6.7 英寸 iPhone 一组) 必须 必须
演示账号 审核员登录用的测试账号密码,本项目是登录态应用,必须提供 必须 必须
支付方式 支持国际支付的信用卡(缴年费) 必须 必须

备案与资质的官方口径以工信部与 Apple 最新要求为准。本次核实说明:beian.miit.gov.cn 返回 HTTP 521 无法直接取原文,备案规则改以华为官方《APP 备案指引》为来源(工信部要求对各分发渠道一致);Apple 侧的 /cn/support/icp/ 页面已无正文。

个人账号 vs 公司账号的实际差别(决定要不要折腾 D-U-N-S):

维度 个人(Individual) 组织(Organization / 公司)
App Store 上显示的开发者名 个人姓名 公司名称
D-U-N-S 编号 不需要 必须
团队协作 只有你一个人,不能加成员 可以在 App Store Connect 里加多个成员、分配角色
部分能力 不支持企业内部分发等能力 支持更完整
审核难度 简单 需核验企业身份,周期更长

本项目是公司产品(应用名「<应用名>」),应当走组织账号,否则 App Store 上会挂个人姓名。

3. 注册 Apple 开发者账号

3.1 注册入口与整体顺序

  1. 打开 https://developer.apple.com/
  2. 先注册一个 Apple ID(如果公司已有专用 Apple ID 就直接用,不要用个人私人 Apple ID);
  3. 给这个 Apple ID 开启双重认证(Two-Factor Authentication)——已核实,Apple 官方把「已启用双重认证的 Apple 账户」和「达到所在地区的法定成年年龄」列为加入开发者计划的两个前置条件,没开双重认证无法加入;
  4. 用这个 Apple ID 加入 Apple Developer Program已核实:年费 99 美元,公司与个人同价;Apple Developer Enterprise Program 为 299 美元)。中国大陆开发者需要发票可致电 400-666-8800,退税事宜联系 salestax@apple.com
  5. 填写主体信息,个人选 Individual,公司选 Organization(这一步之后就要填 D-U-N-S 编号)。
  6. 🔴 已核实的组织注册硬要求:必须是法人实体,Apple 不接受 DBA(商号)、虚构名称、假名或分支机构名称;必须有 D-U-N-S 编号;申请人必须具备约束授权(有权代表组织签署法律协议,否则需法人授权书);必须使用组织域名的工作邮箱;必须有公开可访问的官方网站,且域名与组织名称相关联。Apple 还可能额外要求提供经公证的商业文件
  7. 已核实:若以个人/独资身份注册,你的个人法定姓名会作为供应商名称显示在 App Store 上(这也是本项目必须走组织账号的原因)。
  8. 支付年费 → 等 Apple 审核 → 收到「Welcome to the Apple Developer Program」邮件即开通。
  9. 🔴 已核实的关键差异个人/独资可以在注册流程中直接购买会员资格;组织必须等 Apple Developer Support 完成身份验证并发来邮件之后,才能购买会员资格。也就是说公司账号在「提交材料」和「能付钱」之间存在一段人工等待,无法自助跳过。

强烈建议:这个 Apple ID 用公司公共邮箱注册(比如 dev@公司域名),密码和双重认证手机号做好交接记录。这个账号一旦丢了,已上架的 App 拿不回来。

README.md 里有一条经验:「Mac 账户和开发者账户保持一致」——指的是 Xcode 里登录的 Apple ID 要和申请证书的开发者账号是同一个,否则 Xcode 拉不到你的证书和描述文件。

3.2 D-U-N-S 编号(公司账号必须)

D-U-N-S(邓白氏编码)是邓白氏公司发放的全球企业唯一标识,Apple 用它来确认「这家公司真实存在,且申请人有权代表它」。已核实:Apple 官方表述为「D-U-N-S 编号在大多数司法管辖区可免费申请」,不要被第三方代办的收费误导。

申请要点:

  1. 先查是否已有:Apple 提供查询入口(https://developer.apple.com/enroll/duns-lookup/),输入公司法定名称、国家/地区搜索。很多公司其实已经被登记过了,查到就直接用,不用重新申请。
  2. 查不到再申请:在上述页面提交公司法定名称、注册地址、法人、联系电话、公司网站等。填写的公司名和地址必须和营业执照完全一致(包括括号、简繁体),不一致是最常见的失败原因。
  3. 等待:Apple 官方文档未给出承诺时长(未验证:公开资料一般说 5 个工作日左右,实际可能更久);邓白氏可能会打电话到你填的公司电话核实,务必确保这个电话能接通
  4. 拿到编号后再回到 Apple 的 Enroll 流程填进去。

3.3 注册过程中常见卡点

现象 原因 处理
Enroll 页面不让选 Organization Apple ID 没开双重认证,或年龄/地区信息不全 补全 Apple ID 信息、开双重认证
提交后一直「Pending」 Apple 在核验 D-U-N-S 与法人信息 查企业邮箱(含垃圾箱),Apple 会发邮件要补材料
Apple 要求「法人授权证明」 申请人不是法人本人 出具公司授权书(Apple 会给模板要求),法人签字盖章
付费失败 国内借记卡不支持 换支持境外支付的信用卡
公司名对不上 D-U-N-S 里登记的名称和营业执照不一致 先去邓白氏更正 D-U-N-S 信息,再回 Apple

3.4 账号内的角色说明

开通后在 App Store Connect(https://appstoreconnect.apple.com/)里管理成员,常用角色:

  • Account Holder(账号持有人):唯一,付款和法律责任主体,能做所有事;
  • Admin(管理员):能管成员、管证书、能提交审核;
  • App Manager:能管具体 App 和提交版本,不能管账号;
  • Developer:能上传构建版本,不能提交审核;
  • Marketing / Sales / Finance:只读或财务相关。

开发人员日常打包上传给 Developer 就够;证书和描述文件的管理权限尽量收在 1~2 个人手上,避免有人手滑吊销(Revoke)了正在用的发布证书。


4. 先理解 iOS 的签名体系

这一节不是流程,是概念。不理解这四个东西的关系,后面每一步都会踩坑

概念 后台位置 作用 类比 Android
Certificate(证书) Certificates 证明「这段代码是我这个开发者签的」。分 Development(开发调试)和 Distribution(分发上架) 类似 release.jks 里的密钥对,但必须 Apple 签发
Identifier(App ID) Identifiers 注册你的 Bundle ID,并声明这个 App 要用哪些能力(推送、iCloud、Sign in with Apple…) 类似 applicationId + 权限声明
Device(设备) Devices 登记允许安装非上架包的真机 UDID Android 没有对应概念
Provisioning Profile(描述文件) Profiles 把「证书 + App ID + 设备列表」打包成一个授权凭证,装进 App 里 Android 没有对应概念

关键关系:

CSR(本机生成,含公钥)
   │  上传到 Apple
   ▼
Certificate(.cer,Apple 用它证明公钥属于你)
   │  必须和本机钥匙串里的私钥配对才可用
   ├──────────────┐
   ▼              ▼
Development     Distribution
   │              │
   └──── + App ID + Devices ────┐
                                ▼
                     Provisioning Profile
                       ├─ Development  → 真机调试
                       ├─ Ad Hoc       → 发给指定设备的测试包
                       └─ App Store    → 上架 / TestFlight

最重要的一条:证书的私钥只在你生成 CSR 的那台 Mac 的钥匙串里。换电脑后即使从 Apple 后台重新下载 .cer,没有私钥也签不了包。所以:

  • 要么把证书连私钥一起导出成 .p12 文件保管好(见 5.7);
  • 要么换机后重新生成 CSR 重新申请证书(发布证书有数量上限,滥用会被卡)。

🔴 证书数量限制(已核实,此前的写法是错的):Apple 官方原文为「分发证书属于团队每个团队只能有一种类型的分发证书(Developer ID 证书除外)。只有账户持有人或管理角色可以创建分发证书(如果你以个人身份注册,你即为账户持有人)。」

也就是说不存在「2~3 个额度」这回事 —— 一个团队的 Apple Distribution 证书只有一个,"新建"的实质就是替换掉团队现有的那一个。Revoke 发布证书会导致依赖它的描述文件全部失效,正在用的千万别动。

与之相对,开发证书属于个人:同一团队里每个人各有自己的开发证书,Apple 会在证书名后追加电脑名以区分(如 Gita Kumar (Work Mac))。

5. 申请认证文件(证书 / App ID / 设备 / 描述文件)

以下操作在 macOS + 浏览器上完成,后台地址:https://developer.apple.com/account/resources/

5.1 第一步:在 Mac 上生成 CSR

CSR(Certificate Signing Request,证书签名请求)是一个包含你公钥和身份信息的文件,Apple 拿它签出证书。

  1. 打开「钥匙串访问」(Keychain Access,在「应用程序 → 实用工具」里,或 Spotlight 搜 Keychain);
  2. 菜单栏 → 钥匙串访问 → 证书助理 → 从证书颁发机构请求证书… (英文:Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…)
  3. 填写:
  4. 用户电子邮件地址:填开发者账号邮箱;
  5. 常用名称(Common Name):起个能认出来的名字,例如 Example iOS Distribution 2026
  6. CA 电子邮件地址:留空;
  7. 请求方式:选 存储到磁盘(Saved to disk),并勾选「让我指定密钥对信息」;
  8. 下一步,密钥大小 2048 位,算法 RSA
  9. 保存得到 CertificateSigningRequest.certSigningRequest 文件。

这一步同时会在你的钥匙串「密钥」分类里生成一对公钥/私钥。私钥别删,删了证书就废了。

5.2 第二步:申请证书(Certificates)

后台 → Certificates → 左上 +

要做的事 选哪个类型 说明
真机调试 Apple 开发(Apple Development) 装到自己登记的设备上跑
上架 / TestFlight Apple 分发(Apple Distribution) 发布包用。已核实:后台里的「iOS 开发 / iOS 分发」已被官方标注为「适用于 Xcode 11 及更早版本」,新工程一律选「Apple 开发 / Apple 分发」
推送(本项目暂未用) Apple Push Notification service SSL 需要时再申请

步骤:

  1. 选类型 → Continue;
  2. 上传刚才的 .certSigningRequest 文件 → Continue;
  3. Download 得到 .cer 文件(如 distribution.cer);
  4. 双击 .cer 导入钥匙串。导入后在钥匙串「我的证书」里能看到证书下面挂着一把私钥(可以展开三角箭头看到)——能展开看到私钥,才算真的可用

常见错误:钥匙串里证书显示「此证书是由未知颁发机构签名的」。原因是缺少 Apple 的中间证书(Apple Worldwide Developer Relations Certification Authority)。到 https://www.apple.com/certificateauthority/ 下载对应的 WWDR 中间证书双击安装即可。已核实2021-01-28 之后签发的证书走的是新的 WWDR 媒介证书,该媒介证书于 2030-02-20 到期;更早签发的证书仍依赖旧媒介证书,两者可以并存,别把旧的删掉。

5.3 第三步:注册 App ID(Identifiers)

后台 → Identifiers → + → 选 App IDs → 选 App

  • Description:填应用描述,例如 QiYuXingHe Mobile(这里不能填中文);
  • Bundle ID:选 Explicit(显式),填 com.example.app ——必须和工程里 PRODUCT_BUNDLE_IDENTIFIER 完全一致,也要和 capacitor.config.tsappId 一致;
  • Capabilities:勾选这个 App 需要的能力。本项目当前用到的插件(相机、文件、分享、状态栏、键盘、震动、启动图、热更新)都不需要额外勾特殊 Capability,保持默认即可;如果以后加推送、Sign in with Apple、后台模式,要回来勾上并重新生成描述文件。

Bundle ID 一旦注册就不能删除、不能改(只能停用),所以别拿正式包名做测试。要试手就用 com.example.app.test 之类。

本工程实际值(已核对 ios/App/App.xcodeproj/project.pbxproj):PRODUCT_BUNDLE_IDENTIFIER = com.example.app

5.4 第四步:登记测试设备(Devices)

只有 Development 和 Ad Hoc 需要;上架包(App Store 类型)不需要。

  1. 拿 UDID:iPhone 连 Mac → 打开 Finder(或 Xcode → Window → Devices and Simulators)→ 点设备名可以切换显示 UDID → 右键复制;
  2. 后台 → Devices → + → Platform 选 iOS → 填 Device Name 和 UDID → Continue → Register。

🔴 设备数量规则(已核实,此前的写法不准确):Apple 官方原文为「在每个会员资格年度,对于每个产品系列,Apple Developer Program 和 Apple Developer Enterprise Program 会员可以注册最多 100 台以下设备:Apple TV / Apple Vision Pro / Apple Watch / iPad / iPhone / iPod touch / Mac」。要点:

  • 是「每个产品系列各 100 台」,不是所有设备合计 100 台;
  • 停用(Disable)设备并不会增加可用设备数量
  • 新的会员资格年度开始后,具有「账户持有人 / 管理 / App 管理」职能的人首次登录「证书、标识符和描述文件」时,可以移除设备并把每个产品系列的计数复位到 100。这是一年一次的机会,当次不做就得等下个年度;
  • 会员资格到期前 30 天可以下载设备列表,或选择在到期时立即移除设备;若不做任何选择,到期日 180 天后 Apple 会自动移除全部设备

其他已核实细节:注册设备需要「账户持有人或管理」职能;支持批量导入 Apple Configurator 2 导出的 .deviceids 文件或制表符分隔的 .txtApple 芯片的 Mac 需要「预置 UDID(Provisioning UDID)」,不是普通 UDID。

5.5 第五步:创建描述文件(Profiles)

后台 → Profiles → +,按用途选类型:

类型 用途 需要什么
iOS App Development Xcode 连真机调试 Development 证书 + App ID + Devices
Ad Hoc 打包 .ipa 发给指定设备装(不走商店) Distribution 证书 + App ID + Devices
App Store Connect 上架 / TestFlight Distribution 证书 + App ID(不选设备

步骤(以 App Store 类型为例):

  1. App Store Connect → Continue;
  2. 选 App ID:com.example.app → Continue;
  3. 选证书:选刚申请的 Apple Distribution → Continue;
  4. Provisioning Profile Name,建议带用途和日期,例如 Example AppStore 20260903
  5. Generate → Download 得到 .mobileprovision 文件;
  6. 双击导入(Xcode 会自动放到 ~/Library/MobileDevice/Provisioning Profiles/)。

描述文件有效期:Apple 帮助文档未明写具体天数(未验证:通常跟随证书有效期,约 1 年;本次要查的那个官方页面已无正文)。过期后 App 装不上、也传不上去,需要重新生成。日历上记一下到期时间,别等发版时才发现。

🔴 已核实的重要机制(针对 2021-06-06 之后创建的团队):用开发或 Ad Hoc 描述文件签名的 App,首次启动时需要联网通过 Apple 的 PPQ 验证,因此本地网络与防火墙必须放通 https://ppq.apple.com。Apple 为离线场景提供有效期 7 天的离线预置描述文件;若设备需要离线超过 30 天,得单独向 Apple 申请。另外所有预置描述文件现在都包含 DER 编码版本,iOS 15 及以上的部分 entitlement 可能要求使用 DER 版本。

5.6 第六步(可选):自动签名 vs 手动签名

本工程当前用的是自动签名(已核对:CODE_SIGN_STYLE = Automatic)。

自动签名(Automatically manage signing) 手动签名(Manual)
谁管证书和描述文件 Xcode 自动申请、下载、续期 你自己在后台建,手动选
优点 省事,个人和小团队首选;本项目已配好,直接用 可控,CI/CD 和多人协作可复现
缺点 Xcode 会自作主张新建证书;由于每个团队每种类型只有一个分发证书,这等于直接顶掉团队现有的分发证书,多人协作时互相踩 步骤繁琐,过期要手动换
要填什么 只要选 Team 要选 Provisioning Profile + Signing Certificate

建议:本地开发用自动签名;如果以后接 CI(比如 GitHub Actions 打 iOS 包),必须改成手动签名,并把 .p12 + .mobileprovision 作为加密 Secret 注入。

5.7 认证文件的导出与保管

要换电脑或者给同事用,不能只给 .cer,必须导出带私钥的 .p12

  1. 钥匙串访问 → 「我的证书」找到目标证书;
  2. 右键 → 导出
  3. 格式选 个人信息交换(.p12)
  4. 设置导出密码(这个密码要单独记,导入时要用);
  5. 得到 xxx.p12

保管要求(和 Android 的 release.jks 同等重要):

  • .p12 + 导出密码 + .mobileprovision 三样,存到公司密码管理器 / 加密盘,至少两处异地备份
  • 绝对不能提交到 Git 仓库(本项目开源,尤其注意);
  • 谁能拿到要有记录,人员离职要评估是否吊销重签;
  • Apple 账号的双重认证设备/手机号也要有备份人。

丢失 .p12 的后果比 Android 丢 .jks 轻一些——Apple 允许你 Revoke 旧证书重新申请,App 不会因此无法更新。但 Revoke 会让所有依赖它的描述文件失效,正在灰度/审核的版本会受影响。

6. 把签名配置接入工程

6.1 Xcode 图形界面(当前项目走这条)

参考原 README 的操作,打开 ios/App/App.xcodeproj 后:

  1. 左侧选中 App 这个 Target;
  2. 打开 Signing & Capabilities 标签页;
  3. 勾选 Automatically manage signing
  4. Team 下拉选择你的公司团队;
  5. 确认 Bundle Identifiercom.example.app
  6. 如果报红(比如「No profiles for 'com.example.app' were found」),点旁边的 Try Again,或检查 5.3 的 App ID 是否注册过。

配置签名

6.2 工程里签名相关的实际配置项

已核对 ios/App/App.xcodeproj/project.pbxproj,与签名/版本相关的关键构建设置:

配置项 当前值 说明
PRODUCT_BUNDLE_IDENTIFIER com.example.app 必须与 App ID、capacitor.config.tsappId 一致
CODE_SIGN_STYLE Automatic 自动签名
DEVELOPMENT_TEAM 公司的 10 位 Apple Team ID(本文不回显具体值 开源前必须清空
CODE_SIGN_IDENTITY iPhone Developer 项目级默认值
IPHONEOS_DEPLOYMENT_TARGET 14.0 最低支持 iOS 14
MARKETING_VERSION 1.0 对应 CFBundleShortVersionString,即用户看到的版本号
CURRENT_PROJECT_VERSION 1 对应 CFBundleVersion,构建号
SWIFT_VERSION 5.0
TARGETED_DEVICE_FAMILY 1,2 1=iPhone,2=iPad,即支持双端

上架版本号规则:每次上传到 App Store Connect,CURRENT_PROJECT_VERSION(构建号)必须比上一次大,否则会被拒收(提示 build already exists)。MARKETING_VERSION 是给用户看的,同一个 marketing version 下可以有多个构建号。

6.3 Info.plist 里必须写清楚的权限说明

这是 iOS 审核最高频的驳回点:用了某个隐私能力但没写用途说明,或者说明写得敷衍(比如只写「用于相机」),会被以「隐私说明不充分」驳回。

本工程 ios/App/App/Info.plist 已配置的隐私说明(已核对):

Key 当前文案 对应能力
NSCameraUsageDescription 用于扫码配置服务器 相机(扫码)
NSPhotoLibraryUsageDescription 用于从相册选择二维码图片配置服务器 相册读取

其他相关配置(已核对):

Key 作用
CFBundleDisplayName mobile-app 桌面显示名。注意:这个值和 capacitor.config.tsappName(<应用名>)不一致,上架前需要确认要显示哪个
UIFileSharingEnabled true 允许「文件」App 访问沙盒 Documents——这是日志能在「文件 → 我的 iPhone → mobile-app」里看到的原因
LSSupportsOpeningDocumentsInPlace true 同上,配合文件浏览
UILaunchStoryboardName LaunchScreen 启动页 storyboard
UIStatusBarHidden / UIStatusBarStyle true / LightContent 状态栏,配合 @capacitor/status-bar
UISupportedInterfaceOrientations 竖屏 + 左右横屏 注意与 Android 的 screenOrientation="portrait"(锁竖屏)不一致
UIRequiredDeviceCapabilities armv7 Capacitor 默认值
NSAppTransportSecurityNSExceptionDomains 配了一个公司服务域名的 HTTP 例外(NSTemporaryExceptionAllowsInsecureHTTPLoads = true,最低 TLS 1.2);本文不回显该域名 见下方警告

⚠️ ATS 例外是审核风险点NSTemporaryExceptionAllowsInsecureHTTPLoads 表示允许对该域名走非 HTTPS。Apple 审核可能会要求你说明理由,或直接驳回。上架前的正确做法是让服务端上 HTTPS,然后把这段例外删掉

如果以后加了定位、麦克风、通讯录等能力,要同步补 NSLocationWhenInUseUsageDescriptionNSMicrophoneUsageDescription 等,文案要具体说明「拿来干什么」

6.4 隐私清单 PrivacyInfo.xcprivacy

🔴 已核实,并且这是本工程当前最硬的一个上架卡点。 Apple 官方《需要隐私清单和签名的 SDK》名单中明确包含 Capacitor(同一份名单里还有 CordovaSDWebImage)。官方规则是:

  • 提交包含名单内 SDK 的新 App,或提交新增了名单内 SDK 的更新时,必须包含隐私清单
  • 名单内 SDK 作为二进制依赖使用时,还必须带签名

对应到本工程:

  • 本工程是 Capacitor 7,正好落在这份名单内;
  • ios/App/App/ 目录下当前没有 PrivacyInfo.xcprivacy(已核对工程文件);
  • 因此首次提交 App Store 会因缺少隐私清单被卡。这不是"可能收到警告邮件"级别的问题,而是名单内 SDK 的强制要求;
  • 此外本项目用到 @capacitor/filesystem(访问文件时间戳/磁盘空间)、@capgo/capacitor-updater(读写文件),落在必要理由 API(Required Reason API)范围内,隐私清单里要逐项填 API 类型和理由码;
  • 处理办法:Xcode 里 New File → App Privacy 创建 PrivacyInfo.xcprivacy,按实际情况填写 NSPrivacyAccessedAPITypesNSPrivacyCollectedDataTypes;同时确认 Capacitor 及各插件的 pod 自身是否已带隐私清单与签名,没带的要升到带隐私清单的版本。

7. App Store Connect 创建应用

地址:https://appstoreconnect.apple.com/

7.1 新建 App

「我的 App」→ + → 新建 App:

字段 填法
平台 iOS
名称 App Store 上显示的名字(全局唯一,被占用就得换)。最长 30 字符
主要语言 简体中文
套装 ID(Bundle ID) 下拉选 com.example.app——只有在 Developer 后台注册过 App ID 才会出现在这个下拉里
SKU 内部编号,用户看不到,自己定一个唯一串(如 example-ios-001
用户访问权限 完全访问权限

7.2 版本信息(必填清单)

说明
截图 按机型尺寸上传。至少要 6.7 英寸 iPhone 一组;有 iPad 支持(本项目 TARGETED_DEVICE_FAMILY = 1,2)则还要提供 iPad 截图,不然无法提交
描述 / 关键词 / 推广文本 商店文案
支持网址 / 营销网址 支持网址必填,要能打开
版本号 MARKETING_VERSION 对应
版权 2026 <公司全称>
分级问卷 内容分级,逐项回答
App 隐私 「数据收集」问卷:逐项声明是否收集(联系信息、标识符、使用数据…)。必须和 App 实际行为一致,虚假声明是重点打击对象
隐私政策 URL 必填,必须可访问
登录信息 勾「需要登录」→ 填演示账号密码。本项目是登录态应用,不填这个必被拒
备注(App Review Information) 给审核员的补充说明,例如「本 App 需要连接企业私有服务器,服务器地址已在演示账号中预置」
出口合规(Export Compliance) 问你是否使用加密。用了 HTTPS 一般可走「仅使用 Apple 提供/豁免的加密」,需在 Info.plist 里加 ITSAppUsesNonExemptEncryption = false 来免除每次询问(未验证:具体豁免口径以 Apple 表述为准)
广告标识符(IDFA) 问是否使用 IDFA。本项目没接广告 SDK,选「否」

7.3 中国大陆区上架的额外要求

在中国大陆区上架,需要额外准备:

  • App 备案号(工信部备案)。已核实的通用规则(来源:华为官方《APP 备案指引》,工信部要求对各分发渠道一致):备案不在应用商店办,在云服务商(接入商)的备案系统办 —— 华为云/阿里云/腾讯云/移动云/天翼云/联通云;存在多个包名时所有包名均需备案;备案的包名、应用名称、主体信息必须与在架信息一致同一主体下不同 App 名称不可重复,同一款 App 在不同运行平台下名称应保持一致。本项目 iOS 与 Android 同为 com.example.app、鸿蒙为 com.example.app_hm两个包名都要备案;若安卓/iOS 已备案而后补鸿蒙,需申请变更备案
  • 无需备案的两类(已核实):单机应用(未通过公共互联网提供互联网信息服务)、境外应用(境外主体运营且服务器仅置于境外)。
  • 实操坑(已核实):填「主体证件号」时要分清数字 5 与字母 S、数字 1 与字母 I、数字 0 与字母 O,这是最常见的填错项。
  • 部分品类需要软件著作权证书或行业许可证:游戏需版号;金融/医疗/新闻/影视/直播/招聘/地图等需对应资质;深度合成或生成式 AI 还需互联网信息服务算法备案https://beian.cac.gov.cn/)、《安全评估报告》以及显式标识截图或《人工智能生成合成内容标识说明函》。

备案是国内独有环节,耗时可能比 Apple 审核长得多,要提前启动。

核实说明:beian.miit.gov.cn 本次访问返回 HTTP 521,Apple 的 /cn/support/icp/ 页面已无正文,上述规则以华为官方备案指引为来源。

8. 打包上传(Archive → Distribute)

8.1 Xcode 图形界面方式

  1. 先按第二部分把 H5 产物构建并同步进 iOS 工程(npm run buildnpx cap sync ios);
  2. Xcode 顶部设备选择器选 Any iOS Device (arm64)——必须是这个,选模拟器时 Archive 是灰的
  3. 菜单 Product → Archive
  4. 构建完成后自动弹出 Organizer 窗口(也可以 Window → Organizer 打开);
  5. 选中刚生成的 Archive → 点 Distribute App
  6. 选分发方式:
  7. App Store Connect → 上架/TestFlight(最常用);
  8. Ad Hoc → 导出 .ipa 给登记过的设备安装;
  9. Development → 内部调试导出;
  10. Enterprise → 仅企业开发者账号;
  11. Upload(直接传)或 Export(先导出 ipa,再用 Transporter 传);
  12. 走完签名确认 → Upload → 等进度条。

8.2 命令行方式(CI 用)

# 1. 归档
xcodebuild -workspace ios/App/App.xcworkspace \
  -scheme App \
  -configuration Release \
  -archivePath build/App.xcarchive \
  archive

# 2. 导出 ipa(需要一个 ExportOptions.plist)
xcodebuild -exportArchive \
  -archivePath build/App.xcarchive \
  -exportPath build/ipa \
  -exportOptionsPlist ExportOptions.plist

# 3. 上传(二选一)
xcrun altool --upload-app -f build/ipa/App.ipa -t ios \
  -u <AppleID> -p <应用专用密码>
# 或新版工具
xcrun notarytool / Transporter

注意用 .xcworkspace 而不是 .xcodeproj——本项目用了 CocoaPods,必须走 workspace,否则找不到 Pods。

ExportOptions.plist 最小示例(App Store 分发):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>你的 Team ID</string>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

未验证:altool--upload-app 参数在新版 Xcode 中已提示弃用,改用应用专用密码(App-Specific Password)或 API Key(--apiKey / --apiIssuer)。以本机 Xcode 版本的实际提示为准。

8.3 TestFlight 内部测试

上传成功后(未验证:处理需要几分钟到几十分钟),构建版本会出现在 TestFlight 里:

  • 内部测试已核实上限 100 名内部测试员,成员需具备「账户持有人 / 管理 / App 管理 / 开发者 / 营销」职能,不需要 Apple 审核,可以立刻装;
  • 外部测试已核实最多 10,000 名外部测试员;🔴 第一个构建版本必须先获得 App Review 针对 TestFlight 的批准才能发给外部测试员;
  • 已核实的其他配额:一个 App 最多可分享 100 个构建版本给测试员,每个测试员最多可在 30 台设备上安装;

强烈建议先走一轮内部 TestFlight,确认「装上能起、能连服务器、能登录」再提交正式审核。iOS 审核被拒一次要重排队。

9. 提交审核与常见驳回原因

9.1 提交

版本信息填完 + 选好构建版本 → 右上角 添加以供审核 / 提交以供审核

审核状态流转:

准备提交 → 正在等待审核 → 正在审核 → 待发布 / 已拒绝
已核实:Apple 官方文档没有承诺固定审核时长developer.apple.com/cn/app-store/review/ 页面本次抓取正文为空,未取到官方统计值)。经验值 24~48 小时属未验证信息,节假日和大版本期(如 iOS 新版本发布前)会更慢。

发布方式可选: - 自动发布:审核通过立即上架; - 手动发布:通过后停在「待发布」,你点按钮才上; - 定时发布:指定日期。

建议选手动发布,给自己留一个「审核通过但先别上」的缓冲。

9.2 常见驳回原因(结合本项目实际情况)

驳回原因 与本项目的关系 处理
需要登录但没给演示账号 本项目就是登录态 App,高危 App Review Information 里填可用的账号密码,并确认审核期间账号有效
需要连接私有服务器,审核员连不上 本项目连的是企业服务器 备注里说明清楚;确保服务器公网可达、演示账号能登;必要时提供服务器配置二维码截图
隐私说明不充分 已配相机/相册说明,但文案偏简短 NSCameraUsageDescription 等文案写具体(「用于扫描二维码以配置企业服务器地址」)
App 隐私问卷与实际不符 本项目会存 user_info(账号、登录时间)到 localStorage 在「App 隐私」里如实声明标识符/联系信息的收集
明文 HTTP 传输 Info.plist 有 ATS 例外 上 HTTPS 并删除例外,见 6.3
动态下发代码(热更新) 本项目用 @capgo/capacitor-updater 做热更新 重点:Apple 允许更新 JS/资源,但不允许改变 App 主要功能或绕过审核。要保证热更新只更新已审核功能的 Web 资源,不新增未审核功能;不要在审核期间下发差异内容
仅是网站的封装(4.2 最低功能性) 本项目是 WebView 壳,高危 强调调用了原生能力(相机扫码、文件读写、分享、震动、状态栏、启动页),并在描述中体现原生价值
iPad 上无法正常使用 TARGETED_DEVICE_FAMILY = 1,2 声明支持 iPad 要么真的适配 iPad 并提供截图,要么改成只支持 iPhone(1 省事
崩溃 / 白屏 Web 资源没同步进包时会白屏 Archive 前一定确认 npx cap sync ios 已执行、ios/App/App/public 里有资源(见 13 节)
元数据问题 截图与实际界面不符、含其他平台字样(如出现「Android」「安卓」) 检查截图和描述文案

9.3 被拒之后

  • 在 App Store Connect 的「解决方案中心」看 Apple 的具体理由(会附截图/日志);
  • 纯元数据问题(截图、描述、隐私问卷)改完直接重新提交,不需要重新打包;
  • 代码问题要改代码 → 提高 CURRENT_PROJECT_VERSION → 重新 Archive 上传;
  • 认为判断有误可以在解决方案中心申辩,或提交 App Review Board 申诉。

10. 上架后的版本更新

  1. 改代码 → 提升版本号:
  2. 功能更新:MARKETING_VERSION1.01.1
  3. 构建号 CURRENT_PROJECT_VERSION 必须递增
  4. 重新 npm run buildnpx cap sync ios → Archive → 上传;
  5. App Store Connect「我的 App」→ 点 + 版本 创建新版本 → 填「此版本的新增内容」→ 选新构建版本 → 提交审核;
  6. 也可以用分阶段发布(Phased Release),让新版本按天逐步推给用户,出问题可以暂停。

关于走热更新而不是走商店更新

本项目集成了 @capgo/capacitor-updatercapacitor.config.tsautoUpdate: falsestatsUrl: '')。

  • 能做:修 Web 层的 bug、调样式、改文案,不用过审;
  • 不能做:新增未审核的功能模块、改变 App 用途、绕过 Apple 审核机制。这是 App Store 审核指南明确禁止的(未验证:对应 2.5.2 / 3.1.1 等条目),违规风险是直接下架
  • 原生层的改动(新增插件、改 Info.plist、改权限)必须走商店更新,热更新覆盖不到。

第二部分 构建与打包操作文档

本部分迁移自原 open_h5/README.md,并补充了工程实际配置的核对结果。 本部分的操作步骤本次未实测(无 macOS 环境),原文来自团队此前在 macOS 上的实际操作记录。

11. 环境要求

要求 备注
操作系统 macOS(原文记录使用 26.0.1) Windows 无法直接编译 iOS,这是硬性限制
Node / npm Node ≥ 20 建议用 nvm 管理,版本过高过低都可能报错
Capacitor CLI 随项目依赖安装 管理 iOS/Android 平台的构建与同步
Xcode iOS 16+ 对应版本 从 App Store 或 Apple Developer 下载
CocoaPods 必须 iOS 的依赖管理器,Capacitor 插件靠它引入
真机 原文记录用 iPhone6 调试 需要能传数据的数据线;工程 IPHONEOS_DEPLOYMENT_TARGET = 14.0,iPhone6 最高只能到 iOS 12,实际调试请用支持 iOS 14+ 的设备
开发者账号 需申请注册 iOS 开发者账户 Mac 上 Xcode 登录的 Apple ID 要和开发者账号保持一致
编辑器 VSCode 前端部分的构建命令在 VSCode 里执行

Capacitor 工程结构

路径 说明
open_h5/ H5 主工程(Vue3 + Vite),业务代码都在这里
open_h5/ios/ iOS 平台外壳工程
open_h5/ios/App/App.xcodeproj Xcode 工程文件
open_h5/ios/App/App.xcworkspace 接了 CocoaPods 后要用这个打开
open_h5/ios/App/App/Info.plist iOS 配置与权限说明
open_h5/ios/App/App/public/ 同步进来的 H5 产物(由 cap sync 生成)
open_h5/ios/App/Podfile CocoaPods 依赖清单
open_h5/android/ Android 平台外壳工程
open_h5/capacitor.config.ts Capacitor 配置:appIdappNamewebDir、各插件配置
open_h5/src/capacitor/ 项目里调用 Capacitor 插件能力的封装文件
open_h5/dist/ Vite 构建产物(webDir 指向它)

本项目使用的 Capacitor 插件(iOS 侧,已核对 ios/App/Podfile

Pod npm 包 用途
Capacitor @capacitor/core + @capacitor/ios 核心运行时
CapacitorCordova 同上 Cordova 插件兼容层
CapacitorApp @capacitor/app 应用生命周期
CapacitorDialog @capacitor/dialog 原生弹窗
CapacitorFilesystem @capacitor/filesystem 文件读写
CapacitorHaptics @capacitor/haptics 震动
CapacitorKeyboard @capacitor/keyboard 键盘
CapacitorShare @capacitor/share 分享
CapacitorSplashScreen @capacitor/splash-screen 启动画面
CapacitorStatusBar @capacitor/status-bar 状态栏
CapgoCapacitorUpdater @capgo/capacitor-updater 热更新,远端拉 dist.zip 并切换版本

Podfile 关键设置(已核对):platform :ios, '14.0'use_frameworks!install! 'cocoapods', :disable_input_output_paths => true(用于避免 Xcode 缓存 Pods 的问题)。

capacitor.config.ts 里的 iOS 相关配置(已核对)

配置 说明
appId com.example.app 必须与 Bundle ID 一致
appName <应用名>
webDir dist 构建产物目录
ios.scheme <应用名>
ios.contentInset automatic 内容自动避让安全区
ios.appendUserAgent Iosappmessenger 服务端可据此识别 iOS 客户端
ios.limitsNavigationsToAppBoundDomains false
插件 SplashScreenlaunchShowDuration: 3000launchAutoHide: falselayoutName: splash、全屏沉浸)、StatusBarstyle: DARKoverlaysWebView: true)、Keyboardresize: none)、CapacitorUpdaterautoUpdate: false)、CapacitorAssetsresources/logo.pngresources/splash.png

12. 开发工具

  1. Xcode(iOS 16+):编译、签名、Archive、真机调试都在这里;
  2. 真机:数据线连 Mac,Xcode 能识别后即可调试;
  3. VSCode:执行前端构建命令(npm run buildnpx cap sync ios)。

拉到项目后先装依赖:npm install。原 README 还提到「链接本地仓库 npm link 到此项目」——本项目依赖内部组件库 <内部组件库>,如果用 link 方式开发需要执行这一步。

13. 构建步骤

构建这部分操作在 VSCode 里执行。

这是还没执行 build 和 add ios 命令之前的文件夹状态:

未执行文件状态

# 1. 安装依赖(首次)
npm install

# 2. 项目打包,产出 dist
npm run build

# 3. 添加 iOS 平台(仅首次、且项目里没有 ios 目录时)
npx cap add ios

# 4. 同步 Web 产物和插件到 iOS
npx cap sync ios

# 5. 打开 Xcode
npx cap open ios

npm run build 实际执行的是(已核对 package.json):

node prune-sourcemaps.cjs && vite build && node importmap.js
最后一步 importmap.js 会把 importmap 替换成 CDN 地址。

第 5 步也可以不用命令,直接双击打开工程文件:

open_h5/ios/App/App.xcodeproj

但因为项目用了 CocoaPods,推荐打开 open_h5/ios/App/App.xcworkspace,否则可能出现找不到 Pods 的编译错误。

public 目录问题(首次构建的核心坑)

  • 如果在 npx cap add ios 之前项目里已经有 webDir(即 dist存在,Capacitor 会在 add 阶段自动拷贝一次 Web 资源到 iOS 的 public 目录;
  • 如果 npx cap add iosnpm run build 之前执行,就不会有 public 目录资源,必须再执行 npx cap sync ios 才会同步所有资源

没有 public 的状态:

没有public

有 public 的状态:

有public

  • 从 Git 上拉取的代码已经带了配置好的 ios 目录,就不要再执行 npx cap add ios,可能导致重置、丢掉已有配置(签名、Info.plist 改动等);
  • 有新的配置要重来时,把 iosdist 都删掉,再重新走 build → add → sync 流程。

Android 的首次构建 public 目录问题与 iOS 完全一致,原 README 里 Android 部分没有重复写,就是参考 iOS 这段。

14. 打包调试

把构建成功的项目在 Xcode 中操作:

第 1 步:在项目的「Signing & Capabilities」标签页配置签名(详见第 6 节)

配置签名

第 2 步:用可以传输数据的数据线将真机连接到 Mac 后,Xcode 可以识别到手机,然后就可以真机调试

调试运行操作

第 3 步:在构建成安装包后,要在手机「设置」中打开开发者模式 (路径一般是:设置 → 隐私与安全性 → 开发者模式 → 打开 → 重启手机)

第 4 步:在手机设置中找到 APP,对手机弹出的提示点击信任构建后的 APP

第 2 步操作后会遇到:

第2步操作后会遇到

根据提示进行操作:

根据提示进行操作

点击信任:

点击信任

手机上的路径一般是:设置 → 通用 → VPN与设备管理 → 开发者 App → 点开你的证书 → 信任。

第 5 步:信任后再去 Xcode 执行一次安装就可以了。

15. 应用图标与启动页

参考 https://github.com/ionic-team/capacitor-assets

npm install --save-dev @capacitor/assets
  • 主要依赖 @capacitor/assets 插件;
  • public/manifest.webmanifest 是由 capacitor/assets 命令生成的,对应生成的还有 icons 目录;
  • 该工具要求项目根目录中存在一个 assetsresources 文件夹,不能改成其他文件名
  • 本项目 capacitor.config.ts 里配置的资源是 resources/logo.pngresources/splash.png

iOS 配置启动页遇到错误时,可以在构建完成之后直接贴图配置,替换掉红框内的图片:

启动页贴图配置

上架用的 App Store 图标是 1024×1024 PNG,不能带 Alpha 通道、不能自己做圆角,否则上传时会被 ITMS 报错拒收。

16. 注意事项

  1. iOS 环境下需要安装 CocoaPods(iOS 开发的依赖管理器)。
  2. 建议安装 nvm 统一管理 Node 版本,版本过高会导致报错。
  3. Mac 打包时可能遇到 Node.js 堆内存溢出(OOM):JS 堆被顶到约 2GB 上限,Mark-Compact 连续失败,最终触发 FATAL OOM。解决方式是提升 Node 堆上限:
    export NODE_OPTIONS="--max-old-space-size=8192"
    
  4. 注意环境变量的配置,确保项目能正常运行(.env.development / .env.production 等里的 VITE_BASE_APIVITE_IAMVITE_AGENT_VERSION)。
  5. 安装 Homebrew(macOS 包管理器,CocoaPods 通常靠它装):
    /bin/bash -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
    
    (原 README 用的是国内 Gitee 镜像脚本)
  6. 日志位置
  7. Android:文件管理 → documents → app
  8. iOS:文件 → 我的 iPhone → mobile-app(这个名字来自 Info.plistCFBundleDisplayName,能看到是因为开了 UIFileSharingEnabled
  9. 滑动退出在 iOS 环境下不生效(已知差异,不是 bug)。
  10. iOS 首次构建的 public 目录问题与 Android 一致,见第 13 节。

第三部分 常见坑

  1. Xcode 里登录的 Apple ID 必须和开发者账号是同一个(Mac 账户和开发者账户保持一致),否则 Xcode 拉不到证书和描述文件。
  2. 从 git 拉取的代码已带配置好的 ios 目录,不要再执行 npx cap add ios,可能重置、丢掉已有配置;若 cap addnpm run build 之前执行过,必须再 npx cap sync ios 同步资源,否则白屏。
  3. Mac 打包可能遇到 Node 堆内存溢出(OOM)export NODE_OPTIONS="--max-old-space-size=8192"
  4. 工程必须用 App.xcworkspace 打开(项目用了 CocoaPods),用 .xcodeproj 会找不到 Pods。
  5. CURRENT_PROJECT_VERSION(构建号)每次上传必须递增,否则被拒收(提示 build already exists)。
  6. 工程当前缺少 PrivacyInfo.xcprivacy 隐私清单:Capacitor 属 Apple 强制隐私清单 SDK 名单,首次提交会被卡,见 6.4。
搜索