Flutter iOS 27 launch crash: App Store rejection 2.1(a) and the UIScene fix

Xcode 27 builds without UIScene fail to launch. Read the crash log, migrate the Flutter project and test the release build before resubmitting.

Read time
14 min
Word count
2.3K
Sections
13
FAQs
8
Share
Title card on a Flutter App Store 2.1(a) rejection on iOS 27, with an Info.plist naming a SceneDelegate class
On this page · 13 sections
  1. What a 2.1(a) rejection means
  2. What the crash log can tell you
  3. Why iOS 27 will not launch the app
  4. Why a partial scene manifest is not enough
  5. Why testing can miss it
  6. How to confirm the diagnosis
  7. How to migrate a Flutter app
  8. Problems that appear after the migration
  9. Is this about foldables or the iPhone Duo?
  10. Does this affect React Native and other cross-platform apps?
  11. Pre-submission checklist
  12. How eCorpIT can help
  13. References

Summary. A Flutter app built with Xcode 27 that has not adopted the UIKit scene-based life cycle will not launch, and App Review rejects binaries that crash under Guideline 2.1(a), App Completeness. Apple's iOS and iPadOS 27 release notes say apps built with the latest SDK "must adopt the scene-based life cycle or they fail to launch". Flutter 3.41 and later migrate unmodified projects automatically, but a customised AppDelegate, plugins and lifecycle code need manual work. This guide covers reading the crash log, confirming the cause, migrating the project and testing the build before you resubmit.

About this article. This is a diagnostic guide for teams that have received a crash-on-launch rejection, not a report on one app. Platform claims are checked against Apple's and Flutter's documentation as of the date at the end. Researched with AI assistance and reviewed by our editorial team.

What a 2.1(a) rejection means

Guideline 2.1 sits in the Performance section of the App Review Guidelines, and part (a) is App Completeness. It asks developers to test on-device for bugs and stability, and says Apple will reject binaries that crash or show obvious technical problems.

When the reviewer's steps to reproduce amount to opening the app, the crash happens before any feature runs. That points the investigation at start-up: native initialisation, lifecycle configuration and the Info.plist that shipped in the archive. It does not prove the fault is in UIKit, and on its own it does not clear Dart code or plugins.

The rejection tells you what the reviewer saw. Finding the cause is still your job, and it starts with the crash report attached to the rejection.

What the crash log can tell you

Apple documents each field in a crash report's header. For a launch crash, read these first:

Field What to check
Process The crashed executable. In a Flutter project this is usually Runner.
Hardware Model and OS Version The exact device and OS build the reviewer used.
Version The version and build number; match them to the build you submitted.
Exception Type The Mach exception and its BSD signal, which describe how the process ended.
Triggered by Thread The thread where the exception started; read its top frames.
Launch Time and Date/Time A crash within moments of launch fits a start-up failure, but does not prove one.

The exception type guides where to look next. An unsymbolicated report is rarely useful, so symbolicate it first; when you upload debug symbols with the build, Xcode symbolicates App Store and TestFlight crash reports for you.

A UIKit frame near the top of the crashed thread that refers to scene life-cycle adoption is a strong clue. Airbridge reports that an Xcode 27 build without scene support terminates at launch with the runtime issue UIScene life cycle is required for apps built with this SDK. Treat that as a pointer to check, not as Apple's documented wording, and compare what you see with Apple's technical note TN3187 and with the Info.plist inside the built app.

Why iOS 27 will not launch the app

Apple has been warning about this since iOS 18.4. TN3187 records the console message added in that release, which said the app did not adopt the UIScene life cycle and that this would become an assert, and a firmer message in iOS 26 that adoption would soon be required. In iOS 27 the assert arrived. The iOS and iPadOS 27 release notes list it under UIKit deprecations, and Apple's transition guide applies the same rule to iPadOS 27, Mac Catalyst 27, tvOS 27 and visionOS 27.

Two details matter for a rejected build:

  • It follows the SDK, not the device. The rule applies to apps built with the latest SDK. A build from an older Xcode that launched last month says little about a build from Xcode 27.
  • Adoption is not the same as multiple windows. TN3187 says only adoption of the scene life cycle is required; supporting several scenes is encouraged but optional. A single-window app still needs a scene configuration.

The same release notes add a second rule worth checking in the same pass: apps built with the 27.0 SDK must declare a launch screen through UILaunchStoryboardName, UILaunchStoryboards, UILaunchScreen or UILaunchScreens, and App Review rejects apps without one. Flutter's iOS template sets UILaunchStoryboardName to LaunchScreen, so this mostly affects projects that removed or renamed it.

Why a partial scene manifest is not enough

Adding the key alone does not count. This manifest exists, but it gives UIKit no configuration to build the window from:


            <key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <false/>
</dict>
          

UIApplicationSupportsMultipleScenes answers one question: can the app show more than one scene at a time? It does not name a scene class, a delegate or a storyboard. Apple describes the manifest as a dictionary that holds both UIApplicationSupportsMultipleScenes and UISceneConfigurations. TN3187's test for whether you still need to migrate starts there: if the UIApplicationSceneManifest key is missing or has no configurations, you are not done.

The class named in UISceneDelegateClassName must also exist in the built app. A manifest that points at a SceneDelegate nobody wrote fixes nothing.

Why testing can miss it

Most teams that hit this rejection did test. The usual gaps:

  • The development Mac still builds with an older Xcode, while CI or the archiving machine uses Xcode 27.
  • The team tested a debug build; the reviewer ran the release archive.
  • The team tested on iPhone while the reviewer used an iPad. Because the rule follows the SDK, check first that both runs used the same archive.
  • A customised AppDelegate kept the project off Flutter's automatic migration path.
  • The source Info.plist differs from the one packaged in the archive, for example because a build phase or flavour rewrites it.
  • A plugin or native SDK depends on app-level lifecycle callbacks that stop firing after migration.

Test the exact archive, SDK and device families you are submitting. A clean debug run is not evidence.

How to confirm the diagnosis

Before changing the project, work through these checks in order:

  1. Symbolicate the crash report. Read the top frames of the crashed thread.
  1. Open the source plist. Look for UIApplicationSceneManifest in ios/Runner/Info.plist and check that it holds a full scene configuration.
  1. Check the app delegate. If it implements application(_:configurationForConnecting:options:), confirm it returns a valid configuration.
  1. Record the toolchain. Note the Flutter and Xcode versions that built the rejected archive.
  1. Search the start-up code. Look for early access to window, rootViewController or FlutterViewController.
  1. Inspect the archive. Read the Info.plist inside the built app, not just the source file.
  1. Reproduce it. Install the release build fresh on a device running the reviewer's OS version and cold-launch it.

Fix the cause the evidence points to. A scene manifest that names a missing class is not a fix.

How to migrate a Flutter app

The path depends on your Flutter version and on how far the iOS host project has been customised. Flutter's UIScene adoption guide is the source of truth, and the steps below summarise it. For the UIKit side, including apps with no Flutter at all, see our iOS 27 UISceneDelegate migration guide.

Let Flutter migrate it

UIScene support is the default from Flutter 3.41. If the project's AppDelegate has not been customised, running flutter run or flutter build ios migrates it, and the CLI prints this line when it succeeds:


            Finished migration to UIScene lifecycle
          

If the automatic migration cannot run, the CLI warns you and prints instructions for the manual route. Review and commit the changes it makes.

Add a complete scene manifest

For a manual migration, Flutter's guide gives this single-window configuration for Info.plist:


            <key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <false/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneClassName</key>
        <string>UIWindowScene</string>
        <key>UISceneDelegateClassName</key>
        <string>FlutterSceneDelegate</string>
        <key>UISceneConfigurationName</key>
        <string>flutter</string>
        <key>UISceneStoryboardFile</key>
        <string>Main</string>
      </dict>
    </array>
  </dict>
</dict>
          

If you add your own scene delegate in Swift, set UISceneDelegateClassName to $(PRODUCT_MODULE_NAME).SceneDelegate; in Objective-C, SceneDelegate is enough. Flutter's current app template does exactly this, with a SceneDelegate class that subclasses FlutterSceneDelegate. Do not name a custom class in the manifest unless the class is in the target.

Move plugin registration

In a customised AppDelegate, conform to FlutterImplicitEngineDelegate and move plugin registration into the implicit-engine callback. This is the shape of Flutter's current template:


            @main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
    GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
  }
}
          

Method channels and platform views created at launch move into the same callback and use engineBridge.applicationRegistrar.messenger() as their messenger. Compare the snippet with your own file rather than pasting it over custom code.

Stop reaching for the view controller at launch

Flutter's guide warns that accessing FlutterViewController in application(_:didFinishLaunchingWithOptions:) can crash the app, and singles out a force cast of window?.rootViewController as the pattern to remove. Under the scene life cycle the window belongs to the scene, which connects after that callback. If you need the controller, Flutter suggests subclassing FlutterViewController and doing the work in awakeFromNib, or moving it to scene(_:willConnectTo:options:) in a scene delegate.

Move UI lifecycle code to a scene delegate

After migration, UIKit stops calling the app delegate's UI-state events, such as applicationDidBecomeActive. Move that logic to the scene equivalents, for example sceneDidBecomeActive and scene(_:openURLContexts:). A custom scene delegate must subclass FlutterSceneDelegate or conform to FlutterSceneLifeCycleProvider so that Flutter still receives the events.

If you maintain a plugin

Plugins that use lifecycle events need the scene APIs added in Flutter 3.38. Raise the plugin's minimum Flutter version to 3.38, adopt FlutterSceneLifeCycleDelegate and register with registrar.addSceneDelegate(instance). Launch options are nil after migration, so logic that reads them belongs in scene(_:willConnectTo:options:).

Problems that appear after the migration

A build that launches can still misbehave. Check these before you resubmit.

Plugin registration order

Register plugins once, in the place your Flutter version expects. Airbridge documents an EXC_BAD_ACCESS crash near GeneratedPluginRegistrant.register(with:) that appeared after teams adopted the scene life cycle.

Cold launches from links and notifications

A deep link or notification tap that starts the app now arrives through the scene's connection options, not the app delegate's launch options. Test each entry point from a cold start. Courier's write-up covers what the change means for push notifications.

Work that must finish before launch completes

Some APIs still have to be set up before application(_:didFinishLaunchingWithOptions:) returns. Flutter's guide names BGTaskScheduler task registration, the UNUserNotificationCenter delegate and HealthKit background delivery. Do not move every initialisation call into the scene delegate.

Background and foreground behaviour

The callbacks behind foregrounding, backgrounding and returning to the app have moved, so test each transition again.

Temporary workarounds

Flutter documents a way to disable UIScene temporarily by putting an underscore in front of the Application Scene Manifest entry. Under the iOS 27 SDK that leaves the app without a scene life cycle, which is the failure you are fixing. Use it for diagnosis on older toolchains only, never in a release build.

Is this about foldables or the iPhone Duo?

No. A launch-time lifecycle assertion and a layout problem are different failures. A scene life-cycle crash stops the app before any interface exists; a layout problem shows up afterwards, as overlapping content or controls that do not adapt when the window changes size. Once the app launches reliably, base layout on the scene's or container's bounds rather than a fixed screen size, and test resizing and multitasking. Our iPhone Duo developer guide covers the foldable APIs separately. Do not blame a rejection on a form factor unless the crash evidence supports it.

Does this affect React Native and other cross-platform apps?

Yes. Every iOS app runs on a native UIKit host, whatever draws its interface, so React Native and web-view hybrid apps built with the iOS 27 SDK face the same rule. The steps differ by framework and version, so follow your framework's own guide and check that the generated iOS project has a complete scene configuration. Flutter draws its own interface rather than wrapping a web view, which is why it is usually called cross-platform rather than hybrid; the UIKit requirement applies either way.

Flutter teams moving to Xcode 27 should also read our notes on the CocoaPods sunset and the move to Swift Package Manager and on the mprotect failed 13 debug crash on iOS 26 and 27.

Pre-submission checklist

Before uploading a new build:

  • Build with the Xcode and iOS SDK you intend to ship with.
  • Test the release configuration, not only debug.
  • Delete the app, install the release build and cold-launch it on every supported device family, including iPad if you support it.
  • Read the scene manifest inside the archived app and confirm the delegate class it names exists.
  • Confirm Info.plist declares a launch screen, which the 27.0 SDK also requires.
  • Review plugin registration and any custom AppDelegate code.
  • Test deep links and notification launches from a cold start.
  • Test backgrounding, foregrounding, resizing and multitasking.
  • Confirm your plugins and native SDKs support the scene life cycle.
  • Increment the build number and archive from a clean build.
  • Keep the crash report and your passing test evidence with the release record.

When you resubmit, a short note to App Review explaining the cause, the change and how you tested it helps the reviewer. For the wider list of iOS 27 changes to regression-test, see our iOS 27 testing checklist for app teams.

How eCorpIT can help

eCorpIT is a Gurugram-based technology organisation, founded in 2021, assessed at CMMI Level 5 and MSME certified, with senior-led engineering teams working across AWS, Microsoft and Google platforms. Our Flutter and iOS teams audit iOS host projects for the scene life cycle, migrate customised app delegates and plugins, and test release builds on real iPhone and iPad hardware before an app goes back to review. Talk to us at /contact-us/.

Last updated: 21 October 2026.

References

  1. iOS & iPadOS 27 Release Notes — Apple Developer Documentation
  1. UIScene adoption — Flutter documentation
  1. App Review Guidelines, 2.1 App Completeness — Apple Developer
  1. Examining the fields in a crash report — Apple Developer Documentation
  1. Understanding the exception types in a crash report — Apple Developer Documentation
  1. Adding identifiable symbol names to a crash report — Apple Developer Documentation
  1. Xcode 27 SceneDelegate migration required, troubleshooting guide — Airbridge Help Center
  1. TN3187: Migrating to the UIKit scene-based life cycle — Apple Developer Documentation
  1. Transitioning to the UIKit scene-based life cycle — Apple Developer Documentation
  1. Flutter iOS app template (Info.plist, AppDelegate.swift, SceneDelegate.swift) — flutter/flutter on GitHub
  1. Specifying the scenes your app supports — Apple Developer Documentation
  1. How to migrate AppDelegate to UISceneDelegate (iOS 26 requirement) — Courier

Frequently asked

Quick answers.

01 What does App Store Guideline 2.1(a) mean in a crash rejection?
Guideline 2.1(a) is App Completeness, inside the Performance section. Apple asks developers to test on-device and rejects binaries that crash or show obvious technical problems. A crash at launch means the reviewer could not use the app at all, so start with the attached crash report and reproduce the failure before choosing a fix.
02 Does iOS 27 require the UIScene life cycle?
Yes, for apps built with the latest SDK. Apple's iOS and iPadOS 27 release notes say such apps must adopt the scene-based life cycle or they fail to launch, and Apple's transition guide applies the same rule to iPadOS, Mac Catalyst, tvOS and visionOS 27. Supporting multiple windows remains optional.
03 Is an app already on the App Store affected?
Apple ties the requirement to apps built with the latest SDK, so it bites when you rebuild an existing app with Xcode 27 for its next update. A binary built earlier was built under the old rules. Test the new build on real devices rather than assuming the published version predicts how it will behave.
04 Am I safe if I use Flutter 3.41 or later?
Not automatically. Flutter 3.41 and later migrate projects with an unmodified AppDelegate when you run flutter run or flutter build ios. A customised AppDelegate, custom lifecycle code or plugins that rely on app-level events still need manual changes. Follow Flutter's UIScene guide, then check that the archived build launches on a device.
05 Is adding UIApplicationSceneManifest to Info.plist enough?
No. The manifest needs a UISceneConfigurations entry naming a scene class, a configuration and a delegate class that exists in the app. TN3187 treats a manifest with no configurations as not migrated. Setting UIApplicationSupportsMultipleScenes to false only says the app shows one scene at a time; it does not configure that scene.
06 Why did the app crash on iPad when it worked on iPhone?
The scene requirement follows the SDK, not the device, so first check that both tests used the same archive. A local iPhone run from an older Xcode proves little about an Xcode 27 release build. Then test every supported device family, because reviewers may use an iPad you never tested on.
07 Can I disable UIScene to get past review?
No. Flutter documents a temporary switch that hides the scene manifest entry, but under the iOS 27 SDK an app without the scene life cycle fails to launch, which is the crash you are trying to fix. Use the switch only for diagnosis on older toolchains, and ship a properly migrated configuration.
08 Does this affect React Native and other cross-platform frameworks?
Yes. Any iOS app built with the latest SDK runs on a UIKit host, whatever draws its interface. React Native and web-view hybrid apps need the same scene adoption, but the steps differ by framework and version. Follow your framework's own migration guide, because Flutter's steps do not carry over unchanged.

About the author

Aman Mathur

Software Engineer | Flutter

Software Engineer with 3+ years building high-performance, AI-powered cross-platform mobile apps for global clients.

Subscribe

One engineering note a week. No fluff, no spam.

Senior-architect playbooks on AI agents, mobile apps, cloud, security, data, and marketing — delivered every Wednesday.

Past the reading

Read enough. Let's build something.

A senior architect responds in 24 working hours with scope, indicative cost, and a timeline. NDA before any technical conversation.