← 所有文章

iOS 27 啟動畫面規則:四個鍵擇一,否則退件

Apple 的 iOS 27 發行說明,把一句文件敘述變成了送審關卡:「以 27.0 SDK 或更新版本建置的 iOS 與 iPadOS App 必須包含啟動畫面。您 App 的 Info.plist 必須包含下列其中一個鍵:UILaunchStoryboardNameUILaunchStoryboardsUILaunchScreenUILaunchScreens。當 App Store 開始接受以 27.0 SDK 建置的 App 時,未包含啟動畫面的 App 會被退件。」1

這項要求本身並不新。Apple 的 Xcode 指南開宗明義就寫著「每個 iOS App 都必須提供啟動畫面。」2 iOS 27 帶來的,是忽略它之後要付出的代價。

懶人包

  • 以 iOS 27.0 SDK 或更新版本建置的 App,必須透過四個 Info.plist 鍵之一宣告啟動畫面,未宣告的建置版本會被 App Store 退件。1
  • 這道關卡設在送審,不在執行期。Apple 的原文是「當 App Store 開始接受以 27.0 SDK 建置的 App 時會被退件」,因此失敗發生在 App Store Connect,而不是使用者的裝置上。1
  • 規則點名 iOS 與 iPadOS,就此打住。同一則說明中,Apple 並未把範圍延伸到 tvOS、visionOS 或 Mac Catalyst。1 同一週期登場的場景生命週期強制規定則截然不同:其遷移指南逐一點名 iOS 27、iPadOS 27、Mac Catalyst 27、tvOS 27 與 visionOS 27。3
  • 四個鍵中,兩個對應單一啟動畫面(UILaunchScreen 直接在屬性列表裡組出一個,UILaunchStoryboardName 則指定 storyboard 檔名),另外兩個對應「依 URL scheme 分流」的變體(UILaunchScreensUILaunchStoryboards)。四者之中有三個是字典;只有 UILaunchStoryboardName 是字串。4567
  • 用近期 Xcode 範本建立的專案早已符合規則,而且靠的是一項 build setting,不是一個檔案。811 因此在儲存庫裡 grep 這四個鍵來稽核,根本找不到答案。

這條規則的精確說法

Apple 那句話裡有三個細節分量十足,而外界的報導往往三個都講糊了。

首先,觸發條件是您建置時所用的 SDK,不是使用者執行的作業系統版本。以 iOS 26 編譯出來的二進位檔,在商店裡的位置不受影響。一旦改用 Xcode 27 重新建置、想採用新 SDK 的任何功能,這項要求就一併跟著生效。

其次,強制執行的時點落在 App Store 受理送審的那一關。Apple 寫的是「退件」,也就是把失敗放在送審審查,而不是啟動的當下。這一點正好把啟動畫面規則和同週期推出的場景生命週期強制規定區隔開來——後者 Apple 的用字是 App「無法啟動」。1 前者讓您賠上一次被退的建置版本,後者讓使用者手機上多出一個打不開的 App。

第三,適用範圍是 iOS 與 iPadOS。Apple 的說明只點名這兩個平台,然後就停筆了。手上還有 Catalyst 或 tvOS target 的人,應該把這項要求視為「未表態」,而不是可以類推延伸。27 週期在別處確實管得很嚴,從橫跨五個平台的場景生命週期,到 ImageCreator 從 Image Playground 中移除,但啟動畫面這則說明始終維持窄口徑。

四個鍵該用哪一個

Apple 提供兩種建構啟動畫面的方式,各自再乘上兩種數量形態,於是有了這四個鍵。

UILaunchScreen 直接在屬性列表中設定啟動介面,完全不牽涉 storyboard 檔案。Apple 形容它是「以不依賴 storyboard 的方式設定 App 啟動期間使用者介面」的做法,並可接受背景顏色、圖片,以及導覽列、標籤列與工具列顯示與否等子鍵。4 如果 App 的第一個畫面只是純色背景,一個空的 UILaunchScreen 字典就足以滿足規則。Xcode 自家的建置系統走的正是這條路:「啟用 GENERATE_INFOPLIST_FILE 時」,INFOPLIST_KEY_UILaunchScreen_Generation「會將 Info.plist 檔案中 UILaunchScreen 鍵的值設為空字典」。8

UILaunchStoryboardName 以檔名(不含副檔名)指向一個 storyboard:LaunchScreen.storyboard 檔案對應的就是字串 LaunchScreen5 這個鍵可回溯到 iOS 9,也是四者中唯一接受字串而非字典的。5 若 App 有設計過的啟動狀態,或專案本來就帶著 Xcode 至今仍會加進 storyboard 範本的 LaunchScreen.storyboard,該用的就是這個鍵。2

複數形式的兩個鍵只為一種特定情境而生,而且兩者都是字典,不是陣列。67 UILaunchScreens 底下有三個子鍵:UILaunchScreenDefinitions,即啟動畫面設定的陣列,每一項都帶有 UILaunchScreenIdentifierUIURLToLaunchScreenAssociations,也就是 URL scheme 到識別碼的對應表;以及 UIDefaultLaunchScreen,備援用的預設值。6 UILaunchStoryboards 的結構如出一轍,換成 UILaunchStoryboardDefinitionsUIURLToLaunchStoryboardAssociationsUIDefaultLaunchStoryboard7 兩者都能讓一個從 myapp://compose 開啟的 App,呈現與從主畫面開啟時不同的啟動狀態。Apple 講得很直接,多數 App 不必碰它們:「如果您只需要一個啟動畫面,請改用 UILaunchScreen。」6

實務上的判斷可以收斂成一個問題。啟動畫面是 storyboard,就宣告 UILaunchStoryboardName;不是,就宣告 UILaunchScreen。只有在您已經清楚知道自己為何需要時,才去碰複數形式的鍵。

真正會中招的 App

用當前 Xcode 範本建立的 App,不必任何人動手就會通過——這正是為什麼這條規則既容易被輕忽,也容易讓人栽跟頭。真正有風險的族群有個共通點:團隊裡近期沒有人手寫過 Info.plist

自動產生的屬性列表是最大宗,而最大的產生者就是 Xcode 本身。Xcode 13 改了預設值:以多種範本建立的專案「不再需要 entitlements 與 Info.plist 等設定檔」,欄位改在 target 的 Info 分頁與 build settings 編輯器中設定。9 跨平台工具鏈、包裝框架,以及在打包時合成 plist 的建置腳本,又疊上一層,而它們的範本可能根本早於 UILaunchScreen 存在。沒有人會去審查一個由建置系統寫出來的檔案。

被清掉的 plist 是第二類。啟動 storyboard 可能在瘦身最佳化時被刪掉,可能在脫離 Interface Builder 的遷移中消失,也可能在某次清理中隨著專案裡最後一個 storyboard 一起被帶走。App 照樣建置成功,所以刪除看起來很安全。

繼承而來的舊專案是第三類,而且這一群比多數報導以為的更窄。UILaunchStoryboardName 在 iOS 9 就已推出,因此一個從 2015 年沿用至今的專案,其實早就握有合格的鍵。5 四個鍵一個都沒有的 App 比那更老:它們仍透過 UILaunchImages 宣告啟動圖,這個 iOS 7.0 的鍵在 iOS 13.0 被 Apple 標為棄用,附帶一句指示:「UILaunchImages 已棄用;請改用 Xcode 啟動 storyboard。」10 UILaunchImages 陣列不在 Apple 那四個鍵的名單上,所以一個至今仍仰賴它、從未改用 storyboard 參照的專案,手上沒有任何一項是這條要求認可的。十年份的 Xcode 升級也不會替它補上,因為建置過程從來沒出過錯。

稽核一份由建置系統寫出的 Info.plist

第一步是先接受一件事:那個檔案可能根本不存在。GENERATE_INFOPLIST_FILE 會開啟自動產生,而每一項 INFOPLIST_KEY_* build setting 都會往建置產出的 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 再加上一行值為 YESINFOPLIST_KEY_UILaunch,代表建置系統會替您寫入那個鍵:Apple 讓每一項 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'

對一份四月為發佈而建置的 Return 封存檔執行,回傳一行;同一道指令用在沒有啟動畫面的套件上則不會印出任何內容,結束碼為 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"

它印出的每一條路徑,都只是「需要再拿 build settings 對照確認」的候選,不代表那個 target 送審會失敗。在 Banana List 上,這道指令恰好回傳一行 ./Banana List/Info.plist,而那個 target 其實照樣帶著啟動畫面:它的 build settings 有 INFOPLIST_KEY_UILaunchScreen_Generation,封存後的套件裡也含有 UILaunchScreen。若少了那些排除條件,同一道指令在該儲存庫會回傳 25 行,其中 24 行是 build/ 裡的建置產物,包括測試執行器套件、XCTest.framework、一份 .xcresult 記錄,以及一個 watch App。11

手動補上這個鍵,需要的是幾個步驟,而不是一次文字編輯。Apple 給的順序是:在 target 的設定中選取 Info 分頁;在 Custom iOS Target Properties 區塊展開 Launch Screen 鍵;點一下 Add 按鈕,輸入 UILaunchScreen,按下 Return;接著選取 UILaunchScreen 鍵,再按一次 Add,為想要的外觀選項加入子鍵。2 若是 Xcode 產生的 plist,在 target 的 build settings 中把 INFOPLIST_KEY_UILaunchScreen_Generation 設為 YES 可達成同樣效果,而且能在下一次建置後留存。8 若 plist 是由 Xcode 以外的東西產生,請改動產生器的範本;您在輸出檔上做的任何編輯,下一次建置都會被丟掉。

這條規則沒有說的事

Apple 沒有公布明確日期。觸發條件的措辭是「當 App Store 開始接受以 27.0 SDK 建置的 App 時」,歷來大致落在秋季作業系統發表前後,但本週期 Apple 並未以文字承諾。1 您讀到的任何具體日期,都請當成推測。

Apple 同樣沒有說既有 App 會停止運作、TestFlight 建置版本會受影響,或這項要求會延伸到 iOS 與 iPadOS 之外。這則說明涵蓋的只有新建置版本的送審,別無其他。

修補的成本小到讓真正有意思的問題不是「該怎麼符合」,而是「您是不是早就符合了」。手動維護的專案,答案幾乎鐵定是肯定的。至於任何 plist 由建置系統自動產生的專案,在商店開始說不之前,值得先用建置系統確認一次。

常見問題

用當前 Xcode 範本建立的 App 已經符合規定了嗎?

幾乎可以確定是,而且證據藏在 build settings,不在檔案裡。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;Apple 把那些欄位搬進了 target 的 Info 分頁與 build settings 編輯器。9 啟動畫面是在建置期由 INFOPLIST_KEY_UILaunchScreen_GenerationINFOPLIST_KEY_UILaunchStoryboardName 帶進來的。8 對原始檔做文字搜尋看不見這兩項設定,而搜到的那些 Info.plist 通常是建置系統會去合併的片段,或是 build/DerivedData/ 底下的建置產物。

如果我真的一個都沒有,該加哪一個鍵?

LaunchScreen.storyboard 就用 UILaunchStoryboardName,沒有就用 UILaunchScreen45 對於啟動時只是純色背景的 App,一個空的 UILaunchScreen 字典就夠了,而這正是 Xcode 預設產生的內容。8 除非您要為不同 URL scheme 呈現不同的啟動狀態,否則略過 UILaunchScreensUILaunchStoryboards;Apple 自己的建議是「如果您只需要一個啟動畫面,請改用 UILaunchScreen。」6

這項要求適用於 TestFlight 建置版本嗎?

Apple 的說明沒有提。它只點名一種後果,也就是「當 App Store 開始接受以 27.0 SDK 建置的 App 時」會被退件,而其中對 TestFlight 發佈隻字未提。1 由於 TestFlight 建置版本同樣要經過 App Store Connect 與 Beta 審查,較安全的假設是:不符合要求的建置版本,在任何要過審查的環節都會失敗——但 Apple 並沒有把這件事寫下來。若這個答案攸關您的規劃,請實際上傳一份以 27.0 SDK 建置的版本測試,而不是替這份沉默選一種解讀去相信。

重點整理

給 iOS 開發者: - 先查 build settings,再去 grep 檔案。執行 xcodebuild -showBuildSettings -configuration Release -sdk iphoneos,找出 INFOPLIST_KEY_UILaunchScreen_GenerationINFOPLIST_KEY_UILaunchStoryboardName8 儲存庫的文字搜尋看不見這兩者。 - 有啟動 storyboard 就選 UILaunchStoryboardName,沒有就選 UILaunchScreen。除非要依 URL scheme 提供不同的啟動狀態,否則略過複數形式的鍵。

給透過跨平台工具鏈出貨的團隊: - 用 plutil -p 稽核封存後的 .app 套件,而不是版本控管裡的檔案。審查看到的是產生器的輸出;而且路徑有誤時 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 and iPadOS apps built with the 27.0 SDK or later are required to include a launch screen. Your app’s Info.plist must contain one of the following keys: UILaunchStoryboardName, UILaunchStoryboards, UILaunchScreen, or UILaunchScreens. Apps that don’t include a launch screen are rejected when the App Store begins accepting apps built with the 27.0 SDK.」本文引用的場景生命週期用字亦出自同一份文件,另列於 Deprecations(radar 141837548):「Apps built with the latest SDK must adopt the scene-based life cycle or they fail to launch.」該條目未附平台清單。已於 2026 年 7 月 25 日對照 Apple 文件 JSON 查核。 

  2. Apple, Specifying your app’s launch screen, Xcode 文件。「Every iOS app must provide a launch screen」一語、兩種支援方式(資訊屬性列表與使用者介面檔案),以及本文引用之屬性列表操作步驟的出處:在 target 的設定中選取 Info 分頁,於 Custom iOS Target Properties 區塊展開 Launch Screen 鍵,加入 UILaunchScreen 鍵,再為設定選項加入子鍵。 

  3. Apple, Transitioning to the UIKit scene-based life cycle, Apple 開發者文件。五平台列舉的出處:「Beginning in iOS 27, iPadOS 27, Mac Catalyst 27, tvOS 27, and visionOS 27, apps built with the latest SDK must adopt the scene-based life cycle or they fail to launch.」 

  4. Apple, UILaunchScreen, Information Property List 參考文件。字典,iOS 與 iPadOS 14.0 以上。「to configure the user interface during app launch in a way that doesn’t rely on storyboards」一語及各子鍵(UIColorNameUIImageNameUIImageRespectsSafeAreaInsetsUINavigationBarUITabBarUIToolbar)的出處。 

  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)、UIURLToLaunchScreenAssociationsUIDefaultLaunchScreen,以及「If you need only one launch screen, use UILaunchScreen instead.」的出處。 

  7. Apple, UILaunchStoryboards, Information Property List 參考文件。字典,iOS 與 iPadOS 9.0 以上。子鍵 UILaunchStoryboardDefinitionsUIDefaultLaunchStoryboardUIURLToLaunchStoryboardAssociations,以及「單一啟動 storyboard 請改用 UILaunchStoryboardName」這項指引的出處。 

  8. Apple, Build settings reference, Xcode 文件。GENERATE_INFOPLIST_FILE(「Automatically generate an Info.plist file」)、INFOPLIST_FILE(建置系統「merges the values you specify in this file with other values it generates during the build process」,且「When GENERATE_INFOPLIST_FILE is enabled, the build system also includes content from build settings in the merge process」)、INFOPLIST_KEY_UILaunchScreen_Generation(「sets the value of the UILaunchScreen key in the Info.plist file to an empty dictionary」)與 INFOPLIST_KEY_UILaunchStoryboardName 的出處。 

  9. Apple, Xcode 13 Release Notes, Templates, Resolved Issues(radar 68254857):「Projects created from several templates no longer require configuration files such as entitlements and Info.plist files. Configure common fields in the target’s Info tab, and build settings in the project editor. These files are added to the project when additional fields are used.」 

  10. Apple, UILaunchImages, Information Property List 參考文件。字典陣列,iOS 7.0 引入,iOS 13.0 起棄用。「UILaunchImages has been deprecated; use Xcode launch storyboards instead.」的出處。 

  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。 

相關文章

UIKit 的 Scene 強制令:哪些情況會在 iOS 27 上無法啟動

以 iOS 27 SDK 建置的 App 必須採用 UIKit 以 Scene 為基礎的生命週期,否則將無法啟動。本文說明時程、遷移步驟與相關的 agent skill。

3 分鐘閱讀

canOpenURL 已棄用:該改呼叫什麼

Apple 用三句話棄用了 canOpenURL,並把 scheme 允許清單的上限砍半到 25 筆。替代方案是什麼,以及它唯一做不到的那項檢查。

7 分鐘閱讀