diff --git a/docs/main/updating/8-5.md b/docs/main/updating/8-5.md new file mode 100644 index 00000000..e816d1c9 --- /dev/null +++ b/docs/main/updating/8-5.md @@ -0,0 +1,142 @@ +--- +title: Updating to 8.5 +description: Guide for updating Capacitor from v8.4 to v8.5 in your app +slug: /updating/8-5 +--- + +# Updating from Capacitor 8.4 to Capacitor 8.5 + +Capacitor 8.5 adopts the iOS UIScene lifecycle. Xcode 27 requires it, so this ships as a breaking minor rather than waiting for Capacitor 9. The changes are iOS only. + +The core library still supports the AppDelegate path, so updating the dependency alone won't break your app. To build with Xcode 27, though, your app project needs to adopt the scene lifecycle: one new file, one Info.plist entry, and one method in your AppDelegate. + +## What changed in @capacitor/ios + +- A new `SceneDelegateProxy` (in `CAPSceneDelegateProxy.swift`) mirrors the existing `ApplicationDelegateProxy`. It forwards scene callbacks for URL opens, universal links, and scene connection. +- The legacy `.capacitorOpenURL` and `.capacitorOpenUniversalLink` notifications are still posted from the scene path with the same payload, so plugins that consume them keep working without changes. The Cordova `CDVPluginHandleOpenURL` notification is posted as well. +- New scene-scoped notifications: `.capacitorSceneWillConnect`, `.capacitorSceneOpenURL`, and `.capacitorSceneOpenUniversalLink`. These carry the originating `UIScene` as the notification `object`; the URL notifications carry their payload in `userInfo`. They exist so listeners can filter by scene when multi-window support arrives. Note they're only posted on 8.5 and later, so plugins that also support earlier Capacitor 8 versions should stay on the legacy notifications. +- The JS `resume` and `pause` document events are now driven by `UIScene.willEnterForegroundNotification` and `UIScene.didEnterBackgroundNotification`, filtered to the bridge's own scene. Apps that haven't adopted the scene manifest still receive these events; iOS posts scene notifications for the compatibility scene it creates for legacy apps. +- `WebViewDelegationHandler` now checks the web view's `windowScene.activationState` instead of `UIApplication.shared.applicationState` when deciding to hand navigation to the system. +- `ApplicationDelegateProxy.lastURL` is populated from the scene path, so `App.getLaunchUrl()` keeps working. +- Removed: `TmpViewController` and the long-deprecated `CapacitorBridge.tmpWindow` property and `tmpViewControllerAppeared` notification. + +## Update your iOS project + +Update the Capacitor packages first: + +```bash +npm i @capacitor/core@^8.5.0 @capacitor/ios@^8.5.0 +npm i -D @capacitor/cli@^8.5.0 +``` + +### 1. Add SceneDelegate.swift + +Create `App/App/SceneDelegate.swift`. The same file works for SPM and CocoaPods projects and matches the shipped templates: + +```swift +import UIKit +import Capacitor + +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 } + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = CAPBridgeViewController() + window?.makeKeyAndVisible() + + SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions) + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts) + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + SceneDelegateProxy.shared.scene(scene, continue: userActivity) + } +} +``` + +The delegate creates the window and root view controller in code; `Main.storyboard` no longer provides them. If you use a custom `CAPBridgeViewController` subclass, instantiate it here instead of setting it in the storyboard. + +### 2. Add the scene manifest to Info.plist + +```xml +UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + +``` + +### 3. Add the scene configuration hook to AppDelegate.swift + +```swift +func application(_ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions) -> UISceneConfiguration { + let config = UISceneConfiguration(name: "Default Configuration", + sessionRole: connectingSceneSession.role) + config.delegateClass = SceneDelegate.self + return config +} +``` + +Once the scene manifest is in place, iOS stops calling `application(_:open:options:)` and `application(_:continue:restorationHandler:)` on your AppDelegate and delivers those events to the scene delegate instead. You can remove the old methods or leave them; they no longer run. The app-level callbacks stay: `didFinishLaunchingWithOptions`, `applicationWillTerminate`, and the remote-notification callbacks (push token registration and delivery) keep working as before. The foreground/background lifecycle methods stop being called too; see the audit section below. + +### 4. Register the file in the Xcode project + +Add `SceneDelegate.swift` to the App target. Xcode does this automatically when you add the file through the IDE. If you edit `project.pbxproj` by hand, the file needs a `PBXFileReference`, a `PBXBuildFile`, an entry in the `App` group, and an entry in the target's Sources build phase. + +Then run `npx cap sync ios`. + +## Audit your custom code and plugins + +Most apps need nothing beyond the steps above. Check for these patterns in your own native code and in any custom plugins: + +- `UIApplication.shared.applicationState`: still works in a single-scene app, but scene-aware code should read the window scene's `activationState` instead. See `WebViewDelegationHandler` for the pattern. +- Custom logic inside `application(_:open:options:)` or `application(_:continue:restorationHandler:)`: these methods stop being called once the scene manifest exists. Move the logic into the matching `SceneDelegate` methods, alongside the `SceneDelegateProxy` forwarding calls. +- Custom logic inside the AppDelegate lifecycle methods (`applicationDidBecomeActive`, `applicationWillResignActive`, `applicationDidEnterBackground`, `applicationWillEnterForeground`): these also stop being called under the scene lifecycle. Move the code to the matching `SceneDelegate` methods, or observe the `UIApplication` notifications, which still fire. The template ships these methods as empty stubs, so this only affects apps that added code to them. +- Observers of `.capacitorOpenURL` or `.capacitorOpenUniversalLink`: no change needed. The payload shape is the same on the scene path. +- References to `tmpWindow` or `TmpViewController`: these were removed. Delete the code that touches them; the bridge's `viewController` is the presentation anchor. + +## Verify the migration + +After migrating, confirm on a device or simulator: + +- The app launches and renders normally. +- Backgrounding and foregrounding the app fires the JS `pause` and `resume` events. +- A custom URL scheme open reaches the app both cold (app killed, then link tapped) and warm (app running). Check both the `appUrlOpen` listener and `App.getLaunchUrl()`. +- Universal links route into the app, if your app uses them. + +## Using the CLI to Migrate + +Install the `latest` version of the Capacitor CLI to your project and run the migrator: + +```bash +npm i -D @capacitor/cli@latest +npx cap migrate +``` + +The migrator applies the project changes above (SceneDelegate, Info.plist manifest, AppDelegate hook, project file registration) for apps that still match the Capacitor template shape, and prints what it can't do automatically. Projects with hand-rolled scene delegates or custom AppDelegate URL handling should follow the manual steps instead. + +## Using the Migration Skill + +For the projects the CLI skips, the `capacitor-uiscene-migrator` skill in [ionic-team/capacitor-skills](https://github.com/ionic-team/capacitor-skills) walks the manual path with an AI agent: it audits your project and installed plugins, reports findings before changing anything, asks where a judgement call is needed (custom deep-link logic, an existing SceneDelegate), and merges into your files instead of overwriting them. Prompt it with "migrate my Capacitor app to UIScene". Template-shaped projects get handed back to `npx cap migrate`; this guide remains the source of truth for every step it doesn't automate. diff --git a/sidebars.js b/sidebars.js index 361b4be6..e29ff7ec 100644 --- a/sidebars.js +++ b/sidebars.js @@ -32,6 +32,7 @@ module.exports = { label: 'Upgrade Guides', collapsed: false, items: [ + 'main/updating/8-5', 'main/updating/8-0', 'main/updating/plugins/8-0', 'main/updating/7-0',