How to clear each sdkbump finding in an app you build with Xcode yourself. The native-uikit and native-uikit-scenes fixtures are the same app before and after these steps; compare them file by file.
Add a scene delegate. If the window was made in code, make it here instead (this also clears SCN002):
import UIKit
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = RootViewController()
window.makeKeyAndVisible()
self.window = window
}
}
Declare it in Info.plist (Xcode: target › General › Deployment Info › Scene manifest, or by hand):
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
With a main storyboard, add UISceneStoryboardFile (for example Main) to the configuration
and UIKit makes the window from it. Instead of the Info.plist entry you can implement
application(_:configurationForConnecting:options:) in the app delegate and return a
UISceneConfiguration whose delegateClass is SceneDelegate.self.
Leave the app delegate with process-level work only: third-party SDK setup, push registration, background tasks.
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES alone is not enough for a UIKit app:
Xcode then writes a manifest with an empty UISceneConfigurations, and UIKit stops the app at
launch just as without one (row I of the launch matrix).
UIWindow(frame:) in the app delegate gives a window no scene shows. Create it with
UIWindow(windowScene:) in scene(_:willConnectTo:options:) as above, and delete the window code
from application(_:didFinishLaunchingWithOptions:).
Once the app has a scene, UIKit calls the scene delegate for these, and stops calling the app
delegate even if the method is still there. A link or quick action that launches the app arrives
in connectionOptions of scene(_:willConnectTo:options:), not in launchOptions.
| App delegate | Scene delegate | SwiftUI |
|---|---|---|
application(_:open:options:) |
scene(_:openURLContexts:) |
.onOpenURL |
application(_:continue:restorationHandler:) |
scene(_:continue:) |
.onContinueUserActivity |
application(_:willContinueUserActivityWithType:) |
scene(_:willContinueUserActivityWithType:) |
|
application(_:didFailToContinueUserActivityWithType:error:) |
scene(_:didFailToContinueUserActivityWithType:error:) |
|
application(_:performActionFor:completionHandler:) |
windowScene(_:performActionFor:completionHandler:) |
|
applicationDidBecomeActive(_:) |
sceneDidBecomeActive(_:) |
scenePhase |
applicationWillResignActive(_:) |
sceneWillResignActive(_:) |
scenePhase |
applicationDidEnterBackground(_:) |
sceneDidEnterBackground(_:) |
scenePhase |
applicationWillEnterForeground(_:) |
sceneWillEnterForeground(_:) |
scenePhase |
launchOptions[.url] |
connectionOptions.urlContexts |
.onOpenURL |
launchOptions[.userActivityDictionary] |
connectionOptions.userActivities |
.onContinueUserActivity |
launchOptions[.shortcutItem] |
connectionOptions.shortcutItem |
|
launchOptions[.remoteNotification] |
UNUserNotificationCenterDelegate userNotificationCenter(_:didReceive:withCompletionHandler:) |
Each row comes from Apple's documentation of the app delegate method or launch option key; sdkbump
links the page with every finding. For the whole app's state rather than one scene's, Apple
suggests the UIApplication notifications (didBecomeActiveNotification and the others), which
UIKit posts with or without scenes.
A SwiftUI App always runs on scenes, so it never trips SCN001. An app delegate attached with
@UIApplicationDelegateAdaptor still compiles with the methods above, but UIKit does not call
them. In the native-swiftui fixture, the iOS 27 simulator moved
the app to the background and its applicationDidEnterBackground(_:) never ran. Use the SwiftUI
column instead:
WindowGroup {
ContentView()
.onOpenURL { url in router.open(url) }
}
.onChange(of: scenePhase) { _, phase in
if phase == .background { store.save() }
}
UIApplication.shared.keyWindow returns a key window across every connected scene and
UIApplication.shared.windows lists all of them; both are deprecated. The app delegate's window
property is no longer set by UIKit. From a view or view controller, use view.window or
view.window?.windowScene. Outside the view hierarchy, pick the foreground scene:
let scene = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }
let presenter = scene?.keyWindow?.rootViewController
Run sdkbump in the project folder again, as in the README's quick start:
npx @mcamdali/sdkbump
The native-uikit-scenes fixture reports no findings, and it launches in the iOS 27.0 simulator
(scripts/simulator-check.sh native-uikit-scenes). sdkbump does not build or launch your app;
building with Xcode 27 and opening the app once in the iOS 27 simulator is the final check.