← 所有文章

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 之外的工具生成,就去改生成器的模板;你在输出结果上做的任何修改,下一次构建都会被丢弃。

规则没有说的部分

2026年9月15日 更新: 触发条件现在有了边界。App Store Connect 的发行说明记录了从 2026年9月9日 起接受以 27.0 RC SDK 构建的应用上传到 App Store,从 9月14日 起接受以正式版 SDK 构建的应用,但两条记录都没有写明开始拒绝的日子,所以这两个日期只是把触发点夹在中间,并没有把它钉死。12 本节其余内容保持 7 月所写的原样。

苹果没有公布具体日期。触发条件的表述是“当 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 生态系统系列。

参考资料


  1. 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 核实。 ↩↩↩↩↩↩↩

  2. Apple,Specifying your app’s launch screen,Xcode 文档。“每个 iOS 应用都必须提供启动屏幕”一句、两种受支持的方式(信息属性列表和用户界面文件),以及本文引用的属性列表操作步骤的出处:在 target 的设置中选择 Info 标签页,在 Custom iOS Target Properties 一节展开 Launch Screen 键,添加 UILaunchScreen 键,然后添加用于配置选项的子键。 ↩↩↩

  3. Apple,Transitioning to the UIKit scene-based life cycle,Apple 开发者文档。五个平台逐一列举的出处:“从 iOS 27、iPadOS 27、Mac Catalyst 27、tvOS 27 和 visionOS 27 开始,使用最新 SDK 构建的应用必须采用基于场景的生命周期,否则无法启动。” ↩

  4. Apple,UILaunchScreen,Information Property List 参考文档。字典类型,iOS 与 iPadOS 14.0 及更高版本。“以不依赖 storyboard 的方式配置应用启动期间的用户界面”一句及各子键(UIColorName、UIImageName、UIImageRespectsSafeAreaInsets、UINavigationBar、UITabBar、UIToolbar)的出处。 ↩↩↩

  5. Apple,UILaunchStoryboardName,Information Property List 参考文档。字符串类型,iOS 与 iPadOS 9.0 及更高版本(另有 tvOS 9.0、watchOS 2.0)。“文件名去掉扩展名”这一规则的出处。 ↩↩↩↩↩

  6. Apple,UILaunchScreens,Information Property List 参考文档。字典类型,iOS 与 iPadOS 14.0 及更高版本。子键 UILaunchScreenDefinitions(配置数组,每一项带有 UILaunchScreenIdentifier)、UIURLToLaunchScreenAssociations 和 UIDefaultLaunchScreen,以及“如果只需要一个启动屏幕,请改用 UILaunchScreen”一句的出处。 ↩↩↩↩↩

  7. Apple,UILaunchStoryboards,Information Property List 参考文档。字典类型,iOS 与 iPadOS 9.0 及更高版本。子键 UILaunchStoryboardDefinitions、UIDefaultLaunchStoryboard 和 UIURLToLaunchStoryboardAssociations,以及“单个启动 storyboard 应改用 UILaunchStoryboardName”这一指引的出处。 ↩↩↩

  8. 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 的出处。 ↩↩↩↩↩↩↩↩↩↩↩

  9. Apple,Xcode 13 Release Notes,Templates,Resolved Issues(radar 68254857):“用若干模板创建的项目不再需要 entitlements 和 Info.plist 之类的配置文件。常用字段在 target 的 Info 标签页中配置,构建设置在项目编辑器中配置。当用到额外字段时,这些文件才会被添加到项目中。” ↩↩

  10. Apple,UILaunchImages,Information Property List 参考文档。字典数组,iOS 7.0 引入,iOS 13.0 废弃。“UILaunchImages 已废弃;请改用 Xcode 启动 storyboard”一句的出处。 ↩

  11. 作者于 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。 ↩↩↩↩↩↩↩↩

  12. Apple,App Store Connect release notes,2026年9月15日 获取。2026年9月9日:”You can now upload apps built with Xcode 27 RC using the SDK for iOS 27.0 RC, iPadOS 27.0 RC, macOS 27.0 RC, tvOS 27.0 RC, visionOS 27.0 RC, and watchOS 27.0 RC for the App Store, and for internal and external testing through TestFlight.” 2026年9月14日:”You can now upload apps built with Xcode 27 using the SDK for iOS 27.0, iPadOS 27.0, macOS 27.0, tvOS 27.0, visionOS 27.0, and watchOS 27.0 for the App Store, and for internal and external testing through TestFlight.” 两条记录都没有提到启动屏幕或拒绝。 ↩

相关文章

UIKit 的场景强制要求:哪些应用在 iOS 27 上将无法启动

使用 iOS 27 SDK 构建的应用必须采用 UIKit 基于场景的生命周期,否则将无法启动。本文梳理时间线、迁移步骤以及对应的 agent skill。

7 分钟阅读

iOS 27.0发行说明:弃用、破坏性变更与修复

iOS 27.0(24A437)于9月14日发布。从开发者角度解读这份说明:11项弃用、用27 SDK构建时会触发什么,以及我亲自验证的一项修复。

22 分钟阅读

像素艺术的光:昼与夜、天气与阴影

从源码实测《宝可梦 水晶》和《绿宝石》如何保持一天、雨和阴影,并为 Kiradex 的像素小镇写出一份关于昼夜、天气与阴影的简报。

120 分钟阅读