From edd1e2e95c38d760531329dd022ae1f4be2e0500 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 12 Jun 2026 15:10:55 -0500 Subject: [PATCH 01/21] Starting on adding UIScene support --- .../Capacitor/CAPNotifications.swift | 2 + .../Capacitor/CAPSceneDelegateProxy.swift | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift diff --git a/ios/Capacitor/Capacitor/CAPNotifications.swift b/ios/Capacitor/Capacitor/CAPNotifications.swift index 03d80aa87b..9421b32af3 100644 --- a/ios/Capacitor/Capacitor/CAPNotifications.swift +++ b/ios/Capacitor/Capacitor/CAPNotifications.swift @@ -18,6 +18,7 @@ extension Notification.Name { public static let capacitorStatusBarTapped = Notification.Name(rawValue: "CapacitorStatusBarTappedNotification") public static let capacitorViewDidAppear = Notification.Name(rawValue: "CapacitorViewDidAppear") public static let capacitorViewWillTransition = Notification.Name(rawValue: "CapacitorViewWillTransition") + public static let capacitorSceneWillConnect = Notification.Name(rawValue: "CapacitorSceneWillConnect") } @objc extension NSNotification { @@ -30,6 +31,7 @@ extension Notification.Name { public static let capacitorStatusBarTapped = Notification.Name.capacitorStatusBarTapped public static let capacitorViewDidAppear = Notification.Name.capacitorViewDidAppear public static let capacitorViewWillTransition = Notification.Name.capacitorViewWillTransition + public static let capacitorSceneWillConnect = Notification.Name.capacitorSceneWillConnect } /** diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift new file mode 100644 index 0000000000..eb3a556741 --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -0,0 +1,85 @@ +// +// CAPSceneDelegateProxy.swift +// Capacitor +// +// Created by Joseph Orlando Pender on 6/10/26. +// Copyright © 2026 Drifty Co. All rights reserved. +// + +import Foundation + +@objc(CAPSceneDelegateProxy) +public class SceneDelegateProxy: NSObject, UISceneDelegate { + public static let shared = SceneDelegateProxy() + + public private(set) var lastURL: URL? + + public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + NotificationCenter.default.post(name: .capacitorSceneWillConnect, object: scene) + + if !connectionOptions.urlContexts.isEmpty { + self.scene(scene, openURLContexts: connectionOptions.urlContexts) + } + + for userActivity in connectionOptions.userActivities { + self.scene(scene, continue: userActivity) + } + } + + public func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + for context in URLContexts { + lastURL = context.url + NotificationCenter.default.post(name: .capacitorOpenURL, object: [ + "url": context.url, + "options": Self.openURLOptions(from: context.options) + ]) + } + } + + public func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let url = userActivity.webpageURL else { + return + } + lastURL = url + NotificationCenter.default.post(name: .capacitorOpenUniversalLink, object: [ + "url": url + ]) + } + + // Lifecycle hooks are intentionally stubbed until multi-window support lands. + // Until then, CapacitorBridge keeps observing the UIApplication.* equivalents, + // which still fire in scene-based apps for first-foreground / last-background. + + public func sceneDidDisconnect(_ scene: UIScene) { + // TODO: multi-window — tear down the bridge associated with this scene. + } + + public func sceneDidBecomeActive(_ scene: UIScene) { + // TODO: multi-window — forward per-scene active event to the bridge. + } + + public func sceneWillResignActive(_ scene: UIScene) { + // TODO: multi-window — forward per-scene resign-active event to the bridge. + } + + public func sceneWillEnterForeground(_ scene: UIScene) { + // TODO: multi-window — drive JS `resume` from this instead of UIApplication.willEnterForegroundNotification. + } + + public func sceneDidEnterBackground(_ scene: UIScene) { + // TODO: multi-window — drive JS `pause` from this instead of UIApplication.didEnterBackgroundNotification. + } + + private static func openURLOptions(from sceneOptions: UIScene.OpenURLOptions) -> [UIApplication.OpenURLOptionsKey: Any] { + var options: [UIApplication.OpenURLOptionsKey: Any] = [:] + if let sourceApplication = sceneOptions.sourceApplication { + options[.sourceApplication] = sourceApplication + } + if let annotation = sceneOptions.annotation { + options[.annotation] = annotation + } + options[.openInPlace] = sceneOptions.openInPlace + return options + } +} From 799226f8e99632e9b52e521002ec38b99d23c5c2 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 12 Jun 2026 15:18:44 -0500 Subject: [PATCH 02/21] use UIScene lifecycle notifications --- ios/Capacitor/Capacitor.xcodeproj/project.pbxproj | 4 ++++ ios/Capacitor/Capacitor/CapacitorBridge.swift | 9 +++++++-- ios/Capacitor/Capacitor/WebViewDelegationHandler.swift | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index b060d9f7d0..e4a9e93369 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 62FABD1A25AE5C01007B3814 /* Array+Capacitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */; }; 62FABD2325AE60BA007B3814 /* BridgedTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2225AE60BA007B3814 /* BridgedTypesTests.m */; }; 62FABD2B25AE6182007B3814 /* BridgedTypesHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */; }; + 952707712FD9DD2D0079E5D3 /* CAPSceneDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */; }; 957BD9402E78A4A50056874C /* SystemBars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957BD93E2E78A4A20056874C /* SystemBars.swift */; }; A327E6B628DB8B2900CA8B0A /* HttpRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */; }; A327E6B728DB8B2900CA8B0A /* CapacitorHttp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */; }; @@ -238,6 +239,7 @@ 62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Capacitor.swift"; sourceTree = ""; }; 62FABD2225AE60BA007B3814 /* BridgedTypesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BridgedTypesTests.m; sourceTree = ""; }; 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgedTypesHelper.swift; sourceTree = ""; }; + 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPSceneDelegateProxy.swift; sourceTree = ""; }; 957BD93E2E78A4A20056874C /* SystemBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemBars.swift; sourceTree = ""; }; A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequestHandler.swift; sourceTree = ""; }; A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorHttp.swift; sourceTree = ""; }; @@ -355,6 +357,7 @@ 62959AE12524DA7700A3D7F1 /* Capacitor */ = { isa = PBXGroup; children = ( + 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */, A7D9312C2B2370EF00FF59A2 /* Codable */, 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */, A76739782B98E09700795F7B /* PrivacyInfo.xcprivacy */, @@ -724,6 +727,7 @@ A7F7EDD5292BE8520015B73B /* CAPInstancePlugin.swift in Sources */, A38C3D7B2848BE6F004B3680 /* CapacitorCookieManager.swift in Sources */, 62959B1B2524DA7800A3D7F1 /* CAPFile.swift in Sources */, + 952707712FD9DD2D0079E5D3 /* CAPSceneDelegateProxy.swift in Sources */, 62959B462524DA7800A3D7F1 /* CAPBridge.swift in Sources */, 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */, diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 7b8b9b99dc..6e6f50228f 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -270,11 +270,16 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { exportCordovaJS() registerCordovaPlugins() } else { - observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in + observers.append(NotificationCenter.default.addObserver(forName: UIScene.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in + if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { self?.triggerDocumentJSEvent(eventName: "resume") + } + }) - observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in + observers.append(NotificationCenter.default.addObserver(forName: UIScene.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in + if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { self?.triggerDocumentJSEvent(eventName: "pause") + } }) } } diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift index 2b4a256d00..cab0279c4b 100644 --- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift @@ -107,8 +107,8 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat if !isApplicationNavigation, toplevelNavigation { // disallow and let the system handle it - if UIApplication.shared.applicationState == .active { - UIApplication.shared.open(navURL, options: [:], completionHandler: nil) + if webView.window?.windowScene?.activationState == .foregroundActive { + UIApplication.shared.open(navURL, options: [:], completionHandler: nil) } decisionHandler(.cancel) return From ca7ba2351a94cacdccc62b43adfa517db07a3d74 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 15 Jun 2026 09:42:40 -0500 Subject: [PATCH 03/21] scene-aware openURL and universal link notifications --- .../Capacitor/CAPNotifications.swift | 5 +++++ .../Capacitor/CAPSceneDelegateProxy.swift | 20 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ios/Capacitor/Capacitor/CAPNotifications.swift b/ios/Capacitor/Capacitor/CAPNotifications.swift index 9421b32af3..9731f74e4d 100644 --- a/ios/Capacitor/Capacitor/CAPNotifications.swift +++ b/ios/Capacitor/Capacitor/CAPNotifications.swift @@ -19,6 +19,9 @@ extension Notification.Name { public static let capacitorViewDidAppear = Notification.Name(rawValue: "CapacitorViewDidAppear") public static let capacitorViewWillTransition = Notification.Name(rawValue: "CapacitorViewWillTransition") public static let capacitorSceneWillConnect = Notification.Name(rawValue: "CapacitorSceneWillConnect") + public static let capacitorSceneOpenURL = Notification.Name(rawValue: "CapacitorSceneOpenURLNotification") + public static let capacitorSceneOpenUniversalLink = + Notification.Name(rawValue: "CapacitorSceneOpenUniversalLinkNotification") } @objc extension NSNotification { @@ -32,6 +35,8 @@ extension Notification.Name { public static let capacitorViewDidAppear = Notification.Name.capacitorViewDidAppear public static let capacitorViewWillTransition = Notification.Name.capacitorViewWillTransition public static let capacitorSceneWillConnect = Notification.Name.capacitorSceneWillConnect + public static let capacitorSceneOpenURL = Notification.Name.capacitorSceneOpenURL + public static let capacitorSceneOpenUniversalLink = Notification.Name.capacitorSceneOpenUniversalLink } /** diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift index eb3a556741..045ee7bad2 100644 --- a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -29,9 +29,17 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { public func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { for context in URLContexts { lastURL = context.url + let options = Self.openURLOptions(from: context.options) + + // Capacitor 8 backwards compat NotificationCenter.default.post(name: .capacitorOpenURL, object: [ "url": context.url, - "options": Self.openURLOptions(from: context.options) + "options": options + ]) + + NotificationCenter.default.post(name: .capacitorSceneOpenURL, object: scene, userInfo: [ + "url": context.url, + "options": options ]) } } @@ -42,14 +50,18 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { return } lastURL = url + + // Capacitor 8 backwards compat NotificationCenter.default.post(name: .capacitorOpenUniversalLink, object: [ "url": url ]) + + NotificationCenter.default.post(name: .capacitorSceneOpenUniversalLink, object: scene, userInfo: [ + "url": url + ]) } - // Lifecycle hooks are intentionally stubbed until multi-window support lands. - // Until then, CapacitorBridge keeps observing the UIApplication.* equivalents, - // which still fire in scene-based apps for first-foreground / last-background. + // TODO: Not until Phase 2 of the UI modernization project public func sceneDidDisconnect(_ scene: UIScene) { // TODO: multi-window — tear down the bridge associated with this scene. From 8e219f121d0486638eb076fa23928e6b13b8ef39 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 15 Jun 2026 09:56:41 -0500 Subject: [PATCH 04/21] remove TmpViewController --- ios/Capacitor/Capacitor.xcodeproj/project.pbxproj | 4 ---- ios/Capacitor/Capacitor/CapacitorBridge.swift | 7 ------- ios/Capacitor/Capacitor/TmpViewController.swift | 8 -------- 3 files changed, 19 deletions(-) delete mode 100644 ios/Capacitor/Capacitor/TmpViewController.swift diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index e4a9e93369..6cd33b6005 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 62959B3A2524DA7800A3D7F1 /* CAPLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B082524DA7700A3D7F1 /* CAPLog.swift */; }; 62959B3B2524DA7800A3D7F1 /* CAPPluginMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */; settings = {ATTRIBUTES = (Public, ); }; }; 62959B3C2524DA7800A3D7F1 /* CAPBridgeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B0A2524DA7700A3D7F1 /* CAPBridgeDelegate.swift */; }; - 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */; }; 62959B412524DA7800A3D7F1 /* Capacitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959B0F2524DA7700A3D7F1 /* Capacitor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 62959B422524DA7800A3D7F1 /* DocLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B102524DA7700A3D7F1 /* DocLinks.swift */; }; 62959B432524DA7800A3D7F1 /* Data+Capacitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B112524DA7700A3D7F1 /* Data+Capacitor.swift */; }; @@ -205,7 +204,6 @@ 62959B082524DA7700A3D7F1 /* CAPLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPLog.swift; sourceTree = ""; }; 62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPPluginMethod.h; sourceTree = ""; }; 62959B0A2524DA7700A3D7F1 /* CAPBridgeDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPBridgeDelegate.swift; sourceTree = ""; }; - 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TmpViewController.swift; sourceTree = ""; }; 62959B0F2524DA7700A3D7F1 /* Capacitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Capacitor.h; sourceTree = ""; }; 62959B102524DA7700A3D7F1 /* DocLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocLinks.swift; sourceTree = ""; }; 62959B112524DA7700A3D7F1 /* Data+Capacitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Capacitor.swift"; sourceTree = ""; }; @@ -398,7 +396,6 @@ 6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */, 62959B082524DA7700A3D7F1 /* CAPLog.swift */, 62959AE72524DA7700A3D7F1 /* CAPFile.swift */, - 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */, 62959B102524DA7700A3D7F1 /* DocLinks.swift */, 62959B152524DA7700A3D7F1 /* CAPNotifications.swift */, 62959B072524DA7700A3D7F1 /* CapacitorExtension.swift */, @@ -713,7 +710,6 @@ A7D9312F2B23710300FF59A2 /* JSValueDecoder.swift in Sources */, 62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */, 621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */, - 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */, 621ECCD6254205BD00D3D615 /* CAPBridgeProtocol.swift in Sources */, 62D43AF02581817500673C24 /* WKWebView+Capacitor.swift in Sources */, 62959B432524DA7800A3D7F1 /* Data+Capacitor.swift in Sources */, diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 6e6f50228f..58af7819ca 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -93,10 +93,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { } } } - @available(*, deprecated, message: "obsolete") - var tmpWindow: UIWindow? - @available(*, deprecated, message: "obsolete") - static let tmpVCAppeared = Notification(name: Notification.Name(rawValue: "tmpViewControllerAppeared")) public static let capacitorSite = "https://capacitorjs.com/" public static let fileStartIdentifier = "/_capacitor_file_" public static let httpInterceptorStartIdentifier = "/_capacitor_http_interceptor_" @@ -223,9 +219,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { setupCordovaCompatibility() exportMiscJS() canInjectJS = false - observers.append(NotificationCenter.default.addObserver(forName: type(of: self).tmpVCAppeared.name, object: .none, queue: .none) { [weak self] _ in - self?.tmpWindow = nil - }) self.setupWebDebugging(configuration: configuration) } diff --git a/ios/Capacitor/Capacitor/TmpViewController.swift b/ios/Capacitor/Capacitor/TmpViewController.swift deleted file mode 100644 index 3511d18aed..0000000000 --- a/ios/Capacitor/Capacitor/TmpViewController.swift +++ /dev/null @@ -1,8 +0,0 @@ -import UIKit - -internal class TmpViewController: UIViewController { - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - NotificationCenter.default.post(CapacitorBridge.tmpVCAppeared) - } -} From 4bd1bc3b546e6a8acbd01a36e5f902a7b4d062d3 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 22 Jun 2026 10:49:16 -0500 Subject: [PATCH 05/21] adding CapacitorView --- .../Capacitor.xcodeproj/project.pbxproj | 4 ++++ ios/Capacitor/Capacitor/CapacitorView.swift | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 ios/Capacitor/Capacitor/CapacitorView.swift diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index 6cd33b6005..4a866b6255 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 62FABD2B25AE6182007B3814 /* BridgedTypesHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */; }; 952707712FD9DD2D0079E5D3 /* CAPSceneDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */; }; 957BD9402E78A4A50056874C /* SystemBars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957BD93E2E78A4A20056874C /* SystemBars.swift */; }; + 9582B6802FE98C910072D4E8 /* CapacitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */; }; A327E6B628DB8B2900CA8B0A /* HttpRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */; }; A327E6B728DB8B2900CA8B0A /* CapacitorHttp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */; }; A327E6B828DB8B2900CA8B0A /* CapacitorUrlRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B528DB8B2900CA8B0A /* CapacitorUrlRequest.swift */; }; @@ -239,6 +240,7 @@ 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgedTypesHelper.swift; sourceTree = ""; }; 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPSceneDelegateProxy.swift; sourceTree = ""; }; 957BD93E2E78A4A20056874C /* SystemBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemBars.swift; sourceTree = ""; }; + 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorView.swift; sourceTree = ""; }; A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequestHandler.swift; sourceTree = ""; }; A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorHttp.swift; sourceTree = ""; }; A327E6B528DB8B2900CA8B0A /* CapacitorUrlRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorUrlRequest.swift; sourceTree = ""; }; @@ -355,6 +357,7 @@ 62959AE12524DA7700A3D7F1 /* Capacitor */ = { isa = PBXGroup; children = ( + 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */, 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */, A7D9312C2B2370EF00FF59A2 /* Codable */, 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */, @@ -727,6 +730,7 @@ 62959B462524DA7800A3D7F1 /* CAPBridge.swift in Sources */, 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */, + 9582B6802FE98C910072D4E8 /* CapacitorView.swift in Sources */, 62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */, 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */, 62ADC0CA25CB678000E914DE /* PluginCallResult.swift in Sources */, diff --git a/ios/Capacitor/Capacitor/CapacitorView.swift b/ios/Capacitor/Capacitor/CapacitorView.swift new file mode 100644 index 0000000000..fbbae46935 --- /dev/null +++ b/ios/Capacitor/Capacitor/CapacitorView.swift @@ -0,0 +1,16 @@ +// +// CapacitorView.swift +// Capacitor +// +// Created by Joseph Orlando Pender on 6/22/26. +// Copyright © 2026 Drifty Co. All rights reserved. +// +import SwiftUI + +public struct CapacitorView: UIViewControllerRepresentable { + public func makeUIViewController(context: Context) -> CAPBridgeViewController { + CAPBridgeViewController() + } + + public func updateUIViewController(_ vc: CAPBridgeViewController, context: Context) {} +} From 2f14984f23dfb879eec2ed433fb83a3ed181d23e Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 22 Jun 2026 11:19:17 -0500 Subject: [PATCH 06/21] updating templates for UIScene changes --- .../App/App.xcodeproj/project.pbxproj | 4 +++ ios-pods-template/App/App/AppDelegate.swift | 9 +++++++ ios-pods-template/App/App/Info.plist | 19 ++++++++++++++ ios-pods-template/App/App/SceneDelegate.swift | 26 +++++++++++++++++++ .../App/App.xcodeproj/project.pbxproj | 4 +++ ios-spm-template/App/App/AppDelegate.swift | 21 ++++++--------- ios-spm-template/App/App/Info.plist | 21 ++++++++++++++- ios-spm-template/App/App/SceneDelegate.swift | 20 ++++++++++++++ 8 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 ios-pods-template/App/App/SceneDelegate.swift create mode 100644 ios-spm-template/App/App/SceneDelegate.swift diff --git a/ios-pods-template/App/App.xcodeproj/project.pbxproj b/ios-pods-template/App/App.xcodeproj/project.pbxproj index 092f574919..75dc9750fa 100644 --- a/ios-pods-template/App/App.xcodeproj/project.pbxproj +++ b/ios-pods-template/App/App.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + 9582B6852FE996820072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6842FE996800072D4E8 /* SceneDelegate.swift */; }; A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; /* End PBXBuildFile section */ @@ -27,6 +28,7 @@ 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + 9582B6842FE996800072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; @@ -73,6 +75,7 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( + 9582B6842FE996800072D4E8 /* SceneDelegate.swift */, 50379B222058CBB4000EE86E /* capacitor.config.json */, 504EC3071FED79650016851F /* AppDelegate.swift */, 504EC30B1FED79650016851F /* Main.storyboard */, @@ -210,6 +213,7 @@ buildActionMask = 2147483647; files = ( 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + 9582B6852FE996820072D4E8 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift index c3cd83b5c0..0198f78130 100644 --- a/ios-pods-template/App/App/AppDelegate.swift +++ b/ios-pods-template/App/App/AppDelegate.swift @@ -45,5 +45,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) } + + 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 + } } diff --git a/ios-pods-template/App/App/Info.plist b/ios-pods-template/App/App/Info.plist index ed6ff0bd4d..f8c8a57659 100644 --- a/ios-pods-template/App/App/Info.plist +++ b/ios-pods-template/App/App/Info.plist @@ -22,6 +22,25 @@ $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/ios-pods-template/App/App/SceneDelegate.swift b/ios-pods-template/App/App/SceneDelegate.swift new file mode 100644 index 0000000000..412fe45746 --- /dev/null +++ b/ios-pods-template/App/App/SceneDelegate.swift @@ -0,0 +1,26 @@ +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) + } +} + + diff --git a/ios-spm-template/App/App.xcodeproj/project.pbxproj b/ios-spm-template/App/App.xcodeproj/project.pbxproj index 46f934efd5..56a0b7d40f 100644 --- a/ios-spm-template/App/App.xcodeproj/project.pbxproj +++ b/ios-spm-template/App/App.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + 9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +28,7 @@ 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -62,6 +64,7 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( + 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */, 50379B222058CBB4000EE86E /* capacitor.config.json */, 504EC3071FED79650016851F /* AppDelegate.swift */, 504EC30B1FED79650016851F /* Main.storyboard */, @@ -156,6 +159,7 @@ buildActionMask = 2147483647; files = ( 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + 9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios-spm-template/App/App/AppDelegate.swift b/ios-spm-template/App/App/AppDelegate.swift index c3cd83b5c0..65256aaac9 100644 --- a/ios-spm-template/App/App/AppDelegate.swift +++ b/ios-spm-template/App/App/AppDelegate.swift @@ -32,18 +32,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - // Called when the app was launched with a url. Feel free to add additional processing here, - // but if you want the App API to support tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(app, open: url, options: options) - } - - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - // Called when the app was launched with an activity, including Universal Links. - // Feel free to add additional processing here, but if you want the App API to support - // tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) + + 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 } - } diff --git a/ios-spm-template/App/App/Info.plist b/ios-spm-template/App/App/Info.plist index 42bcf67503..fc41ce69bc 100644 --- a/ios-spm-template/App/App/Info.plist +++ b/ios-spm-template/App/App/Info.plist @@ -2,7 +2,7 @@ - CAPACITOR_DEBUG + CAPACITOR_DEBUG $(CAPACITOR_DEBUG) CFBundleDevelopmentRegion en @@ -24,6 +24,25 @@ $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift new file mode 100644 index 0000000000..c60c9a8ac3 --- /dev/null +++ b/ios-spm-template/App/App/SceneDelegate.swift @@ -0,0 +1,20 @@ +import UIKit +import Capacitor + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + 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) + } +} + + From 1f3e8d24b2d9ca843f69a198da8613829ecf1304 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 22 Jun 2026 12:20:02 -0500 Subject: [PATCH 07/21] remove CapacitorView from core --- .../Capacitor.xcodeproj/project.pbxproj | 4 ---- ios/Capacitor/Capacitor/CapacitorView.swift | 16 ---------------- 2 files changed, 20 deletions(-) delete mode 100644 ios/Capacitor/Capacitor/CapacitorView.swift diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index 4a866b6255..6cd33b6005 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -81,7 +81,6 @@ 62FABD2B25AE6182007B3814 /* BridgedTypesHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */; }; 952707712FD9DD2D0079E5D3 /* CAPSceneDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */; }; 957BD9402E78A4A50056874C /* SystemBars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957BD93E2E78A4A20056874C /* SystemBars.swift */; }; - 9582B6802FE98C910072D4E8 /* CapacitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */; }; A327E6B628DB8B2900CA8B0A /* HttpRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */; }; A327E6B728DB8B2900CA8B0A /* CapacitorHttp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */; }; A327E6B828DB8B2900CA8B0A /* CapacitorUrlRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A327E6B528DB8B2900CA8B0A /* CapacitorUrlRequest.swift */; }; @@ -240,7 +239,6 @@ 62FABD2A25AE6182007B3814 /* BridgedTypesHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgedTypesHelper.swift; sourceTree = ""; }; 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPSceneDelegateProxy.swift; sourceTree = ""; }; 957BD93E2E78A4A20056874C /* SystemBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemBars.swift; sourceTree = ""; }; - 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorView.swift; sourceTree = ""; }; A327E6B228DB8B2800CA8B0A /* HttpRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequestHandler.swift; sourceTree = ""; }; A327E6B428DB8B2900CA8B0A /* CapacitorHttp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorHttp.swift; sourceTree = ""; }; A327E6B528DB8B2900CA8B0A /* CapacitorUrlRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorUrlRequest.swift; sourceTree = ""; }; @@ -357,7 +355,6 @@ 62959AE12524DA7700A3D7F1 /* Capacitor */ = { isa = PBXGroup; children = ( - 9582B67E2FE98C8D0072D4E8 /* CapacitorView.swift */, 9527076F2FD9DD260079E5D3 /* CAPSceneDelegateProxy.swift */, A7D9312C2B2370EF00FF59A2 /* Codable */, 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */, @@ -730,7 +727,6 @@ 62959B462524DA7800A3D7F1 /* CAPBridge.swift in Sources */, 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */, - 9582B6802FE98C910072D4E8 /* CapacitorView.swift in Sources */, 62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */, 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */, 62ADC0CA25CB678000E914DE /* PluginCallResult.swift in Sources */, diff --git a/ios/Capacitor/Capacitor/CapacitorView.swift b/ios/Capacitor/Capacitor/CapacitorView.swift deleted file mode 100644 index fbbae46935..0000000000 --- a/ios/Capacitor/Capacitor/CapacitorView.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// CapacitorView.swift -// Capacitor -// -// Created by Joseph Orlando Pender on 6/22/26. -// Copyright © 2026 Drifty Co. All rights reserved. -// -import SwiftUI - -public struct CapacitorView: UIViewControllerRepresentable { - public func makeUIViewController(context: Context) -> CAPBridgeViewController { - CAPBridgeViewController() - } - - public func updateUIViewController(_ vc: CAPBridgeViewController, context: Context) {} -} From c4a5f340dc8ddbfd67b74c4491ed021194341935 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 22 Jun 2026 12:25:54 -0500 Subject: [PATCH 08/21] Add SwiftUI App-struct support to SceneDelegateProxy and bridge --- ios-pods-template/App/App/AppDelegate.swift | 4 +- ios-pods-template/App/App/SceneDelegate.swift | 32 ++-- ios-spm-template/App/App/AppDelegate.swift | 4 +- ios-spm-template/App/App/SceneDelegate.swift | 31 ++-- .../Capacitor/CAPSceneDelegateProxy.swift | 150 ++++++++++++++++-- ios/Capacitor/Capacitor/CapacitorBridge.swift | 47 +++++- .../Capacitor/WebViewDelegationHandler.swift | 2 +- 7 files changed, 214 insertions(+), 56 deletions(-) diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift index 0198f78130..7ad11d83ed 100644 --- a/ios-pods-template/App/App/AppDelegate.swift +++ b/ios-pods-template/App/App/AppDelegate.swift @@ -45,11 +45,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) } - + 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 diff --git a/ios-pods-template/App/App/SceneDelegate.swift b/ios-pods-template/App/App/SceneDelegate.swift index 412fe45746..f352e1e959 100644 --- a/ios-pods-template/App/App/SceneDelegate.swift +++ b/ios-pods-template/App/App/SceneDelegate.swift @@ -2,25 +2,23 @@ import UIKit import Capacitor class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? + 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, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts) - } + window = UIWindow(windowScene: windowScene) + window?.rootViewController = CAPBridgeViewController() + window?.makeKeyAndVisible() - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - SceneDelegateProxy.shared.scene(scene, continue: userActivity) - } -} + 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) + } +} diff --git a/ios-spm-template/App/App/AppDelegate.swift b/ios-spm-template/App/App/AppDelegate.swift index 65256aaac9..fec95acae1 100644 --- a/ios-spm-template/App/App/AppDelegate.swift +++ b/ios-spm-template/App/App/AppDelegate.swift @@ -32,12 +32,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let config = UISceneConfiguration(name: "Default Configuration", - sessionRole: connectingSceneSession.role) + sessionRole: connectingSceneSession.role) config.delegateClass = SceneDelegate.self return config } diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift index c60c9a8ac3..3e27adf244 100644 --- a/ios-spm-template/App/App/SceneDelegate.swift +++ b/ios-spm-template/App/App/SceneDelegate.swift @@ -1,20 +1,27 @@ -import UIKit import Capacitor +import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? + var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions) - } + func scene( + _ scene: UIScene, willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { return } - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts) - } + window = UIWindow(windowScene: windowScene) + window?.rootViewController = CAPBridgeViewController() + window?.makeKeyAndVisible() - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - SceneDelegateProxy.shared.scene(scene, continue: userActivity) - } -} + 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) + } +} diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift index 045ee7bad2..9aabf713a6 100644 --- a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -14,7 +14,10 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { public private(set) var lastURL: URL? - public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + public func scene( + _ scene: UIScene, willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { NotificationCenter.default.post(name: .capacitorSceneWillConnect, object: scene) if !connectionOptions.urlContexts.isEmpty { @@ -32,33 +35,140 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { let options = Self.openURLOptions(from: context.options) // Capacitor 8 backwards compat - NotificationCenter.default.post(name: .capacitorOpenURL, object: [ - "url": context.url, + NotificationCenter.default.post( + name: .capacitorOpenURL, + object: [ + "url": context.url, + "options": options + ]) + + NotificationCenter.default.post( + name: .capacitorSceneOpenURL, object: scene, + userInfo: [ + "url": context.url, + "options": options + ]) + } + } + + public func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let url = userActivity.webpageURL + else { + return + } + lastURL = url + + // Capacitor 8 backwards compat + NotificationCenter.default.post( + name: .capacitorOpenUniversalLink, + object: [ + "url": url + ]) + + NotificationCenter.default.post( + name: .capacitorSceneOpenUniversalLink, object: scene, + userInfo: [ + "url": url + ]) + } + + /// Routes a URL into Capacitor's open-URL handlers from a SwiftUI App-struct app. + /// + /// This is the recommended integration point for apps whose root is a `SwiftUI.App` + /// and which therefore do not declare an explicit `UISceneDelegate` subclass. + /// Call it from the `.onOpenURL` modifier inside the scene body: + /// + /// ```swift + /// WindowGroup { + /// CapacitorView() + /// .onOpenURL { url in + /// SceneDelegateProxy.shared.handle(openURL: url) + /// } + /// } + /// ``` + /// + /// Posts the same notifications as the `scene(_:openURLContexts:)` protocol path — + /// both `.capacitorOpenURL` (Capacitor 8 back-compat payload) and + /// `.capacitorSceneOpenURL` (scene-aware, with the resolved scene as the notification + /// object and the URL in `userInfo`). + /// + /// - Parameters: + /// - openURL: The URL to route. + /// - scene: The scene that received the URL. SwiftUI's `.onOpenURL` does not + /// surface a scene reference, so this defaults to `nil`; when `nil`, the active + /// foreground `UIWindowScene` is resolved from + /// `UIApplication.shared.connectedScenes`. For single-scene apps (the Phase 1 + /// default) this is unambiguous; multi-scene URL routing is Phase 2. + public func handle(openURL: URL, scene: UIScene? = nil) { + let targetScene = scene ?? Self.activeForegroundScene() + lastURL = openURL + let options: [UIApplication.OpenURLOptionsKey: Any] = [:] + + // Capacitor 8 backwards compat + NotificationCenter.default.post( + name: .capacitorOpenURL, + object: [ + "url": openURL, "options": options ]) - NotificationCenter.default.post(name: .capacitorSceneOpenURL, object: scene, userInfo: [ - "url": context.url, + NotificationCenter.default.post( + name: .capacitorSceneOpenURL, object: targetScene, + userInfo: [ + "url": openURL, "options": options ]) - } } - public func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + /// Routes a browsing-web `NSUserActivity` into Capacitor's universal-link handlers + /// from a SwiftUI App-struct app. + /// + /// This is the recommended integration point for SwiftUI App-struct apps. Call it + /// from the `.onContinueUserActivity` modifier inside the scene body: + /// + /// ```swift + /// WindowGroup { + /// CapacitorView() + /// .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in + /// SceneDelegateProxy.shared.handle(userActivity: activity) + /// } + /// } + /// ``` + /// + /// Only activities with `activityType == NSUserActivityTypeBrowsingWeb` and a + /// non-nil `webpageURL` produce notifications; other activity types are silently + /// ignored, matching the `scene(_:continue:)` protocol path. When a notification is + /// produced, both `.capacitorOpenUniversalLink` (Capacitor 8 back-compat payload) + /// and `.capacitorSceneOpenUniversalLink` (scene-aware) are posted. + /// + /// - Parameters: + /// - userActivity: The activity to inspect. + /// - scene: The scene that received the activity. SwiftUI does not surface a + /// scene reference here either, so this defaults to `nil`; when `nil`, the + /// active foreground `UIWindowScene` is resolved from + /// `UIApplication.shared.connectedScenes`. + public func handle(userActivity: NSUserActivity, scene: UIScene? = nil) { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let url = userActivity.webpageURL else { + let url = userActivity.webpageURL + else { return } + let targetScene = scene ?? Self.activeForegroundScene() lastURL = url // Capacitor 8 backwards compat - NotificationCenter.default.post(name: .capacitorOpenUniversalLink, object: [ - "url": url - ]) + NotificationCenter.default.post( + name: .capacitorOpenUniversalLink, + object: [ + "url": url + ]) - NotificationCenter.default.post(name: .capacitorSceneOpenUniversalLink, object: scene, userInfo: [ - "url": url - ]) + NotificationCenter.default.post( + name: .capacitorSceneOpenUniversalLink, object: targetScene, + userInfo: [ + "url": url + ]) } // TODO: Not until Phase 2 of the UI modernization project @@ -83,7 +193,17 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { // TODO: multi-window — drive JS `pause` from this instead of UIApplication.didEnterBackgroundNotification. } - private static func openURLOptions(from sceneOptions: UIScene.OpenURLOptions) -> [UIApplication.OpenURLOptionsKey: Any] { + private static func activeForegroundScene() -> UIWindowScene? { + let scenes = UIApplication.shared.connectedScenes + if let active = scenes.first(where: { $0.activationState == .foregroundActive }) + as? UIWindowScene { + return active + } + return scenes.first(where: { $0.activationState == .foregroundInactive }) as? UIWindowScene + } + + private static func openURLOptions(from sceneOptions: UIScene.OpenURLOptions) -> [UIApplication + .OpenURLOptionsKey: Any] { var options: [UIApplication.OpenURLOptionsKey: Any] = [:] if let sourceApplication = sceneOptions.sourceApplication { options[.sourceApplication] = sourceApplication diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 58af7819ca..b13ab9fafc 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -263,20 +263,53 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { exportCordovaJS() registerCordovaPlugins() } else { - observers.append(NotificationCenter.default.addObserver(forName: UIScene.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in - if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { + // Scene-filtered lifecycle observers. + // + // The bridge is constructed inside `CAPBridgeViewController.loadView()`, + // which can happen *before* the view is attached to a window — notably when + // the view controller is hosted by a `UIViewControllerRepresentable` rather + // than installed as a window-scene's root by `UIWindowSceneDelegate`. + // + // We deliberately resolve the view's `windowScene` lazily inside each + // notification block rather than capturing it at registration time. This + // gives the right behavior under both hosting paths: + // + // - Before the view has a window: `view.window?.windowScene` is `nil` and + // the identity check against the (non-nil) scene from the notification + // fails, so observers safely no-op. No spurious fires. + // - Once SwiftUI inserts the representable into the hierarchy (or the + // scene delegate installs us as the root), the next foreground/background + // transition resolves the correct scene and the JS event is dispatched. + observers.append(NotificationCenter.default.addObserver( + forName: UIScene.willEnterForegroundNotification, + object: nil, + queue: OperationQueue.main + ) { [weak self] notification in + guard self?.notificationMatchesViewScene(notification) == true else { return } self?.triggerDocumentJSEvent(eventName: "resume") - } - }) - observers.append(NotificationCenter.default.addObserver(forName: UIScene.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in - if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { + observers.append(NotificationCenter.default.addObserver( + forName: UIScene.didEnterBackgroundNotification, + object: nil, + queue: OperationQueue.main + ) { [weak self] notification in + guard self?.notificationMatchesViewScene(notification) == true else { return } self?.triggerDocumentJSEvent(eventName: "pause") - } }) } } + /// Returns `true` when the scene attached to the notification is the same + /// `UIWindowScene` currently hosting this bridge's view. Returns `false` when the + /// notification's scene is not a `UIWindowScene`, when the view is not yet in a + /// window (representable-hosting initialization), or when the scenes differ. + /// + /// Must be called on the main queue — touches `UIView.window`. + private func notificationMatchesViewScene(_ notification: Notification) -> Bool { + guard let scene = notification.object as? UIWindowScene else { return false } + return scene === viewController?.view.window?.windowScene + } + /** Export the core Cordova JS runtime */ diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift index cab0279c4b..866c7e8c5d 100644 --- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift @@ -108,7 +108,7 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat if !isApplicationNavigation, toplevelNavigation { // disallow and let the system handle it if webView.window?.windowScene?.activationState == .foregroundActive { - UIApplication.shared.open(navURL, options: [:], completionHandler: nil) + UIApplication.shared.open(navURL, options: [:], completionHandler: nil) } decisionHandler(.cancel) return From 8f43e0acf0a9d26cc52c792efe8835b76337f96c Mon Sep 17 00:00:00 2001 From: jcesarmobile Date: Mon, 15 Jun 2026 12:11:19 +0200 Subject: [PATCH 09/21] fix(cli): make SPM dependency patch work on prereleases (#8508) --- cli/src/ios/update.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts index 1f4bc87d03..3ab793c250 100644 --- a/cli/src/ios/update.ts +++ b/cli/src/ios/update.ts @@ -1,6 +1,6 @@ import { copy, remove, pathExists, readFile, realpath, writeFile } from 'fs-extra'; import { basename, dirname, join, relative } from 'path'; -import { major } from 'semver'; +import { major, prerelease } from 'semver'; import c from '../colors'; import { checkPlatformVersions, getCapacitorPackageVersion, runTask } from '../common'; @@ -70,11 +70,13 @@ async function updatePluginFiles(config: Config, plugins: Plugin[], deployment: const version = content.match(regex)?.[1]; const majorCapVersion = major(iosPlatformVersion); if (version && major(version) != majorCapVersion) { + const preCapVersion = prerelease(iosPlatformVersion); + const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`; content = setAllStringIn( content, `url: "https://github.com/ionic-team/capacitor-swift-pm.git",`, `)`, - ` from: "${majorCapVersion}.0.0"`, + ` from: "${forceVersion}"`, ); await writeFile(packageSwiftPath, content); logger.warn(`${plugin.id} is built for Capacitor ${major(version)}, it might cause issues`); From 3f0b63d20b572b8073ad82bc0f7e4b1bd9316687 Mon Sep 17 00:00:00 2001 From: "Github Workflow (on behalf of jcesarmobile)" Date: Fri, 19 Jun 2026 09:01:59 +0000 Subject: [PATCH 10/21] Release 8.4.1 --- CHANGELOG.md | 7 +++++++ android/CHANGELOG.md | 4 ++++ android/package.json | 2 +- cli/CHANGELOG.md | 7 +++++++ cli/package.json | 2 +- core/CHANGELOG.md | 4 ++++ core/package.json | 2 +- ios/CHANGELOG.md | 4 ++++ ios/package.json | 2 +- lerna.json | 2 +- 10 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd4fe5f5ad..db39545625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19) + +### Bug Fixes + +- **cli:** make SPM dependency patch work on prereleases ([#8508](https://github.com/ionic-team/capacitor/issues/8508)) ([6048e90](https://github.com/ionic-team/capacitor/commit/6048e90171afa0229a3c25b52a23c377c6bb804c)) +- **cli:** patch Capacitor SPM dependency version in plugins ([#8492](https://github.com/ionic-team/capacitor/issues/8492)) ([28bb2c6](https://github.com/ionic-team/capacitor/commit/28bb2c687069dfdd6aa7abc866004a1c6388d103)) + # [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02) ### Bug Fixes diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index df7ce545e4..5bc9de9f61 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19) + +**Note:** Version bump only for package @capacitor/android + # [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02) ### Bug Fixes diff --git a/android/package.json b/android/package.json index af3f4f556f..7354f3b2c6 100644 --- a/android/package.json +++ b/android/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/android", - "version": "8.4.0", + "version": "8.4.1", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index ee48a8d5f4..df61fcbaaf 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19) + +### Bug Fixes + +- **cli:** make SPM dependency patch work on prereleases ([#8508](https://github.com/ionic-team/capacitor/issues/8508)) ([6048e90](https://github.com/ionic-team/capacitor/commit/6048e90171afa0229a3c25b52a23c377c6bb804c)) +- **cli:** patch Capacitor SPM dependency version in plugins ([#8492](https://github.com/ionic-team/capacitor/issues/8492)) ([28bb2c6](https://github.com/ionic-team/capacitor/commit/28bb2c687069dfdd6aa7abc866004a1c6388d103)) + # [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02) ### Bug Fixes diff --git a/cli/package.json b/cli/package.json index 1f987d33ad..7a77d11e96 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/cli", - "version": "8.4.0", + "version": "8.4.1", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 1de9522ff4..1003c8ceb1 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19) + +**Note:** Version bump only for package @capacitor/core + # [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02) **Note:** Version bump only for package @capacitor/core diff --git a/core/package.json b/core/package.json index b22aef174f..f821cfd9d9 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/core", - "version": "8.4.0", + "version": "8.4.1", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index eac0a4a1ca..dfcf383ff8 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19) + +**Note:** Version bump only for package @capacitor/ios + # [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02) ### Features diff --git a/ios/package.json b/ios/package.json index a58c020903..a6a78c23df 100644 --- a/ios/package.json +++ b/ios/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/ios", - "version": "8.4.0", + "version": "8.4.1", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/lerna.json b/lerna.json index b2efb400d4..be36669427 100644 --- a/lerna.json +++ b/lerna.json @@ -11,6 +11,6 @@ "tagVersionPrefix": "" } }, - "version": "8.4.0", + "version": "8.4.1", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } From db0e074da71c1df08ed3dc9ab51406b4444a9e57 Mon Sep 17 00:00:00 2001 From: Chace Daniels Date: Mon, 29 Jun 2026 11:00:49 -0500 Subject: [PATCH 11/21] fix(ios): mirror deep-link URL to ApplicationDelegateProxy for getLaunchUrl --- ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift | 2 +- ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift index 7f510bc5ac..ef41457d2d 100644 --- a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift @@ -4,7 +4,7 @@ import Foundation public class ApplicationDelegateProxy: NSObject, UIApplicationDelegate { public static let shared = ApplicationDelegateProxy() - public private(set) var lastURL: URL? + public internal(set) var lastURL: URL? public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { NotificationCenter.default.post(name: .capacitorOpenURL, object: [ diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift index 045ee7bad2..1ec16af7fd 100644 --- a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -29,6 +29,7 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { public func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { for context in URLContexts { lastURL = context.url + ApplicationDelegateProxy.shared.lastURL = context.url let options = Self.openURLOptions(from: context.options) // Capacitor 8 backwards compat @@ -37,6 +38,8 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { "options": options ]) + NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: context.url) + NotificationCenter.default.post(name: .capacitorSceneOpenURL, object: scene, userInfo: [ "url": context.url, "options": options @@ -50,6 +53,7 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { return } lastURL = url + ApplicationDelegateProxy.shared.lastURL = url // Capacitor 8 backwards compat NotificationCenter.default.post(name: .capacitorOpenUniversalLink, object: [ From 7d9b89218b35f0c7c9ddb2591648cd751c924616 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 22 Jul 2026 10:19:59 -0500 Subject: [PATCH 12/21] fmt --- .../cordova/MockCordovaWebViewImpl.java | 14 ++++---- ios-pods-template/App/App/AppDelegate.swift | 4 +-- ios-pods-template/App/App/SceneDelegate.swift | 32 +++++++++---------- ios-spm-template/App/App/AppDelegate.swift | 4 +-- ios-spm-template/App/App/SceneDelegate.swift | 22 ++++++------- ios/Capacitor/Capacitor/CapacitorBridge.swift | 14 ++++---- .../Capacitor/WebViewDelegationHandler.swift | 2 +- 7 files changed, 45 insertions(+), 47 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java index 020c1457a1..1115429d7b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java @@ -70,12 +70,14 @@ public CapacitorEvalBridgeMode(WebView webView, CordovaInterface cordova) { @Override public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { - cordova.getActivity().runOnUiThread(() -> { - String js = queue.popAndEncodeAsJs(); - if (js != null) { - webView.evaluateJavascript(js, null); - } - }); + cordova + .getActivity() + .runOnUiThread(() -> { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + webView.evaluateJavascript(js, null); + } + }); } } diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift index 0198f78130..7ad11d83ed 100644 --- a/ios-pods-template/App/App/AppDelegate.swift +++ b/ios-pods-template/App/App/AppDelegate.swift @@ -45,11 +45,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) } - + 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 diff --git a/ios-pods-template/App/App/SceneDelegate.swift b/ios-pods-template/App/App/SceneDelegate.swift index 412fe45746..f352e1e959 100644 --- a/ios-pods-template/App/App/SceneDelegate.swift +++ b/ios-pods-template/App/App/SceneDelegate.swift @@ -2,25 +2,23 @@ import UIKit import Capacitor class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? + 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, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts) - } + window = UIWindow(windowScene: windowScene) + window?.rootViewController = CAPBridgeViewController() + window?.makeKeyAndVisible() - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - SceneDelegateProxy.shared.scene(scene, continue: userActivity) - } -} + 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) + } +} diff --git a/ios-spm-template/App/App/AppDelegate.swift b/ios-spm-template/App/App/AppDelegate.swift index 65256aaac9..fec95acae1 100644 --- a/ios-spm-template/App/App/AppDelegate.swift +++ b/ios-spm-template/App/App/AppDelegate.swift @@ -32,12 +32,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let config = UISceneConfiguration(name: "Default Configuration", - sessionRole: connectingSceneSession.role) + sessionRole: connectingSceneSession.role) config.delegateClass = SceneDelegate.self return config } diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift index c60c9a8ac3..a21dd960c8 100644 --- a/ios-spm-template/App/App/SceneDelegate.swift +++ b/ios-spm-template/App/App/SceneDelegate.swift @@ -2,19 +2,17 @@ import UIKit import Capacitor class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? + var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions) - } + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + 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, openURLContexts URLContexts: Set) { + SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts) + } - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - SceneDelegateProxy.shared.scene(scene, continue: userActivity) - } + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + SceneDelegateProxy.shared.scene(scene, continue: userActivity) + } } - - diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 58af7819ca..2634363950 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -264,15 +264,15 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { registerCordovaPlugins() } else { observers.append(NotificationCenter.default.addObserver(forName: UIScene.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in - if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { - self?.triggerDocumentJSEvent(eventName: "resume") - } - + if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { + self?.triggerDocumentJSEvent(eventName: "resume") + } + }) observers.append(NotificationCenter.default.addObserver(forName: UIScene.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in - if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { - self?.triggerDocumentJSEvent(eventName: "pause") - } + if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene { + self?.triggerDocumentJSEvent(eventName: "pause") + } }) } } diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift index cab0279c4b..866c7e8c5d 100644 --- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift @@ -108,7 +108,7 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat if !isApplicationNavigation, toplevelNavigation { // disallow and let the system handle it if webView.window?.windowScene?.activationState == .foregroundActive { - UIApplication.shared.open(navURL, options: [:], completionHandler: nil) + UIApplication.shared.open(navURL, options: [:], completionHandler: nil) } decisionHandler(.cancel) return From c524ec6f442ea2c7cd8a4154669d6d37df391c2b Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 22 Jul 2026 11:44:28 -0500 Subject: [PATCH 13/21] add SceneDelegate to TestsHostApp --- ios/Capacitor/Capacitor.xcodeproj/project.pbxproj | 4 ++++ ios/Capacitor/TestsHostApp/AppDelegate.swift | 9 +++++++++ ios/Capacitor/TestsHostApp/SceneDelegate.swift | 5 +++++ 3 files changed, 18 insertions(+) create mode 100644 ios/Capacitor/TestsHostApp/SceneDelegate.swift diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index 6cd33b6005..573e90cd9a 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 62959BA52526475A00A3D7F1 /* Cordova.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62959BA02526474300A3D7F1 /* Cordova.framework */; }; 6296A77E253A2E49005A202A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6296A77D253A2E49005A202A /* AppDelegate.swift */; }; 6296A782253A2E49005A202A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6296A781253A2E49005A202A /* ViewController.swift */; }; + 6296A7A0253A2E49005A202A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6296A7A1253A2E49005A202A /* SceneDelegate.swift */; }; 6296A785253A2E49005A202A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6296A783253A2E49005A202A /* Main.storyboard */; }; 6296A787253A2E49005A202A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6296A786253A2E49005A202A /* Assets.xcassets */; }; 6296A78A253A2E49005A202A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6296A788253A2E49005A202A /* LaunchScreen.storyboard */; }; @@ -218,6 +219,7 @@ 6296A77B253A2E49005A202A /* TestsHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestsHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6296A77D253A2E49005A202A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 6296A781253A2E49005A202A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 6296A7A1253A2E49005A202A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 6296A784253A2E49005A202A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 6296A786253A2E49005A202A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6296A789253A2E49005A202A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -446,6 +448,7 @@ isa = PBXGroup; children = ( 6296A77D253A2E49005A202A /* AppDelegate.swift */, + 6296A7A1253A2E49005A202A /* SceneDelegate.swift */, 6296A781253A2E49005A202A /* ViewController.swift */, 62E0735125535E6500BAAADB /* configurations */, 6296A783253A2E49005A202A /* Main.storyboard */, @@ -784,6 +787,7 @@ files = ( 6296A782253A2E49005A202A /* ViewController.swift in Sources */, 6296A77E253A2E49005A202A /* AppDelegate.swift in Sources */, + 6296A7A0253A2E49005A202A /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Capacitor/TestsHostApp/AppDelegate.swift b/ios/Capacitor/TestsHostApp/AppDelegate.swift index f8b87fe2d1..9107e3c451 100644 --- a/ios/Capacitor/TestsHostApp/AppDelegate.swift +++ b/ios/Capacitor/TestsHostApp/AppDelegate.swift @@ -6,4 +6,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. return true } + + 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 + } } diff --git a/ios/Capacitor/TestsHostApp/SceneDelegate.swift b/ios/Capacitor/TestsHostApp/SceneDelegate.swift new file mode 100644 index 0000000000..e0be6e9cfe --- /dev/null +++ b/ios/Capacitor/TestsHostApp/SceneDelegate.swift @@ -0,0 +1,5 @@ +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? +} From f2e6a3dd64172238336e933ec4b23428a2ce007d Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 22 Jul 2026 11:44:55 -0500 Subject: [PATCH 14/21] fmt --- .../cordova/MockCordovaWebViewImpl.java | 14 ++++++------- .../com/getcapacitor/plugin/SystemBars.java | 20 +++++++++---------- .../plugin/util/HttpRequestHandlerTest.java | 4 ++-- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java index 1115429d7b..020c1457a1 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java @@ -70,14 +70,12 @@ public CapacitorEvalBridgeMode(WebView webView, CordovaInterface cordova) { @Override public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { - cordova - .getActivity() - .runOnUiThread(() -> { - String js = queue.popAndEncodeAsJs(); - if (js != null) { - webView.evaluateJavascript(js, null); - } - }); + cordova.getActivity().runOnUiThread(() -> { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + webView.evaluateJavascript(js, null); + } + }); } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java index fd71370945..b3c95916a7 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -43,17 +43,17 @@ public class SystemBars extends Plugin { private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144; static final String viewportMetaJSFunction = """ - function capacitorSystemBarsCheckMetaViewport() { - const meta = document.querySelectorAll("meta[name=viewport]"); - if (meta.length == 0) { - return false; - } - // get the last found meta viewport tag - const metaContent = meta[meta.length - 1].content; - return metaContent.includes("viewport-fit=cover"); + function capacitorSystemBarsCheckMetaViewport() { + const meta = document.querySelectorAll("meta[name=viewport]"); + if (meta.length == 0) { + return false; } - capacitorSystemBarsCheckMetaViewport(); - """; + // get the last found meta viewport tag + const metaContent = meta[meta.length - 1].content; + return metaContent.includes("viewport-fit=cover"); + } + capacitorSystemBarsCheckMetaViewport(); + """; private String insetsHandling = INSETS_HANDLING_CSS; private boolean hasViewportCover = false; diff --git a/android/capacitor/src/test/java/com/getcapacitor/plugin/util/HttpRequestHandlerTest.java b/android/capacitor/src/test/java/com/getcapacitor/plugin/util/HttpRequestHandlerTest.java index fd8b686ee6..1006721235 100644 --- a/android/capacitor/src/test/java/com/getcapacitor/plugin/util/HttpRequestHandlerTest.java +++ b/android/capacitor/src/test/java/com/getcapacitor/plugin/util/HttpRequestHandlerTest.java @@ -11,8 +11,8 @@ public class HttpRequestHandlerTest { static final String BASE_URL = "https://httpbin.org/get"; static final String PARAMS_JSON = """ - {"k": "a&b"} - """; + {"k": "a&b"} + """; @Test public void testHttpURLConnectionBuilderSetUrlParamsEncoded() throws Exception { From 23ec4a0824cedbd61e13cbc28283e041e2fdfa4d Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 27 Jul 2026 12:47:18 -0500 Subject: [PATCH 15/21] Initialize window with CAPBridgeViewController in SceneDelegate --- ios-spm-template/App/App/SceneDelegate.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift index a21dd960c8..0194fad6df 100644 --- a/ios-spm-template/App/App/SceneDelegate.swift +++ b/ios-spm-template/App/App/SceneDelegate.swift @@ -5,7 +5,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: 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) { From ee5b0f0e5124a4fd60da8ee1119552d55eb4e09c Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 27 Jul 2026 12:54:37 -0500 Subject: [PATCH 16/21] Defer scene connect URL/activity delivery until plugins load --- .../Capacitor/CAPSceneDelegateProxy.swift | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift index 1ec16af7fd..a172a1c1f7 100644 --- a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -17,12 +17,20 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { NotificationCenter.default.post(name: .capacitorSceneWillConnect, object: scene) - if !connectionOptions.urlContexts.isEmpty { - self.scene(scene, openURLContexts: connectionOptions.urlContexts) - } - - for userActivity in connectionOptions.userActivities { - self.scene(scene, continue: userActivity) + // Plugins haven't loaded yet on a cold start, so notifications posted here are + // missed. Deliver them on the first capacitorViewDidAppear, once plugins are + // registered. + var token: NSObjectProtocol? + token = NotificationCenter.default.addObserver(forName: .capacitorViewDidAppear, object: nil, queue: .main) { _ in + if let token { + NotificationCenter.default.removeObserver(token) + } + if !connectionOptions.urlContexts.isEmpty { + self.scene(scene, openURLContexts: connectionOptions.urlContexts) + } + for userActivity in connectionOptions.userActivities { + self.scene(scene, continue: userActivity) + } } } From 5832336d8a9d57d250a437718ec92102c361d717 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 27 Jul 2026 12:54:42 -0500 Subject: [PATCH 17/21] fmt --- ios-spm-template/App/App/SceneDelegate.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift index 0194fad6df..f352e1e959 100644 --- a/ios-spm-template/App/App/SceneDelegate.swift +++ b/ios-spm-template/App/App/SceneDelegate.swift @@ -5,13 +5,13 @@ 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 } + guard let windowScene = scene as? UIWindowScene else { return } - window = UIWindow(windowScene: windowScene) - window?.rootViewController = CAPBridgeViewController() - window?.makeKeyAndVisible() + window = UIWindow(windowScene: windowScene) + window?.rootViewController = CAPBridgeViewController() + window?.makeKeyAndVisible() - SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions) + SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions) } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { From c590693f243ba3a824d99e3cd442a22dd1f1bc77 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 27 Jul 2026 12:56:44 -0500 Subject: [PATCH 18/21] Remove Phase 2 multi-window TODO stubs from SceneDelegateProxy --- .../Capacitor/CAPSceneDelegateProxy.swift | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift index a172a1c1f7..782efe4cff 100644 --- a/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPSceneDelegateProxy.swift @@ -73,28 +73,6 @@ public class SceneDelegateProxy: NSObject, UISceneDelegate { ]) } - // TODO: Not until Phase 2 of the UI modernization project - - public func sceneDidDisconnect(_ scene: UIScene) { - // TODO: multi-window — tear down the bridge associated with this scene. - } - - public func sceneDidBecomeActive(_ scene: UIScene) { - // TODO: multi-window — forward per-scene active event to the bridge. - } - - public func sceneWillResignActive(_ scene: UIScene) { - // TODO: multi-window — forward per-scene resign-active event to the bridge. - } - - public func sceneWillEnterForeground(_ scene: UIScene) { - // TODO: multi-window — drive JS `resume` from this instead of UIApplication.willEnterForegroundNotification. - } - - public func sceneDidEnterBackground(_ scene: UIScene) { - // TODO: multi-window — drive JS `pause` from this instead of UIApplication.didEnterBackgroundNotification. - } - private static func openURLOptions(from sceneOptions: UIScene.OpenURLOptions) -> [UIApplication.OpenURLOptionsKey: Any] { var options: [UIApplication.OpenURLOptionsKey: Any] = [:] if let sourceApplication = sceneOptions.sourceApplication { From 3ac152f9ed194f778a1ffbf39f0258776f504e26 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 22 Jun 2026 13:05:21 -0500 Subject: [PATCH 19/21] Adding SwiftUI components to pods template --- .../App/App.xcodeproj/project.pbxproj | 28 +++------ ios-pods-template/App/App/App.swift | 18 ++++++ ios-pods-template/App/App/AppDelegate.swift | 58 ------------------- .../App/App/Base.lproj/Main.storyboard | 19 ------ ios-pods-template/App/App/CapacitorView.swift | 10 ++++ ios-pods-template/App/App/Info.plist | 18 +----- ios-pods-template/App/App/SceneDelegate.swift | 24 -------- 7 files changed, 37 insertions(+), 138 deletions(-) create mode 100644 ios-pods-template/App/App/App.swift delete mode 100644 ios-pods-template/App/App/AppDelegate.swift delete mode 100644 ios-pods-template/App/App/Base.lproj/Main.storyboard create mode 100644 ios-pods-template/App/App/CapacitorView.swift delete mode 100644 ios-pods-template/App/App/SceneDelegate.swift diff --git a/ios-pods-template/App/App.xcodeproj/project.pbxproj b/ios-pods-template/App/App.xcodeproj/project.pbxproj index 026cbd4583..2ebc501505 100644 --- a/ios-pods-template/App/App.xcodeproj/project.pbxproj +++ b/ios-pods-template/App/App.xcodeproj/project.pbxproj @@ -9,12 +9,11 @@ /* Begin PBXBuildFile section */ 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; - 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; - 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; - 9582B6852FE996820072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6842FE996800072D4E8 /* SceneDelegate.swift */; }; + 9582B68A2FE9ABF30072D4E8 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6892FE9ABF10072D4E8 /* App.swift */; }; + 9582B68C2FE9ACC30072D4E8 /* CapacitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */; }; A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; /* End PBXBuildFile section */ @@ -22,13 +21,12 @@ 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; - 9582B6842FE996800072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 9582B6892FE9ABF10072D4E8 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; + 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorView.swift; sourceTree = ""; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; @@ -75,10 +73,9 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( - 9582B6842FE996800072D4E8 /* SceneDelegate.swift */, + 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */, + 9582B6892FE9ABF10072D4E8 /* App.swift */, 50379B222058CBB4000EE86E /* capacitor.config.json */, - 504EC3071FED79650016851F /* AppDelegate.swift */, - 504EC30B1FED79650016851F /* Main.storyboard */, 504EC30E1FED79650016851F /* Assets.xcassets */, 504EC3101FED79650016851F /* LaunchScreen.storyboard */, 504EC3131FED79650016851F /* Info.plist */, @@ -164,7 +161,6 @@ 50B271D11FEDC1A000F3C39B /* public in Resources */, 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, - 504EC30D1FED79650016851F /* Main.storyboard in Resources */, 2FAD9763203C412B000D30F8 /* config.xml in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -212,22 +208,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, - 9582B6852FE996820072D4E8 /* SceneDelegate.swift in Sources */, + 9582B68A2FE9ABF30072D4E8 /* App.swift in Sources */, + 9582B68C2FE9ACC30072D4E8 /* CapacitorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - 504EC30B1FED79650016851F /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 504EC30C1FED79650016851F /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; 504EC3101FED79650016851F /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/ios-pods-template/App/App/App.swift b/ios-pods-template/App/App/App.swift new file mode 100644 index 0000000000..5255bdb611 --- /dev/null +++ b/ios-pods-template/App/App/App.swift @@ -0,0 +1,18 @@ +import SwiftUI +import Capacitor + +@main +struct CapacitorApp: App { + var body: some Scene { + WindowGroup { + CapacitorView() + .ignoresSafeArea() + .onOpenURL { url in + SceneDelegateProxy.shared.handle(openURL: url) + } + .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in + SceneDelegateProxy.shared.handle(userActivity: activity) + } + } + } +} diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift deleted file mode 100644 index 1dfbed0901..0000000000 --- a/ios-pods-template/App/App/AppDelegate.swift +++ /dev/null @@ -1,58 +0,0 @@ -import UIKit -import Capacitor - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - // Called when the app was launched with a url. Feel free to add additional processing here, - // but if you want the App API to support tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(app, open: url, options: options) - } - - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - // Called when the app was launched with an activity, including Universal Links. - // Feel free to add additional processing here, but if you want the App API to support - // tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) - } - - 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 - } - -} diff --git a/ios-pods-template/App/App/Base.lproj/Main.storyboard b/ios-pods-template/App/App/Base.lproj/Main.storyboard deleted file mode 100644 index b44df7be8f..0000000000 --- a/ios-pods-template/App/App/Base.lproj/Main.storyboard +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/ios-pods-template/App/App/CapacitorView.swift b/ios-pods-template/App/App/CapacitorView.swift new file mode 100644 index 0000000000..12575c875b --- /dev/null +++ b/ios-pods-template/App/App/CapacitorView.swift @@ -0,0 +1,10 @@ +import SwiftUI +import Capacitor + +public struct CapacitorView: UIViewControllerRepresentable { + public func makeUIViewController(context: Context) -> CAPBridgeViewController { + CAPBridgeViewController() + } + + public func updateUIViewController(_ vc: CAPBridgeViewController, context: Context) {} +} diff --git a/ios-pods-template/App/App/Info.plist b/ios-pods-template/App/App/Info.plist index f8c8a57659..23320dae5e 100644 --- a/ios-pods-template/App/App/Info.plist +++ b/ios-pods-template/App/App/Info.plist @@ -25,26 +25,10 @@ UIApplicationSceneManifest UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - + UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/ios-pods-template/App/App/SceneDelegate.swift b/ios-pods-template/App/App/SceneDelegate.swift deleted file mode 100644 index f352e1e959..0000000000 --- a/ios-pods-template/App/App/SceneDelegate.swift +++ /dev/null @@ -1,24 +0,0 @@ -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) - } -} From 4de4aa51e7b3c2d70c3b99add9b2b6f9e9ed601b Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 7 Aug 2026 11:48:21 -0500 Subject: [PATCH 20/21] Add AppDelegate lifecycle integration to iOS pods template for legacy usage --- ios-pods-template/App/App.xcodeproj/project.pbxproj | 4 ++++ ios-pods-template/App/App/App.swift | 2 ++ ios-pods-template/App/App/AppDelegate.swift | 9 +++++++++ 3 files changed, 15 insertions(+) create mode 100644 ios-pods-template/App/App/AppDelegate.swift diff --git a/ios-pods-template/App/App.xcodeproj/project.pbxproj b/ios-pods-template/App/App.xcodeproj/project.pbxproj index 2ebc501505..88d1ad2041 100644 --- a/ios-pods-template/App/App.xcodeproj/project.pbxproj +++ b/ios-pods-template/App/App.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + 952C1C64302642D6000D0FF6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952C1C63302642D3000D0FF6 /* AppDelegate.swift */; }; 9582B68A2FE9ABF30072D4E8 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6892FE9ABF10072D4E8 /* App.swift */; }; 9582B68C2FE9ACC30072D4E8 /* CapacitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */; }; A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; @@ -25,6 +26,7 @@ 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + 952C1C63302642D3000D0FF6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9582B6892FE9ABF10072D4E8 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorView.swift; sourceTree = ""; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -73,6 +75,7 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( + 952C1C63302642D3000D0FF6 /* AppDelegate.swift */, 9582B68B2FE9ACBE0072D4E8 /* CapacitorView.swift */, 9582B6892FE9ABF10072D4E8 /* App.swift */, 50379B222058CBB4000EE86E /* capacitor.config.json */, @@ -208,6 +211,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 952C1C64302642D6000D0FF6 /* AppDelegate.swift in Sources */, 9582B68A2FE9ABF30072D4E8 /* App.swift in Sources */, 9582B68C2FE9ACC30072D4E8 /* CapacitorView.swift in Sources */, ); diff --git a/ios-pods-template/App/App/App.swift b/ios-pods-template/App/App/App.swift index 5255bdb611..23b0a8cc18 100644 --- a/ios-pods-template/App/App/App.swift +++ b/ios-pods-template/App/App/App.swift @@ -3,6 +3,8 @@ import Capacitor @main struct CapacitorApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + var body: some Scene { WindowGroup { CapacitorView() diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift new file mode 100644 index 0000000000..99ba4ab96e --- /dev/null +++ b/ios-pods-template/App/App/AppDelegate.swift @@ -0,0 +1,9 @@ +import Foundation +import UIKit + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } +} + From c963f589f0a5960bdac9098293d7b84930dbd5ee Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 7 Aug 2026 16:45:17 -0500 Subject: [PATCH 21/21] Adding SwiftUI to SPM template --- .../App/App.xcodeproj/project.pbxproj | 12 ++++-- ios-spm-template/App/App/App.swift | 21 ++++++++++ ios-spm-template/App/App/AppDelegate.swift | 39 +------------------ ios-spm-template/App/App/CapacitorView.swift | 11 ++++++ ios-spm-template/App/App/SceneDelegate.swift | 24 ------------ 5 files changed, 41 insertions(+), 66 deletions(-) create mode 100644 ios-spm-template/App/App/App.swift create mode 100644 ios-spm-template/App/App/CapacitorView.swift delete mode 100644 ios-spm-template/App/App/SceneDelegate.swift diff --git a/ios-spm-template/App/App.xcodeproj/project.pbxproj b/ios-spm-template/App/App.xcodeproj/project.pbxproj index 87225f719f..269623b05a 100644 --- a/ios-spm-template/App/App.xcodeproj/project.pbxproj +++ b/ios-spm-template/App/App.xcodeproj/project.pbxproj @@ -15,7 +15,8 @@ 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; - 9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */; }; + 9575E845302683A8004C1ED7 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9575E844302683A6004C1ED7 /* App.swift */; }; + 9575E847302683C1004C1ED7 /* CapacitorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9575E846302683BC004C1ED7 /* CapacitorView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,7 +29,8 @@ 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; - 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 9575E844302683A6004C1ED7 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; + 9575E846302683BC004C1ED7 /* CapacitorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorView.swift; sourceTree = ""; }; 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -64,7 +66,8 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( - 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */, + 9575E846302683BC004C1ED7 /* CapacitorView.swift */, + 9575E844302683A6004C1ED7 /* App.swift */, 50379B222058CBB4000EE86E /* capacitor.config.json */, 504EC3071FED79650016851F /* AppDelegate.swift */, 504EC30B1FED79650016851F /* Main.storyboard */, @@ -158,8 +161,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9575E845302683A8004C1ED7 /* App.swift in Sources */, 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, - 9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */, + 9575E847302683C1004C1ED7 /* CapacitorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios-spm-template/App/App/App.swift b/ios-spm-template/App/App/App.swift new file mode 100644 index 0000000000..fec128fcef --- /dev/null +++ b/ios-spm-template/App/App/App.swift @@ -0,0 +1,21 @@ +import SwiftUI +import Capacitor + +@main +struct CapacitorApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + CapacitorView() + .ignoresSafeArea() + .onOpenURL { url in + SceneDelegateProxy.shared.handle(openURL: url) + } + .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in + SceneDelegateProxy.shared.handle(userActivity: activity) + } + } + } +} + diff --git a/ios-spm-template/App/App/AppDelegate.swift b/ios-spm-template/App/App/AppDelegate.swift index 7fe69b516a..5fb4c09fbe 100644 --- a/ios-spm-template/App/App/AppDelegate.swift +++ b/ios-spm-template/App/App/AppDelegate.swift @@ -1,44 +1,7 @@ import UIKit -import Capacitor - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? +class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. return true } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - 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 - } } diff --git a/ios-spm-template/App/App/CapacitorView.swift b/ios-spm-template/App/App/CapacitorView.swift new file mode 100644 index 0000000000..bae562243f --- /dev/null +++ b/ios-spm-template/App/App/CapacitorView.swift @@ -0,0 +1,11 @@ +import SwiftUI +import Capacitor + +public struct CapacitorView: UIViewControllerRepresentable { + public func makeUIViewController(context: Context) -> CAPBridgeViewController { + CAPBridgeViewController() + } + + public func updateUIViewController(_ vc: CAPBridgeViewController, context: Context) {} +} + diff --git a/ios-spm-template/App/App/SceneDelegate.swift b/ios-spm-template/App/App/SceneDelegate.swift deleted file mode 100644 index 0a82aa3ce2..0000000000 --- a/ios-spm-template/App/App/SceneDelegate.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Capacitor -import UIKit - -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) - } -}