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. 流程总览
- 2. 上架前需要准备的材料
- 3. 注册 Apple 开发者账号
- 4. 先理解 iOS 的签名体系
- 5. 申请认证文件(证书 / App ID / 设备 / 描述文件)
- 6. 把签名配置接入工程
- 7. App Store Connect 创建应用
- 8. 打包上传(Archive → Distribute)
- 9. 提交审核与常见驳回原因
- 10. 上架后的版本更新
- 第二部分 构建与打包操作文档
- 11. 环境要求
- 12. 开发工具
- 13. 构建步骤
- 14. 打包调试
- 15. 应用图标与启动页
- 16. 注意事项
- 第三部分 常见坑
第一部分 上架完整流程
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 注册入口与整体顺序
- 打开 https://developer.apple.com/;
- 先注册一个 Apple ID(如果公司已有专用 Apple ID 就直接用,不要用个人私人 Apple ID);
- 给这个 Apple ID 开启双重认证(Two-Factor Authentication)——已核实,Apple 官方把「已启用双重认证的 Apple 账户」和「达到所在地区的法定成年年龄」列为加入开发者计划的两个前置条件,没开双重认证无法加入;
- 用这个 Apple ID 加入 Apple Developer Program(已核实:年费 99 美元,公司与个人同价;Apple Developer Enterprise Program 为 299 美元)。中国大陆开发者需要发票可致电 400-666-8800,退税事宜联系
salestax@apple.com; - 填写主体信息,个人选 Individual,公司选 Organization(这一步之后就要填 D-U-N-S 编号)。
- 🔴 已核实的组织注册硬要求:必须是法人实体,Apple 不接受 DBA(商号)、虚构名称、假名或分支机构名称;必须有 D-U-N-S 编号;申请人必须具备约束授权(有权代表组织签署法律协议,否则需法人授权书);必须使用组织域名的工作邮箱;必须有公开可访问的官方网站,且域名与组织名称相关联。Apple 还可能额外要求提供经公证的商业文件。
- 已核实:若以个人/独资身份注册,你的个人法定姓名会作为供应商名称显示在 App Store 上(这也是本项目必须走组织账号的原因)。
- 支付年费 → 等 Apple 审核 → 收到「Welcome to the Apple Developer Program」邮件即开通。
- 🔴 已核实的关键差异:个人/独资可以在注册流程中直接购买会员资格;组织必须等 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 编号在大多数司法管辖区可免费申请」,不要被第三方代办的收费误导。
申请要点:
- 先查是否已有:Apple 提供查询入口(
https://developer.apple.com/enroll/duns-lookup/),输入公司法定名称、国家/地区搜索。很多公司其实已经被登记过了,查到就直接用,不用重新申请。 - 查不到再申请:在上述页面提交公司法定名称、注册地址、法人、联系电话、公司网站等。填写的公司名和地址必须和营业执照完全一致(包括括号、简繁体),不一致是最常见的失败原因。
- 等待:Apple 官方文档未给出承诺时长(未验证:公开资料一般说 5 个工作日左右,实际可能更久);邓白氏可能会打电话到你填的公司电话核实,务必确保这个电话能接通。
- 拿到编号后再回到 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 拿它签出证书。
- 打开「钥匙串访问」(Keychain Access,在「应用程序 → 实用工具」里,或 Spotlight 搜 Keychain);
- 菜单栏 → 钥匙串访问 → 证书助理 → 从证书颁发机构请求证书… (英文:Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…)
- 填写:
- 用户电子邮件地址:填开发者账号邮箱;
- 常用名称(Common Name):起个能认出来的名字,例如
Example iOS Distribution 2026; - CA 电子邮件地址:留空;
- 请求方式:选 存储到磁盘(Saved to disk),并勾选「让我指定密钥对信息」;
- 下一步,密钥大小 2048 位,算法 RSA;
- 保存得到
CertificateSigningRequest.certSigningRequest文件。
这一步同时会在你的钥匙串「密钥」分类里生成一对公钥/私钥。私钥别删,删了证书就废了。
5.2 第二步:申请证书(Certificates)
后台 → Certificates → 左上 +:
| 要做的事 | 选哪个类型 | 说明 |
|---|---|---|
| 真机调试 | Apple 开发(Apple Development) | 装到自己登记的设备上跑 |
| 上架 / TestFlight | Apple 分发(Apple Distribution) | 发布包用。已核实:后台里的「iOS 开发 / iOS 分发」已被官方标注为「适用于 Xcode 11 及更早版本」,新工程一律选「Apple 开发 / Apple 分发」 |
| 推送(本项目暂未用) | Apple Push Notification service SSL | 需要时再申请 |
步骤:
- 选类型 → Continue;
- 上传刚才的
.certSigningRequest文件 → Continue; - Download 得到
.cer文件(如distribution.cer); - 双击
.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.ts的appId一致; - 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 类型)不需要。
- 拿 UDID:iPhone 连 Mac → 打开 Finder(或 Xcode → Window → Devices and Simulators)→ 点设备名可以切换显示 UDID → 右键复制;
- 后台 → 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文件或制表符分隔的.txt;Apple 芯片的 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 类型为例):
- 选 App Store Connect → Continue;
- 选 App ID:
com.example.app→ Continue; - 选证书:选刚申请的 Apple Distribution → Continue;
- 填 Provisioning Profile Name,建议带用途和日期,例如
Example AppStore 20260903; - Generate → Download 得到
.mobileprovision文件; - 双击导入(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:
- 钥匙串访问 → 「我的证书」找到目标证书;
- 右键 → 导出;
- 格式选 个人信息交换(.p12);
- 设置导出密码(这个密码要单独记,导入时要用);
- 得到
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 后:
- 左侧选中 App 这个 Target;
- 打开 Signing & Capabilities 标签页;
- 勾选 Automatically manage signing;
- Team 下拉选择你的公司团队;
- 确认 Bundle Identifier 是
com.example.app; - 如果报红(比如「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.ts 的 appId 一致 |
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.ts 的 appName(<应用名>)不一致,上架前需要确认要显示哪个 |
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 默认值 |
NSAppTransportSecurity → NSExceptionDomains |
配了一个公司服务域名的 HTTP 例外(NSTemporaryExceptionAllowsInsecureHTTPLoads = true,最低 TLS 1.2);本文不回显该域名 |
见下方警告 |
⚠️ ATS 例外是审核风险点:
NSTemporaryExceptionAllowsInsecureHTTPLoads表示允许对该域名走非 HTTPS。Apple 审核可能会要求你说明理由,或直接驳回。上架前的正确做法是让服务端上 HTTPS,然后把这段例外删掉。如果以后加了定位、麦克风、通讯录等能力,要同步补
NSLocationWhenInUseUsageDescription、NSMicrophoneUsageDescription等,文案要具体说明「拿来干什么」。
6.4 隐私清单 PrivacyInfo.xcprivacy
🔴 已核实,并且这是本工程当前最硬的一个上架卡点。 Apple 官方《需要隐私清单和签名的 SDK》名单中明确包含 Capacitor(同一份名单里还有 Cordova、SDWebImage)。官方规则是:
- 提交包含名单内 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,按实际情况填写NSPrivacyAccessedAPITypes与NSPrivacyCollectedDataTypes;同时确认 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 图形界面方式
- 先按第二部分把 H5 产物构建并同步进 iOS 工程(
npm run build→npx cap sync ios); - Xcode 顶部设备选择器选 Any iOS Device (arm64)——必须是这个,选模拟器时 Archive 是灰的;
- 菜单 Product → Archive;
- 构建完成后自动弹出 Organizer 窗口(也可以 Window → Organizer 打开);
- 选中刚生成的 Archive → 点 Distribute App;
- 选分发方式:
- App Store Connect → 上架/TestFlight(最常用);
- Ad Hoc → 导出
.ipa给登记过的设备安装; - Development → 内部调试导出;
- Enterprise → 仅企业开发者账号;
- 选 Upload(直接传)或 Export(先导出 ipa,再用 Transporter 传);
- 走完签名确认 → 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 提交
版本信息填完 + 选好构建版本 → 右上角 添加以供审核 / 提交以供审核。
审核状态流转:
准备提交 → 正在等待审核 → 正在审核 → 待发布 / 已拒绝
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. 上架后的版本更新
- 改代码 → 提升版本号:
- 功能更新:
MARKETING_VERSION从1.0→1.1; - 构建号
CURRENT_PROJECT_VERSION必须递增; - 重新
npm run build→npx cap sync ios→ Archive → 上传; - App Store Connect「我的 App」→ 点 + 版本 创建新版本 → 填「此版本的新增内容」→ 选新构建版本 → 提交审核;
- 也可以用分阶段发布(Phased Release),让新版本按天逐步推给用户,出问题可以暂停。
关于走热更新而不是走商店更新
本项目集成了 @capgo/capacitor-updater(capacitor.config.ts 里 autoUpdate: false,statsUrl: '')。
- 能做:修 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 配置:appId、appName、webDir、各插件配置 |
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 |
|
| 插件 | SplashScreen(launchShowDuration: 3000、launchAutoHide: false、layoutName: splash、全屏沉浸)、StatusBar(style: DARK、overlaysWebView: true)、Keyboard(resize: none)、CapacitorUpdater(autoUpdate: false)、CapacitorAssets(resources/logo.png、resources/splash.png) |
12. 开发工具
- Xcode(iOS 16+):编译、签名、Archive、真机调试都在这里;
- 真机:数据线连 Mac,Xcode 能识别后即可调试;
- VSCode:执行前端构建命令(
npm run build、npx 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 ios在npm run build之前执行,就不会有 public 目录资源,必须再执行npx cap sync ios才会同步所有资源;
没有 public 的状态:

有 public 的状态:

- 从 Git 上拉取的代码已经带了配置好的
ios目录,就不要再执行npx cap add ios,可能导致重置、丢掉已有配置(签名、Info.plist 改动等); - 有新的配置要重来时,把
ios和dist都删掉,再重新走 build → add → sync 流程。
Android 的首次构建 public 目录问题与 iOS 完全一致,原 README 里 Android 部分没有重复写,就是参考 iOS 这段。
14. 打包调试
把构建成功的项目在 Xcode 中操作:
第 1 步:在项目的「Signing & Capabilities」标签页配置签名(详见第 6 节)

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

第 3 步:在构建成安装包后,要在手机「设置」中打开开发者模式 (路径一般是:设置 → 隐私与安全性 → 开发者模式 → 打开 → 重启手机)
第 4 步:在手机设置中找到 APP,对手机弹出的提示点击信任构建后的 APP
第 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目录;- 该工具要求项目根目录中存在一个
assets或resources文件夹,不能改成其他文件名; - 本项目
capacitor.config.ts里配置的资源是resources/logo.png和resources/splash.png。
iOS 配置启动页遇到错误时,可以在构建完成之后直接贴图配置,替换掉红框内的图片:

上架用的 App Store 图标是 1024×1024 PNG,不能带 Alpha 通道、不能自己做圆角,否则上传时会被 ITMS 报错拒收。
16. 注意事项
- iOS 环境下需要安装 CocoaPods(iOS 开发的依赖管理器)。
- 建议安装 nvm 统一管理 Node 版本,版本过高会导致报错。
- Mac 打包时可能遇到 Node.js 堆内存溢出(OOM):JS 堆被顶到约 2GB 上限,Mark-Compact 连续失败,最终触发 FATAL OOM。解决方式是提升 Node 堆上限:
export NODE_OPTIONS="--max-old-space-size=8192" - 注意环境变量的配置,确保项目能正常运行(
.env.development/.env.production等里的VITE_BASE_API、VITE_IAM、VITE_AGENT_VERSION)。 - 安装 Homebrew(macOS 包管理器,CocoaPods 通常靠它装):
(原 README 用的是国内 Gitee 镜像脚本)
/bin/bash -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" - 日志位置:
- Android:文件管理 → documents → app
- iOS:文件 → 我的 iPhone → mobile-app(这个名字来自
Info.plist的CFBundleDisplayName,能看到是因为开了UIFileSharingEnabled) - 滑动退出在 iOS 环境下不生效(已知差异,不是 bug)。
- iOS 首次构建的 public 目录问题与 Android 一致,见第 13 节。
第三部分 常见坑
- Xcode 里登录的 Apple ID 必须和开发者账号是同一个(Mac 账户和开发者账户保持一致),否则 Xcode 拉不到证书和描述文件。
- 从 git 拉取的代码已带配置好的
ios目录,不要再执行npx cap add ios,可能重置、丢掉已有配置;若cap add在npm run build之前执行过,必须再npx cap sync ios同步资源,否则白屏。 - Mac 打包可能遇到 Node 堆内存溢出(OOM):
export NODE_OPTIONS="--max-old-space-size=8192"。 - 工程必须用
App.xcworkspace打开(项目用了 CocoaPods),用.xcodeproj会找不到 Pods。 CURRENT_PROJECT_VERSION(构建号)每次上传必须递增,否则被拒收(提示 build already exists)。- 工程当前缺少
PrivacyInfo.xcprivacy隐私清单:Capacitor 属 Apple 强制隐私清单 SDK 名单,首次提交会被卡,见 6.4。