iOS 27 启动屏幕新规:四个键,否则被拒
苹果的 iOS 27 发行说明,把一句文档描述变成了提审关口:“iOS 和 iPadOS 应用只要使用 27.0 SDK 或更高版本构建,就必须包含启动屏幕。应用的 Info.plist 必须包含以下键之一:UILaunchStoryboardName、UILaunchStoryboards、UILaunchScreen 或 UILaunchScreens。当 App Store 开始接受使用 27.0 SDK 构建的应用时,未包含启动屏幕的应用将被拒绝。”1
要求本身并不新鲜。苹果的 Xcode 文档开篇就写着“每个 iOS 应用都必须提供启动屏幕”。2 iOS 27 带来的,是忽视它的后果。
要点速览
- 使用 iOS 27.0 SDK 或更高版本构建的应用,必须通过四个
Info.plist键之一声明启动屏幕,否则 App Store 会拒绝该构建版本。1 - 这道关口在提审环节,而非运行时。苹果的措辞是“当 App Store 开始接受使用 27.0 SDK 构建的应用时……将被拒绝”,因此问题暴露在 App Store Connect,而不是用户的设备上。1
- 规则只点名 iOS 和 iPadOS,到此为止。在同一条说明里,苹果并未把它扩展到 tvOS、visionOS 或 Mac Catalyst。1 同一周期落地的场景生命周期强制要求则是另一回事:它的迁移指南逐一点名了 iOS 27、iPadOS 27、Mac Catalyst 27、tvOS 27 和 visionOS 27。3
- 四个键里,两个对应单个启动屏幕(
UILaunchScreen直接在属性列表中构建一个,UILaunchStoryboardName指定一个 storyboard 文件名),另外两个对应按 URL scheme 区分的多套方案(UILaunchScreens、UILaunchStoryboards)。其中三个是字典,只有UILaunchStoryboardName是字符串。4567 - 用近期 Xcode 模板创建的项目已经满足这条规则,而且是靠一项构建设置满足的,不是靠某个文件。811 因此,在代码仓库里 grep 这四个键来做审查,根本查不到答案。
把规则说准确
苹果那句话里有三处细节至关重要,而外界的报道往往把这三处都说糊涂了。
第一,触发条件是构建所用的 SDK,不是用户运行的系统版本。基于 iOS 26 编译的二进制文件仍然可以留在商店里。但只要用 Xcode 27 重新构建、想用上新 SDK 里的任何东西,这条要求就会一并生效。
第二,执行点是 App Store 的接收环节。苹果写的是“拒绝”,也就是说失败发生在提审审核阶段,而不是启动时。正是这一点,把启动屏幕规则和同周期发布的场景生命周期强制要求区分开来——后者苹果的用词是应用“无法启动”。1 前者让你损失一个被拒的构建版本,后者让用户手机上多出一个打不开的应用。
第三,适用范围是 iOS 和 iPadOS。苹果的说明只点了这两个平台,再无下文。维护 Catalyst 或 tvOS target 的人,应该把这条要求理解为“未作规定”,而不是可以类推适用。27 周期在别处的强制力度不小,从横跨五个平台的场景生命周期,到 ImageCreator 从 Image Playground 中移除,但启动屏幕这条说明的范围始终很窄。
四个键,该用哪一个
苹果提供了两种构建启动屏幕的方式,各自又有单个和多个两种形态,于是有了这四个键。
UILaunchScreen 直接在属性列表里配置启动界面,完全不涉及 storyboard 文件。苹果把它描述为“以不依赖 storyboard 的方式配置应用启动期间的用户界面”,它接受一组子键,用于设置背景色、图片,以及导航栏、标签栏和工具栏是否显示。4 如果应用的首屏只是一块纯色背景,一个空的 UILaunchScreen 字典就足以满足规则。Xcode 自己的构建系统走的正是这条路:“启用 GENERATE_INFOPLIST_FILE 时”,INFOPLIST_KEY_UILaunchScreen_Generation 会“把 Info.plist 文件中 UILaunchScreen 键的值设为一个空字典”。8
UILaunchStoryboardName 用文件名(去掉扩展名)指向一个 storyboard:LaunchScreen.storyboard 文件对应字符串 LaunchScreen。5 它可以追溯到 iOS 9,也是四个键中唯一取字符串而非字典的一个。5 如果应用有专门设计过的启动状态,或者项目里带着 Xcode 至今仍会加进 storyboard 模板的那个 LaunchScreen.storyboard,需要的就是这个键。2
两个复数形式只为一种特定场景而存在,而且它们都是字典而非数组。67 UILaunchScreens 包含三个子键:UILaunchScreenDefinitions 是启动屏幕配置的数组,每一项都带有 UILaunchScreenIdentifier;UIURLToLaunchScreenAssociations 是从 URL scheme 到标识符的映射;UIDefaultLaunchScreen 是兜底项。6 UILaunchStoryboards 的结构与之对应,三个子键分别是 UILaunchStoryboardDefinitions、UIURLToLaunchStoryboardAssociations 和 UIDefaultLaunchStoryboard。7 两者都能让应用从 myapp://compose 打开时,呈现与从主屏幕打开时不同的启动状态。苹果说得很直接:大多数应用不必用它们——“如果只需要一个启动屏幕,请改用 UILaunchScreen”。6
实际决策可以归结为一个问题。启动屏幕是 storyboard,就声明 UILaunchStoryboardName;不是,就声明 UILaunchScreen。只有在你已经清楚自己为什么需要复数形式时,才去用它。
真正会被卡住的应用
用当前 Xcode 模板创建的应用,谁都不用动手就能通过——这恰恰是这条规则既容易被无视、又容易让人栽跟头的原因。真正有风险的那批项目有一个共同点:团队里已经很久没人手写过 Info.plist 了。
自动生成的属性列表是最大的一类,而最大的生成者就是 Xcode 自己。Xcode 13 改了默认行为:用若干模板创建的项目“不再需要 entitlements 和 Info.plist 之类的配置文件”,相应字段改在 target 的 Info 标签页和构建设置编辑器里配置。9 跨平台工具链、包装框架,以及在打包时合成 plist 的构建脚本,又叠加了一层风险,它们的模板甚至可能比 UILaunchScreen 还老。构建系统写出来的文件,没人会去审。
被清理掉的 plist 是第二类。启动 storyboard 会在瘦身优化时被删掉,会在迁移出 Interface Builder 时被删掉,也会在某次清理中——删掉项目里最后一个 storyboard 的同时——把启动屏幕一并带走。应用照样能编译通过,于是这次删除看上去毫无风险。
第三类是继承下来的老项目,而这批项目的范围比大多数报道设想的要窄。UILaunchStoryboardName 在 iOS 9 就有了,所以哪怕是 2015 年一路带过来的项目,也已经握着一个合格的键。5 四个键一个都没有的应用比这还老:它们仍然用 UILaunchImages 声明启动画面——那是 iOS 7.0 引入的键,苹果在 iOS 13.0 将其废弃,只留下一句说明:“UILaunchImages 已废弃;请改用 Xcode 启动 storyboard。”10 UILaunchImages 数组不在苹果列出的那四个键之列,因此一个至今依赖它、从未采用 storyboard 引用的项目,手里没有任何符合要求的东西。十年的 Xcode 升级也不会替它补上,因为构建过程从来没有报过错。
审查由构建系统写出的 Info.plist
先接受一个前提:这个文件可能根本不存在。GENERATE_INFOPLIST_FILE 开启自动生成,而每一项 INFOPLIST_KEY_* 构建设置都会往构建产出的 plist 里写入一个键。8 启动屏幕对应其中两项:INFOPLIST_KEY_UILaunchScreen_Generation 写入一个空的 UILaunchScreen 字典,INFOPLIST_KEY_UILaunchStoryboardName 写入 storyboard 名称。8 两者都不会留下任何可供文本搜索命中的痕迹。Xcode 当前的 iOS SwiftUI App 模板在共享设置中就带着 INFOPLIST_KEY_UILaunchScreen_Generation = YES,因此这样创建出来的项目,target 完全合规,而整个仓库里找不到任何与启动屏幕相关的文字。11
所以要问构建系统,而不是问文件系统:
xcodebuild -showBuildSettings \
-project YourApp.xcodeproj -target YourApp \
-configuration Release -sdk iphoneos 2>/dev/null \
| grep -E "^ +(GENERATE_INFOPLIST_FILE|INFOPLIST_FILE|INFOPLIST_KEY_UILaunch)"
在我的机器上对 Ace Citizenship 项目执行,返回三行:11
GENERATE_INFOPLIST_FILE = YES
INFOPLIST_FILE = Ace-Citizenship-Info.plist
INFOPLIST_KEY_UILaunchScreen_Generation = YES
第三行就是合规与否的全部答案,而仓库里没有任何一个文件包含它。输出要按这个顺序读。GENERATE_INFOPLIST_FILE = YES 加上一行值为 YES 的 INFOPLIST_KEY_UILaunch,意味着构建系统会替你写入这个键:苹果规定所有 INFOPLIST_KEY_* 设置都以启用自动生成为前提,所以同样一行在 GENERATE_INFOPLIST_FILE = NO 之下形同虚设;而值为 NO 时,无论如何都不会写入任何内容。8 关闭自动生成时,启动屏幕的键就只能待在 INFOPLIST_FILE 指向的文件里,那就打开那个文件,找找四个键中的一个。开启自动生成、同时又存在文件路径时,构建系统会把两者合并,任一来源都能满足要求。8
两个参数都不是摆设。-configuration Release 之所以重要,是因为 App Store 审核看到的是 Release 产物。-sdk iphoneos 之所以重要,是因为在多平台 target 上,Xcode 会按 SDK 分别写入这些设置:Reps 项目声明了三次 INFOPLIST_KEY_UILaunchScreen_Generation,分别对应 [sdk=iphoneos*]、[sdk=iphonesimulator*] 和 [sdk=appletv*],Banana List 则声明了前两项。去掉 -sdk iphoneos,这些声明一个都不会被解析,键随之从输出中消失,一个本来合规的 target 看上去就像有问题。11
接着检查你真正提交的产物。在 plutil -p 的输出里,行首两个空格标记的是顶层键:
plutil -p YourApp.xcarchive/Products/Applications/*.app/Info.plist \
| grep -E '^ "UILaunch'
对 4 月为分发构建的 Return 归档执行,返回一行;而对一个没有启动屏幕的 bundle 执行同样的命令,则不会有任何输出,退出码为 1:11
"UILaunchScreen" => {
这里要用 plutil,不要用 PlistBuddy。遇到一个无法解析的路径时,PlistBuddy -c "Print" 会把“File Doesn’t Exist, Will Create:”和一个空的 Dict { } 写到标准输出,并以 0 退出;把它管道接给 grep 去找启动屏幕的键,唯一能暴露错误的那一行就被吞掉了,只剩下空输出——看起来和“缺少键”一模一样。plutil 则会说明它打不开哪个文件,并以 1 退出。11
在仓库里做一次全面搜索仍然有用,但作用比看上去小:它的任务是找出值得一读的、手工维护的 plist。
find . -name "Info.plist" \
-not -path "*/build/*" -not -path "*/DerivedData/*" \
-not -path "*/.build/*" -not -path "*/Carthage/*" -not -path "*/Pods/*" \
-print0 | xargs -0 grep -L -E "UILaunchScreen|UILaunchStoryboard"
它打印出的每一条路径,都只是需要再去对照构建设置核查的候选项,而不是一个会提审失败的 target。在 Banana List 上,这条命令恰好返回一行 ./Banana List/Info.plist,而那个 target 其实带着启动屏幕:它的构建设置里有 INFOPLIST_KEY_UILaunchScreen_Generation,归档后的 bundle 里也有 UILaunchScreen。如果不加那些排除项,同样的命令在这个仓库会返回 25 行,其中 24 行是 build/ 目录里的构建产物,包括测试运行器 bundle、XCTest.framework、一份 .xcresult 日志,还有一个 watch 应用。11
手工添加这个键需要走几步,而不是改一行文本。苹果给出的顺序是:在 target 的设置中选择 Info 标签页;在 Custom iOS Target Properties 一节展开 Launch Screen 键;点击 Add 按钮,输入 UILaunchScreen,回车;然后选中 UILaunchScreen 键,再次点击 Add,按需添加控制外观的子键。2 如果 plist 由 Xcode 生成,在 target 的构建设置里把 INFOPLIST_KEY_UILaunchScreen_Generation 设为 YES,效果相同,而且下一次构建后依然有效。8 如果 plist 由 Xcode 之外的工具生成,就去改生成器的模板;你在输出结果上做的任何修改,下一次构建都会被丢弃。
规则没有说的部分
苹果没有公布具体日期。触发条件的表述是“当 App Store 开始接受使用 27.0 SDK 构建的应用时”,从历史看这个时间点接近秋季系统发布,但苹果在本周期并没有以书面形式作出承诺。1 你读到的任何具体日期,都应当视为推测。
苹果同样没有说现有应用会停止工作,没有说 TestFlight 构建版本会受影响,也没有说这条要求适用于 iOS 和 iPadOS 之外的平台。这条说明只涉及新构建版本的提审,别无其他。
修复本身很小,所以真正值得问的不是“怎么合规”,而是“你是不是本来就合规”。对手工维护的项目来说,答案几乎一定是肯定的。而对任何使用自动生成 plist 的项目,值得在商店开始说“不”之前,先去问一遍构建系统。
常见问题
用当前 Xcode 模板创建的应用是否已经合规?
几乎可以肯定是的,而证据在构建设置里,不在文件里。Xcode 的 iOS SwiftUI App 模板在共享设置中设置了 INFOPLIST_KEY_UILaunchScreen_Generation = YES,它会往构建系统产出的 Info.plist 里写入一个空的 UILaunchScreen 字典。811 想确认的话,针对 Release 配置、加上 -sdk iphoneos 运行 xcodebuild -showBuildSettings,看看有没有 INFOPLIST_KEY_UILaunch 那一行。
为什么在仓库里 grep UILaunchScreen 什么都找不到?
因为从 Xcode 13 起,用若干模板创建的项目在磁盘上根本没有 Info.plist;苹果把那些字段搬进了 target 的 Info 标签页和构建设置编辑器。9 启动屏幕是在构建时由 INFOPLIST_KEY_UILaunchScreen_Generation 或 INFOPLIST_KEY_UILaunchStoryboardName 写入的。8 对源文件做文本搜索看不到这两项设置,而它确实找到的那些 Info.plist,通常要么是构建系统会合并进去的片段,要么是 build/ 和 DerivedData/ 下的构建产物。
如果确实一个键都没有,该加哪一个?
项目里带 LaunchScreen.storyboard 就加 UILaunchStoryboardName,没有就加 UILaunchScreen。45 对于启动时只显示纯色背景的应用,一个空的 UILaunchScreen 字典就够了——这也正是 Xcode 默认生成的内容。8 除非要为不同 URL scheme 呈现不同的启动状态,否则不要碰 UILaunchScreens 和 UILaunchStoryboards;苹果自己的建议是“如果只需要一个启动屏幕,请改用 UILaunchScreen”。6
这条要求适用于 TestFlight 构建版本吗?
苹果的说明没有讲。它只点明了一个后果——“当 App Store 开始接受使用 27.0 SDK 构建的应用时”被拒绝——文中完全没有提到 TestFlight 分发。1 由于 TestFlight 构建版本同样要经过 App Store Connect 和 beta 审核,稳妥的假设是:不满足这条要求的构建版本,在任何遇到审核的环节都会失败;但苹果并没有把这一点写下来。如果你的决策依赖这个答案,应该实际上传一个用 27.0 SDK 构建的版本试一试,而不是对这份沉默作任何一种解读。
关键要点
对 iOS 开发者:
- 先查构建设置,再去 grep 文件。运行 xcodebuild -showBuildSettings -configuration Release -sdk iphoneos,查找 INFOPLIST_KEY_UILaunchScreen_Generation 或 INFOPLIST_KEY_UILaunchStoryboardName。8 仓库文本搜索这两项都看不到。
- 带启动 storyboard 就选 UILaunchStoryboardName,不带就选 UILaunchScreen。除非要按 URL scheme 提供不同的启动状态,否则不要用复数形式的键。
对通过跨平台工具链发布的团队:
- 用 plutil -p 检查归档后的 .app bundle,而不是版本控制里的那个文件。审核看到的是生成器的输出;而且路径出错时 plutil 会明确报错,PlistBuddy 却只会打印一个空字典并以 0 退出。
- 要改就改构建配置里的 plist 模板,而不是生成出来的文件,否则下一次构建会把修复丢掉。
对发布负责人: - 问题暴露在 App Store 提审环节,而不是运行时,所以代价是一轮审核周期,而不是一次线上事故。把这项检查安排在第一次用 27.0 SDK 提审之前,而不是被拒之后。 - 把这项检查和场景生命周期迁移放在一起做,两者触发条件相同,而后者的代价更重。
27 周期在持续把建议变成强制:ImageCreator 停止工作,场景生命周期成为启动的前提,启动屏幕成为提审的关口。想了解同一个 SDK 还带来了什么,参见 iOS 27 SwiftUI 新特性。完整的系列汇总页是 Apple 生态系统系列。
参考资料
-
Apple,iOS & iPadOS 27 Release Notes,UIKit 部分。启动屏幕要求的出处,位于 New Features 下(radar 168247372):“iOS 和 iPadOS 应用只要使用 27.0 SDK 或更高版本构建,就必须包含启动屏幕。应用的
Info.plist必须包含以下键之一:UILaunchStoryboardName、UILaunchStoryboards、UILaunchScreen或UILaunchScreens。当 App Store 开始接受使用 27.0 SDK 构建的应用时,未包含启动屏幕的应用将被拒绝。”本文引用的场景生命周期措辞也出自此处,单独列在 Deprecations 下(radar 141837548):“使用最新 SDK 构建的应用必须采用基于场景的生命周期,否则无法启动。”该条目没有附带平台列表。2026 年 7 月 25 日对照苹果文档 JSON 核实。 ↩↩↩↩↩↩↩ -
Apple,Specifying your app’s launch screen,Xcode 文档。“每个 iOS 应用都必须提供启动屏幕”一句、两种受支持的方式(信息属性列表和用户界面文件),以及本文引用的属性列表操作步骤的出处:在 target 的设置中选择 Info 标签页,在 Custom iOS Target Properties 一节展开 Launch Screen 键,添加
UILaunchScreen键,然后添加用于配置选项的子键。 ↩↩↩ -
Apple,Transitioning to the UIKit scene-based life cycle,Apple 开发者文档。五个平台逐一列举的出处:“从 iOS 27、iPadOS 27、Mac Catalyst 27、tvOS 27 和 visionOS 27 开始,使用最新 SDK 构建的应用必须采用基于场景的生命周期,否则无法启动。” ↩
-
Apple,UILaunchScreen,Information Property List 参考文档。字典类型,iOS 与 iPadOS 14.0 及更高版本。“以不依赖 storyboard 的方式配置应用启动期间的用户界面”一句及各子键(
UIColorName、UIImageName、UIImageRespectsSafeAreaInsets、UINavigationBar、UITabBar、UIToolbar)的出处。 ↩↩↩ -
Apple,UILaunchStoryboardName,Information Property List 参考文档。字符串类型,iOS 与 iPadOS 9.0 及更高版本(另有 tvOS 9.0、watchOS 2.0)。“文件名去掉扩展名”这一规则的出处。 ↩↩↩↩↩
-
Apple,UILaunchScreens,Information Property List 参考文档。字典类型,iOS 与 iPadOS 14.0 及更高版本。子键
UILaunchScreenDefinitions(配置数组,每一项带有UILaunchScreenIdentifier)、UIURLToLaunchScreenAssociations和UIDefaultLaunchScreen,以及“如果只需要一个启动屏幕,请改用UILaunchScreen”一句的出处。 ↩↩↩↩↩ -
Apple,UILaunchStoryboards,Information Property List 参考文档。字典类型,iOS 与 iPadOS 9.0 及更高版本。子键
UILaunchStoryboardDefinitions、UIDefaultLaunchStoryboard和UIURLToLaunchStoryboardAssociations,以及“单个启动 storyboard 应改用UILaunchStoryboardName”这一指引的出处。 ↩↩↩ -
Apple,Build settings reference,Xcode 文档。
GENERATE_INFOPLIST_FILE(“自动生成 Info.plist 文件”)、INFOPLIST_FILE(构建系统“会把您在此文件中指定的值与构建过程中生成的其他值合并”,且“启用GENERATE_INFOPLIST_FILE时,构建系统还会把构建设置中的内容纳入合并过程”)、INFOPLIST_KEY_UILaunchScreen_Generation(“把 Info.plist 文件中UILaunchScreen键的值设为一个空字典”)和INFOPLIST_KEY_UILaunchStoryboardName的出处。 ↩↩↩↩↩↩↩↩↩↩↩ -
Apple,Xcode 13 Release Notes,Templates,Resolved Issues(radar 68254857):“用若干模板创建的项目不再需要 entitlements 和
Info.plist之类的配置文件。常用字段在 target 的 Info 标签页中配置,构建设置在项目编辑器中配置。当用到额外字段时,这些文件才会被添加到项目中。” ↩↩ -
Apple,UILaunchImages,Information Property List 参考文档。字典数组,iOS 7.0 引入,iOS 13.0 废弃。“
UILaunchImages已废弃;请改用 Xcode 启动 storyboard”一句的出处。 ↩ -
作者于 2026 年 7 月 25 日在 macOS 26.5.2 与 Xcode 26.6(build 17F113)上,针对四个已上线的 iOS 项目所做的测试:Ace Citizenship、Banana List、Reps 和 Return。命令输出为原样复现。
PlistBuddy的行为经过直接验证:/usr/libexec/PlistBuddy -c "Print" /nonexistent/Info.plist会打印“File Doesn’t Exist, Will Create:”,随后是Dict { },并以 0 退出;而对同一路径执行plutil -p会打印“The file “Info.plist” couldn’t be opened because there is no such file”,并以 1 退出。模板设置来自已安装 Xcode 工具链中的iOS SwiftUI App.xctemplate/TemplateInfo.plist。 ↩↩↩↩↩↩↩↩