收到上传错误:
ITMS-90870: Missing launch screen
最快解法:如果本次 App 使用 iOS 27 SDK 或更高版本构建,就在最终 App 的 Info.plist 中加入 Apple 认可的启动屏配置;然后检查 Release Archive 内的 Info.plist 和资源,最后重新上传 TestFlight 验证。
本周建议动作:生产链路暂时仍使用 iOS 26 SDK 的项目,可以先保持现状,但应立即在独立的 Xcode 27 测试分支迁移。不要等到正式发版当天才处理 iOS 27 启动屏缺失。
最后更新于 2026 年 8 月 27 日,版本与上传边界核实自 Apple TN3208 启动屏技术说明、iOS/iPadOS 27 Release Notes 与 App Store Connect Release Notes。
这篇适合准备使用 Xcode 27 测试或发布现有 iOS App、但项目仍依赖旧启动图配置的独立开发者。
如果项目包含多个 Target、扩展、Build Configuration 或自动生成的 Info.plist,这篇也适合用来确认真正提交的二进制是否合规。
受影响范围:构建 SDK 与最低部署版本
先判断是否真正触发规则。触发条件是使用 iOS 27 SDK 或更高版本构建 iPhone、iPad App,不是把最低部署版本设置为 iOS 27。 即使项目仍支持较旧的 iOS 版本,只要 Archive 使用了 iOS 27 SDK,也要检查启动屏声明。
Apple 在 TN3208 中明确说明,使用 iOS 27 SDK 或更高版本构建的 App 必须在 Info.plist 中提供启动屏配置;缺少认可键时,App Store Connect 会返回 ITMS-90870。最低部署版本和构建 SDK 是两套不同指标,不能互相替代。
当前应把构建链路拆成三条,而不是把所有“Xcode 27”都当成生产发布链路:
| 构建链路 | 当前可执行结论 | 需要保留的验收动作 |
|---|---|---|
| Xcode 26 正式生产链路 | 仍使用 iOS 26 SDK 构建时,可暂时维持现有提交流程 | 记录当前 Xcode、SDK、Scheme 与 Archive 入口 |
| Xcode 27 Beta 测试链路 | Xcode 27 Beta 5 构建已支持上传 TestFlight | 先验证启动屏声明,再验证 TestFlight 处理结果 |
| 未来正式生产切换 | Xcode 27 正式版发布日期及生产 App Store 接收 iOS 27 SDK 构建的具体日期,当前资料尚未确认 | 以 App Store Connect 最新支持列表为准 |
Apple 的 Xcode 系统要求页面显示,Xcode 27 Beta 4 需要 macOS Tahoe 26.4 或更高版本,并包含 iOS 27 SDK。测试环境可能因此同时受到 macOS、Xcode 和项目构建设置的约束。(Apple Xcode 系统要求)
最低部署版本设置成 iOS 15 或 iOS 16,还需要配置启动屏吗?
如果最终使用 iOS 27 SDK 构建,仍然需要。Deployment Target 决定 App 支持哪些系统版本,SDK 决定这次构建采用哪套系统开发工具和服务器校验边界。
声明方式:自动生成与旧 storyboard
Apple 认可的启动屏键包括:
UILaunchScreenUILaunchStoryboardNameUILaunchStoryboardsUILaunchScreens
至少存在一个有效键,才能满足结构性校验。但“出现一个键”不等于修复完成。键值、Storyboard 文件名、资源名称和 Target 成员关系,都必须与最终 App 实际包含的资源一致。
配置选择可以按下面的条件判断:
| 项目现状 | 优先方案 | 不要做的事 |
|---|---|---|
| 新建或较新的 SwiftUI 项目 | 使用 UILaunchScreen 生成配置 |
不要只在源码目录放一张启动图 |
UIKit 项目已有 LaunchScreen.storyboard |
使用 UILaunchStoryboardName 指向真实文件 |
不要改文件名却不更新 Target 配置 |
| 需要多个启动场景 | 使用 Apple 文档支持的多启动场景配置 | 不要把多个无效键全部堆进 plist |
| 跨平台项目由脚本生成 plist | 检查 iOS App Target 的最终合并结果 | 不要只改框架目录里的模板文件 |
UILaunchScreen 是一个字典类型,可以通过系统支持的颜色、图片和安全区域选项描述启动界面。如果项目继续使用 storyboard,则应使用 UILaunchStoryboardName。具体字段类型和支持范围应以 Apple 的 UILaunchScreen 配置说明 为准。
Apple 也支持在 Xcode 的 App Icons and Launch Screen 区域指定启动屏文件,或直接向属性列表加入 UILaunchScreen。项目已经有 LaunchScreen.storyboard 时,不必为了迁移而同时保留两套互相冲突的声明。可参考 Apple 的启动屏配置文档。
注意:不要手工添加空字符串、错误文件名或未加入 Target 的 storyboard 来“骗过”服务器校验。SwiftUI 的自动生成配置与手工添加空键不是一回事。前者由构建设置参与生成,后者可能只让源码看起来完整,最终 Archive 仍然缺失有效内容。
iOS 27 启动屏应该用 UILaunchScreen 还是 LaunchScreen.storyboard?
新项目或希望减少 storyboard 维护的项目,可以优先使用 UILaunchScreen。已经稳定使用 LaunchScreen.storyboard 的 UIKit 项目,可以继续使用 storyboard,只要最终 plist、文件名和 Target 资源保持一致。这里没有“一律重写”的要求,关键是声明完整性和产物一致性。
Target 覆盖:源文件与最终合并结果
SwiftUI 自动生成路径
SwiftUI 项目经常开启 Generate Info.plist File。这时,源代码目录里可能没有一份完整的物理 Info.plist,但 Xcode 会根据 Build Settings 和 Target 配置生成最终文件。
重点检查:
- 选中实际提交的 iOS App Target。
- 打开 Build Settings。
- 搜索
Generate Info.plist File。 - 搜索
Launch Screen (Generation)。 - 确认 Release 配置也启用,而不是只有 Debug 启用。
- 查看
INFOPLIST_KEY_UILaunchScreen_Generation是否在提交 Target 生效。
Apple 的 Build Settings Reference 说明,启用 GENERATE_INFOPLIST_FILE 后,INFOPLIST_KEY_UILaunchScreen_Generation 会把 UILaunchScreen 加入生成的 Info.plist。
因此,SwiftUI 项目自动生成 Info.plist 后仍提示缺少启动屏,通常要优先检查以下层级:
- 只在 Debug 配置打开了启动屏生成;
- Archive 使用了另一个 Scheme;
- 当前提交的是 Extension 或另一个 App Target;
- 脚本在构建后覆盖了生成的 plist;
- Release 配置使用了不同的
.xcconfig; - Archive 内实际产品不是本地刚检查的那个 App。
UIKit 与跨平台项目路径
UIKit 旧项目通常会在 Target 的 General 或 Build Settings 中引用 LaunchScreen.storyboard。如果项目手工维护 plist,则需要确认 INFOPLIST_FILE 指向当前 App Target 使用的文件,而不是测试 Target、旧 Bundle 或共享模板。
跨平台项目还要额外检查生成脚本。例如,某些构建流程会在 Archive 前执行 plist 合并、替换 Bundle ID 或写入临时产物。最终 Info.plist 可能同时受到源文件、Build Settings、产品类型和构建脚本影响,不能把源文件内容直接当作最终结果。
如何处理多 Target?
逐个打开主 App、Notification Extension、Share Extension 和其他可提交产品的配置。主 App 的 ITMS-90870 修复不能自动证明扩展或嵌套 App 也合规。每个真正进入提交包的产品,都要确认启动屏要求和最终 plist。
Archive 检查:源配置与最终产物对比
只在 Xcode 设置界面看到 UILaunchScreen,不能作为验收证据。服务器收到的是 Archive 导出的 App 二进制,不是项目导航栏中的 plist。
建议按下面步骤执行:
- 使用生产一致的 Scheme。
- 将 Configuration 设为 Release。
- 执行
Product > Archive。 - 在 Organizer 中选中刚生成的 Archive。
- 通过
Show in Finder打开.xcarchive。 - 找到
Products/Applications/示例App.app/Info.plist。 - 用 Xcode 或
plutil查看最终键值。 - 检查
LaunchScreen.storyboardc或相关启动屏资源是否位于 App 包内。 - 核对 Bundle ID、版本号和构建号。
- 保存脱敏后的检查结果与上传日志。
示例命令如下,路径和项目名请替换成实际值:
plutil -p \
"示例项目.xcarchive/Products/Applications/示例App.app/Info.plist"
find \
"示例项目.xcarchive/Products/Applications/示例App.app" \
-iname "*Launch*"
如果使用 storyboard,最终 plist 中的 UILaunchStoryboardName 必须与包内实际文件对应。如果使用 UILaunchScreen,要确认该键存在于最终 App 的 plist,而不是只存在于工程文件或某个中间目录。
- [ ] Archive 使用的是 Release 配置。
- [ ] Scheme 指向正确的 App Target。
- [ ] 最终 App 的
Info.plist包含至少一个认可的启动屏键。 - [ ]
UILaunchStoryboardName指向实际存在的 storyboard。 - [ ] 启动屏资源已加入正确的 Target Membership。
- [ ] 归档产物中的 Bundle ID 与 App Store Connect 应用记录一致。
- [ ] 没有脚本在 Archive 后覆盖启动屏配置。
- [ ] 已保存脱敏的
plutil输出和上传日志。 - [ ] 已用同一份 Archive 执行 TestFlight 上传。
Archive 产物怎样确认启动屏声明已经进入 App?
不要只检查 .xcodeproj 或源码目录。应直接打开 .xcarchive 中的 .app,读取 Info.plist,再用 find 检查 storyboard 或相关资源是否实际进入包内。只有这两项同时成立,才能证明构建产物具备可提交的启动屏配置。
首次启动:系统缓存与真实显示
修复后为什么仍可能看到旧启动图或空白页?
启动屏可能受到旧安装和系统缓存影响。Apple TN3208 建议删除设备或模拟器中的旧 App,再重新构建和运行;如果仍看到空白、旧图或没有启动屏,应继续检查启动屏配置和资源。
建议同时做模拟器和真机检查:
- 删除旧 App,再安装新构建;
- 冷启动,不要只从后台恢复;
- 检查启动屏是否出现;
- 检查图片是否被裁切;
- 检查浅色和深色模式;
- 检查刘海屏、动态岛和 iPad 安全区域;
- 检查启动屏后第一帧是否出现旧图残留;
- 记录设备型号、系统版本、构建号和检查时间。
模拟器适合快速确认配置是否生效。正式发布前仍应保留至少一次真机检查,因为设备安全区域、缓存状态和资源加载路径可能不同。不要把“感觉启动更快”写成性能结论;本文验收的是显示正确性,不是启动速度。
TestFlight 上传:Beta 可测与正式发布分开
截至 2026 年 8 月 27 日,Apple 的 App Store Connect 更新记录已确认,Xcode 27 Beta 5 和 iOS 27.0 Beta 5 SDK 构建可以上传到 TestFlight 进行测试。
这不等于 Xcode 27 Beta 5 已经获得正式 App Store 生产发布资格。Apple 当前资料并未确认 Xcode 27 正式版发布日期,也没有在本文核实的资料中给出生产 App Store 接收 iOS 27 SDK 构建的具体切换日期。因此,生产发布判断必须继续查看最新的 App Store Connect 支持版本记录。
TestFlight 验收建议采用“上传—处理—查看状态”的闭环:
- 使用刚刚检查过的 Archive。
- 从 Xcode Organizer 选择 Distribute App。
- 选择 App Store Connect,再选择上传。
- 等待 App Store Connect 处理构建。
- 查看 Delivery Logs 和构建状态。
- 确认不再出现
ITMS-90870。 - 将构建加入内部测试组。
- 如需外部测试,再按照 TestFlight 流程提交测试信息。
Apple 的上传文档说明,上传后构建需要经过服务器处理,才会出现在 App Store Connect;如果构建状态为 Invalid Binary,意味着 Apple 已收到二进制,但它没有满足全部上传要求,需要修复后重新交付。相关上传和构建状态可参考 Apple 的 App 上传说明 与 构建状态说明。
Xcode 27 Beta 构建可以直接提交 App Store 吗?
当前已确认的是 TestFlight 上传支持,不应把 Beta 构建上传 TestFlight 等同于正式 App Store 发布。生产链路应使用 Apple 当前明确支持的 Xcode 和 SDK,并在正式切换前重新查看 App Store Connect Release Notes。
远程构建:本地修好与服务器失败的差异
当本地 Mac 修复成功、自动化打包机仍然报 ITMS-90870,优先怀疑环境差异,而不是立即删除 DerivedData 或重装 Xcode。
至少核对以下项目:
- Xcode 主版本和具体 Beta 版本;
- macOS 版本;
- 使用的 Scheme;
- Archive Configuration;
.xcconfig文件;GENERATE_INFOPLIST_FILE状态;- 启动屏生成设置;
- plist 改写脚本;
- 签名证书和 Provisioning Profile;
- Archive 输出路径;
- 上传工具和登录账号权限。
App Store Connect 会根据 App 包内的 Bundle ID、版本号和构建号关联上传记录。因此不能只确认源代码正确,还要确认最终包的身份信息没有被远程脚本改变。
如果当前电脑无法安装 Xcode 27 所需的 macOS,短期可以使用独立的远程 Mac 环境完成兼容验证。MESHLAUNCH 的 Mac 远程租赁方案适合临时准备一套可回退的 Archive 环境;如果需要固定的 Apple Silicon 节点,也可以查看 Mac mini M4 租赁方案。
经验:远程环境的价值不只是“能打开 Xcode”。真正有用的是锁定 Xcode、Scheme、Configuration 和上传入口,再把同一个脱敏项目分别执行交互式 Archive 与自动化 Archive,比较两份最终 Info.plist。
如果团队已经有稳定的 Xcode 26 生产链路,不建议为了一个启动屏错误直接替换整套打包机。更稳妥的做法是保留现有生产环境,同时建立 Xcode 27 测试环境,完成 Archive、首次启动和 TestFlight 三项验收后,再根据 Apple 的正式支持公告决定切换时间。
对于只需要临时验证 iOS 27 SDK 的独立开发者,直接购买一台 Mac 的缺点是硬件成本、维护成本和版本隔离成本都会提前发生;继续用 Windows 或 Linux 又无法原生运行 Xcode,远程桌面还可能受网络延迟、权限和本地缓存影响。若现有电脑无法安装对应 macOS,租赁 MESHLAUNCH 的远程 Mac 可以先完成兼容验证和 TestFlight 上传,不必立即替换当前生产打包机。需要长期稳定重负载、物理 USB 设备调试或严格离线构建时,自购 Mac 仍更合适;只做短期迁移、版本测试和回退验证时,远程 Mac 的投入更容易控制。
本次验收的最低完成标准只有三项:最终 Archive 有效、首次启动显示正确、TestFlight 处理结果不再出现 ITMS-90870。完成这三项后,再等待 Apple 明确 Xcode 27 正式生产支持边界,而不是依据未经确认的发布日期猜测提前切换。