All Articles
CategoryTools
Reading Time
10 min read
Published
2026-10-15
Word Count
1,652words

Grab a coffee — this one is a deep dive!

sdkbump: will your app launch when you build it with the iOS 27 SDK?

Summary

A file-reading CLI that checks native iOS, Flutter and React Native/Expo projects for the UIScene change in the iOS 27 SDK and names the fix.

  • The rules are SCN001 to SCN004: Each rule was checked in the iOS 27.0 simulator.
  • Versions 0.1.0 and 0.1.1 had a wrong verdict.
  • Source code: [github.com/muhittincamdali/sdkbump](https://github.com/muhittincamdali/sdkbump)
  • An app with no UIApplicationSceneManifest configuration in its Info.plist and no application(_:configurationForConnecting:options:) compiles with Xcode 27 without a warning.
sdkbump: will your app launch when you build it with the iOS 27 SDK?

Table of Contents


The problem

Apple's page Transitioning to the UIKit scene-based life cycle says it in one sentence:

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.

The awkward part is where the failure shows up. An app with no UIApplicationSceneManifest configuration in its Info.plist and no application(_:configurationForConnecting:options:) compiles with Xcode 27 without a warning. The check runs inside UIKit when the app starts, and the process ends with "UIScene life cycle is required for apps built with this SDK". React Native 0.87 and earlier, Expo SDK 57 without its opt-in, Expo SDK 56 and earlier, and Flutter apps that the Flutter tool cannot migrate are all created this way.

A second change is quieter. Once the app has a scene, links, user activities, quick actions and the active/background transitions go to the scene delegate, and a window the app delegate creates belongs to no scene and is never shown.

sdkbump reads a project's files and answers the first question before a test device does, names the fix that applies, and lists the app delegate code that stops working once the app adopts scenes.

What it checks

The rules are SCN001 to SCN004:

  • SCN001 (error): no scene configuration. No configured UIApplicationSceneManifest, no configurationForConnecting, not a SwiftUI App.
  • SCN002 (error): the app delegate creates the window with UIWindow(frame:); once there is a scene, the screen stays black.
  • SCN003 (warning): 13 app delegate methods and launch option keys that UIKit no longer uses with scenes, unless the scene delegate already handles them.
  • SCN004 (warning): window lookups through UIApplication.shared.keyWindow, .windows or the app delegate's window.

Each rule was checked in the iOS 27.0 simulator. The repository has a launch matrix of nine minimal apps built with swiftc and launched on an iPhone 17 simulator with Xcode 27.0 (27A266a). Rows A, B and I stop at launch; C (configurationForConnecting in code) and D (a SwiftUI App) launch with one window; E, G and H launch with no visible window. Row F is the same code as A, linked against SDK 26.0, and it launches: the check depends on the SDK the app was linked with, not on the iOS version of the device.

Design decisions and alternatives

The pipeline is short, and the README draws it like this:

text
1path ─► detectProject native | flutter | react-native | expo; iOS folder; versions
2 ─► collectApp iOS app targets from project.pbxproj, their Info.plist (XML or binary),
3 Swift and Objective-C sources with comments and strings blanked
4 ─► checkApp SCN001…SCN004, fix per ecosystem and version, workaround packages
5 rules/ios27.json what each rule says, fixes, sources (validated with zod)
6 ─► render tty | markdown | json

Read files instead of building. Building and launching the app gives the most certain answer, but needs a Mac, Xcode 27 and minutes per run. Reading files needs only Node.js 22.12, also runs on Linux CI runners, and cannot change the project. Building and launching from the CLI is on the roadmap as --verify (0.3).

Rules as data. What each rule says, every fix, every app delegate hook and every workaround package lives in rules/ios27.json, validated with zod. Each entry carries a source URL from Apple, Flutter, React Native or Expo, and a test fails when one is missing. The data has its own version (ios27@2026.10.15), printed in every report, so a reader can tell how old the rules are. Text inside the code would be simpler, but its sources could not be checked.

Patterns over blanked source, not syntax trees. The detectors are regular expressions over Swift and Objective-C text in which comments and string contents are replaced by spaces. Line breaks and offsets stay the same, so a commented-out application(_:open:options:) does not match and line numbers still point at the right place. Class regions are found by brace matching, so a match only counts inside an app delegate or scene delegate where that matters. A syntax tree (tree-sitter or SwiftSyntax) would be more accurate, but it brings a WASM or native dependency; the README states the trade-off under Limits, and syntax trees are planned for 0.5. The runtime dependencies today are commander and zod.

A fix per ecosystem and version. "Adopt scenes" is not one instruction. For React Native it is 0.88 or a hand-written scene delegate; for Expo, SDK 57's opt-in or SDK 58; for Flutter, the tool's automatic migration when Flutter's own conditions hold, otherwise manual steps; for a SwiftUI app, the SwiftUI API. sdkbump reads the versions from package.json, pubspec.yaml or the FVM configuration and selects accordingly.

Workarounds are recognised. react-native-uiscene-bridge and react-native-xcode27 make a React Native or Expo app launch at run time without code changes. When one is installed, the findings it handles are shown as handled, with the package's limits, and do not fail the run.

The checked project is untrusted input. File names, plist values, target names and the INFOPLIST_FILE path all come from the project being scanned, which may be a pull request from someone else. Project files are read only when they are regular files inside the project folder (readProjectFile in src/core/files.ts). Since 0.1.3, render() replaces control characters in every string of the report once, before any format, and Markdown output cannot add links, images, HTML, headings or table cells.

A real bug: guessing about a file it never read

Versions 0.1.0 and 0.1.1 had a wrong verdict. When sdkbump could not read a target's Info.plist, it treated the target as if Xcode generated the file: SCN001 said "The Info.plist Xcode generates has no UIApplicationSceneManifest", the verdict was "Fails to launch" and the exit code was 1. That happened when INFOPLIST_FILE was set in an .xcconfig file, used a build setting sdkbump does not expand ($(CUSTOM_DIR)/Info.plist), named a missing file, or linked outside the project. Those apps could have a scene manifest and a scene delegate, and GENERATE_INFOPLIST_FILE was NO, so both the verdict and its wording were wrong. In CI, that fails a pull request for an app that launches.

The fix is PR #31, commit 25751e3 ("fix(scan): do not report a launch failure when the Info.plist was not read"), released in 0.1.2. Each target now records where its Info.plist came from in infoPlistSource: file, generated or not-read. A not-read target without configurationForConnecting gets an SCN001 info finding, "Scene configuration not checked", and the verdict code unknown, whose text is "Launch not determined: sdkbump could not read the Info.plist of …". The run exits 0 unless --fail-on info is passed. A target that names no INFOPLIST_FILE and does not have Xcode generate one gets a note that the settings may come from an .xcconfig file.

The lesson is about static analysis in general: a confident wrong answer costs more than "not determined". The same release added a Limits section to the README.

An earlier change of the same kind is recorded in the 0.1.0 changelog. In the first plan, SCN002 meant "no application(_:configurationForConnecting:options:)". The launch matrix showed that the method alone is enough to launch (row C), so the case became part of SCN001, and SCN002 became the window created in the app delegate, the black screen in rows E, G and H.

Usage

Open a terminal in the app's folder, the one with the .xcodeproj, pubspec.yaml or package.json, and run:

sh
1npx @mcamdali/sdkbump

The npm package is @mcamdali/sdkbump; the command it installs is sdkbump. An unscoped npx sdkbump does not run it. The first line under the project name is the verdict. "Fails to launch when built with the iOS 27 SDK" means the app stops at launch until the ERROR findings are fixed, and each one says which fix from the "How to fix" list applies.

To keep a Markdown report next to the code:

sh
1npx @mcamdali/sdkbump --format markdown --output sdkbump.md

For scripts, the JSON report:

sh
1npx @mcamdali/sdkbump --format json > sdkbump.json

The JSON layout is schemaVersion 0 and may change before 1.0. Exit codes are 0 when nothing unhandled is at or above --fail-on (default error), 1 when something is, and 2 for bad arguments, no iOS project found, or an unexpected error. From JavaScript, the package exports scan and render; examples/api/verdicts.mjs is a runnable example that the test suite also runs.

At v0.1.3 the suite has 280 tests in 18 files; they pass with npx vitest run in a fresh clone. CI runs them on Node.js 22 and 24 on Linux, and the README lists local runs on 22.23, 24.22 and 26.11.

Limits

These limits, all from the README, matter when reading a clean report:

  • Patterns, not a compiler. Generated code, code behind macros or code written in an unusual shape can be missed or matched by mistake.
  • Only the app's own sources. Pods, node_modules and Flutter plugins are not read, so a plugin that relies on app delegate methods UIKit no longer calls is not reported. Dependency checks are planned for 0.2.
  • Build settings from project.pbxproj only. sdkbump expands $(SRCROOT), $(PROJECT_DIR), $(TARGET_NAME) and $(PRODUCT_NAME), does not read .xcconfig files, and does not read an Info.plist outside the project folder.
  • No suppression file. A finding cannot be marked as reviewed; --fail-on decides which severities fail the run.
  • Files only. Nothing is built or launched, and the recordings in the repository are from the iOS 27.0 simulator, not from a device.

The rules describe Xcode 27.0 (27A266a); if Apple changes the behaviour, the rule data has to follow, which is why its version appears in every report.

Source code: github.com/muhittincamdali/sdkbump

Tags

#ios27#uiscene#flutter#react-native#expo#cli#typescript
Muhittin Çamdalı

Muhittin Çamdalı

Senior Mobile Engineer

Senior Mobile Engineer with 12+ years of experience. I build mobile products with iOS, Flutter and React Native, working full-stack and AI-native.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

Your subscription starts when you open the link in the confirmation email and press “Confirm my subscription”. The newsletter keeps open/click statistics; you can unsubscribe anytime with one click. Privacy

Share