iPhone 17 Multi-Window: UIScene Lifecycle Migration for Existing Apps
⚠️ Speculative Architecture & Preview: This article discusses future system iterations (e.g., iOS 27, Xcode 27) as conceptual planning and architectural design patterns. Technical details represent previews and proposals rather than finalized APIs.
iPhone 17 Multi-Window: UIScene Lifecycle Migration for Existing Apps
The iPhone 17 generation makes resizable, multi-scene usage a normal part of the iPhone experience rather than an iPad or macOS curiosity. For apps written against a single-window assumption, the result is a class of defects that reproduce only on device, only at certain sizes, and never in a default simulator run. An iPhone 17 multi-window migration is less about adding features than about relocating state that was implicitly global into a scope the system can create more than once.
This guide covers the migration for an app that already runs on iOS 27 but still assumes one window: moving global state to scene scope, deriving geometry from the scene rather than the screen, and eliminating the defects that only appear when the window resizes.
Key Takeaways
- The window is runtime state, not a singleton. Any
staticor shared state that holds UI geometry or view state must move to scene scope. - Derive geometry from the view or the scene, never from a device-level coordinate space that no longer matches the window.
- Resize defects are invisible in the simulator because the simulator frequently boots at a single full-screen size.
- Scene restoration is separate from state restoration. Each scene has its own session and its own state.
- Migrate geometry first; it resolves the majority of launch-only bugs before any feature work.
The Single-Window Assumption
Legacy apps encode the single-window assumption in ways that are easy to miss because they compile cleanly. The common carriers of the assumption:
| Pattern | Why it breaks under multi-window |
|---|---|
static let shared holding view state | One instance serves two scenes; scenes overwrite each other |
Cached screenBounds or frame | The cache is valid for one size; a resize invalidates it silently |
UIApplication.shared.delegate used as a state hub | There is one delegate, but many scenes |
| Global navigation stacks | Two scenes share one path and corrupt each other’s history |
Assumed keyWindow | No single key window exists once scenes are independent |
None of these produce a compiler error. All of them produce defects that are intermittent and device-only.
Step 1 — Move State to Scene Scope
The central migration is replacing app-scoped state with scene-scoped state. Where a scene delegate is the natural owner, hold the state there; where views own state, ensure it is created per scene rather than shared.
import UIKit
final class SceneCoordinator {
// Per-scene state. One coordinator per UISceneSession.
let navigation = UINavigationController()
private(set) var route: Route = .home
func navigate(to route: Route) {
self.route = route
navigation.pushViewController(route.viewController, animated: true)
}
}
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var coordinator: SceneCoordinator?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
let coordinator = SceneCoordinator()
self.coordinator = coordinator
let window = UIWindow(windowScene: windowScene)
window.rootViewController = coordinator.navigation
self.window = window
window.makeKeyAndVisible()
}
}
The rule of thumb: if two scenes could legitimately need different values, the state belongs to the scene. Shared data (a database, a settings store) may remain app-scoped; shared UI state may not.
Step 2 — Derive Geometry From the Scene
Any geometry read from a device-level source must be re-pointed at the scene or the view. This is the highest-yield change in the migration because it eliminates the largest share of resize defects.
// Before: device-level geometry, wrong the moment the window resizes.
let width = UIScreen.main.bounds.width // deprecated and incorrect for a window
// After: derive from the view that is actually being laid out.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let width = view.bounds.width
updateLayout(for: width)
}
For code that genuinely needs the scene’s coordinate space — overlay windows, custom presentations — read it from the scene, not the screen:
guard let scene = view.window?.windowScene else { return }
let sceneBounds = scene.coordinateSpace.bounds
If your app already completed the broader UIKit Adaptivity: The Four Audits Every iOS App Needs for iOS 27 work, much of this is already done. Multi-window migration is the subset that specifically concerns scene ownership and restoration.
Step 3 — Handle Per-Scene Restoration
Each scene restores independently, and restoration state must be scoped accordingly. A single global “restore this screen” flag will restore the wrong screen into the wrong scene.
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
// Encode state per scene, not globally.
guard let coordinator = self.coordinator else { return nil }
let activity = NSUserActivity(activityType: "in.iosdev.scene.restore")
activity.addUserInfoEntries(from: ["route": coordinator.route.encoded])
return activity
}
func scene(_ scene: UIScene, restoreInteractionStateWith activity: NSUserActivity) {
guard let encoded = activity.userInfo?["route"] as? String,
let route = Route(encoded: encoded) else { return }
coordinator?.restore(to: route)
}
The subtle failure here is restoring a route that references data no longer valid in the new scene’s context. Treat restoration as a request and validate it against current data before navigating.
Step 4 — Test at the Sizes You Are Not Testing
The reason these defects escape review is that the default simulator boot is one full-screen size. Add explicit resize coverage:
- Boot, then resize the window to its smallest supported size and walk the primary flows.
- Open two scenes simultaneously and exercise the shared-data paths between them.
- Rotate while a modal or overlay is presented.
- Background and foreground one scene while another stays active.
Any crash or layout corruption in these runs is a scene-scoping defect, and it will almost always trace back to a static or a cached geometry value from Step 1 or Step 2.
The Verdict: Relocate State Before Adding Features
Multi-window support is often scoped as a feature to be added. It is more accurately a correctness migration, and its cost is dominated by state relocation rather than new UI. Teams that treat it as a feature tend to bolt on window handling while leaving global state in place, which produces the worst outcome: apps that appear to support multiple windows but corrupt each other’s state.
When to migrate immediately
- Apps with shared navigation or session state, where a second scene will visibly break the first.
- Apps distributed to device types that ship multi-window by default.
When a phased approach is acceptable
- Apps with a single linear flow and no shared mutable UI state; these can relocate geometry first and defer scene restoration.
- Apps where multi-window is not an expected user behavior, provided the single-window assumptions are contained and audited.
The Hidden Cost
- The defects are device-only and intermittent. They will not be caught by a default CI simulator run, so the migration must include an explicit resize test matrix or it will ship broken.
staticstate is contagious. A single global cache of geometry or navigation state can invalidate otherwise-correct scene-scoped code, and finding it requires auditing shared state, not reading the changed files.- Restoration validation is easy to skip. Restoring a stale route into a new scene is a crash that only reproduces after a real app termination and relaunch, which makes it expensive to find after release.
The verdict: treat the iPhone 17 multi-window migration as a state-ownership audit first and a UI change second. Relocate UI state to scene scope and derive all geometry from the view or scene; the feature surface requires almost nothing once the ownership is correct.
Internal Links
- UIKit Adaptivity: The Four Audits Every iOS App Needs for iOS 27 — The broader adaptivity audit that this migration descends from.
- iPhone Fold Developer Guide: Adapting SwiftUI for Foldable Displays — Posture-driven, resizable layout for the next device class.
References & Further Reading
- Apple Developer Documentation — UIScene for scene lifecycle and sessions.
- Apple Developer Documentation — Supporting multiple windows for scene configuration.
- Apple Design — Layout for resizable layout expectations.