all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Shan Shaji <s.shaji@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve_flutter_frontend 3/4] fix: migrate to UIScene lifecycle for iOS 26+ compatibility
Date: Fri, 10 Apr 2026 17:09:31 +0200	[thread overview]
Message-ID: <20260410150935.25870-4-s.shaji@proxmox.com> (raw)
In-Reply-To: <20260410150935.25870-1-s.shaji@proxmox.com>

Apple has announced that starting with the SDK following iOS 26 [0], all
UIKit-based apps must adopt the UIScene lifecycle. Apps that continue to
rely solely on the legacy AppDelegate lifecycle for UI initialization
will fail to launch.

The plugin registration was happening inside the
`application:didFinishLaunchingWithOptions` method. To accommodate the
new app launch sequence moved the initialization to
`didInitializeImplicitFlutterEngine` [1]

This change also shifts window management away from the AppDelegate.
Attempting to access the window object there may return nil, as the
UI lifecycle is now handled by the new SceneDelegate.

To fix the issue [2][3]:

* Defined a new SceneDelegate class (subclassing FlutterSceneDelegate).
* Registered the SceneDelegate within the Info.plist under the
  `UIApplicationSceneManifest`.
* Migrated all file sharing logic and method channel setups from the
  `AppDelegate` to the `SceneDelegate` within the
  scene(_:willConnectTo:options:) method.

- [0] https://docs.flutter.dev/release/breaking-changes/uiscenedelegate#background
- [1] https://docs.flutter.dev/release/breaking-changes/uiscenedelegate#migrate-appdelegate
- [2] https://docs.flutter.dev/release/breaking-changes/uiscenedelegate#bespoke-flutterviewcontroller-usage
- [3] https://developer.apple.com/documentation/uikit/specifying-the-scenes-your-app-supports#Configure-the-details-for-each-scene

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 ios/Runner.xcodeproj/project.pbxproj |  4 ++
 ios/Runner/AppDelegate.swift         | 53 ++-----------------------
 ios/Runner/Info.plist                | 21 ++++++++++
 ios/Runner/SceneDelegate.swift       | 59 ++++++++++++++++++++++++++++
 4 files changed, 88 insertions(+), 49 deletions(-)
 create mode 100644 ios/Runner/SceneDelegate.swift

diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index a9d4fd5..8bf8472 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
 
 /* Begin PBXBuildFile section */
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		1B87D59D2F89184000623BAC /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B87D59C2F89183C00623BAC /* SceneDelegate.swift */; };
 		1BE02BA02EB5028D00B28B3B /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 1BE02B9F2EB5028D00B28B3B /* AppIcon.icon */; };
 		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
@@ -47,6 +48,7 @@
 		022505048A677FEA7AF056D1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		1B87D59C2F89183C00623BAC /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
 		1BE02B9F2EB5028D00B28B3B /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
 		3158807C4D56CFC909080136 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
@@ -145,6 +147,7 @@
 		97C146F01CF9000F007C117D /* Runner */ = {
 			isa = PBXGroup;
 			children = (
+				1B87D59C2F89183C00623BAC /* SceneDelegate.swift */,
 				1BE02B9F2EB5028D00B28B3B /* AppIcon.icon */,
 				97C146FA1CF9000F007C117D /* Main.storyboard */,
 				97C146FD1CF9000F007C117D /* Assets.xcassets */,
@@ -382,6 +385,7 @@
 			files = (
 				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
 				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+				1B87D59D2F89184000623BAC /* SceneDelegate.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 428976f..bb1afce 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -2,60 +2,15 @@ import Flutter
 import UIKit
 
 @main
-@objc class AppDelegate: FlutterAppDelegate {
+@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
     override func application(
         _ application: UIApplication,
         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
     ) -> Bool {
-        let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
-        let channel: FlutterMethodChannel = FlutterMethodChannel(
-            name: "com.proxmox.app.pve_flutter_frontend/filesharing",
-            binaryMessenger: controller.binaryMessenger)
-
-        channel.setMethodCallHandler({
-            [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
-
-            guard call.method == "shareFile" else {
-                result(FlutterMethodNotImplemented)
-                return
-            }
-
-            let arguments = call.arguments as? [String: Any]
-            let path = arguments?["path"] as? String
-            let type = arguments?["type"] as? String
-
-            if let filePath = path, let _ = type  {
-                self?.shareFile(atPath: filePath, from: controller, result: result)
-            } else {
-                result(FlutterError(code: "FileNotFoundException", message: "File not found", details: nil))
-            }
-        })
-
-        GeneratedPluginRegistrant.register(with: self)
-
         return super.application(application, didFinishLaunchingWithOptions: launchOptions)
     }
-
-    private func shareFile(atPath path: String, from controller: UIViewController, result: @escaping FlutterResult) {
-        let fileURL = URL(fileURLWithPath: path)
-        let activityVC = UIActivityViewController(
-            activityItems: [fileURL],
-            applicationActivities: nil,
-        )
-
-        // To avoid crashing in iPad
-        if let popover = activityVC.popoverPresentationController {
-            popover.sourceView = controller.view
-            popover.sourceRect = CGRect(
-                x: controller.view.bounds.midX,
-                y: controller.view.bounds.midY,
-                width: 0,
-                height: 0,
-            )
-        }
-
-        controller.present(activityVC, animated: true) {
-            result(nil)
-        }
+    
+    func didInitializeImplicitFlutterEngine(_ engineBridge: any FlutterImplicitEngineBridge) {
+        GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
     }
 }
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index a5587ed..9ba026f 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -2,6 +2,27 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+	<key>UIApplicationSceneManifest</key>
+	<dict>
+		<key>UIApplicationSupportsMultipleScenes</key>
+		<false/>
+		<key>UISceneConfigurations</key>
+		<dict>
+			<key>UIWindowSceneSessionRoleApplication</key>
+			<array>
+				<dict>
+					<key>UISceneClassName</key>
+					<string>UIWindowScene</string>
+					<key>UISceneDelegateClassName</key>
+					<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
+					<key>UISceneConfigurationName</key>
+					<string>flutter</string>
+					<key>UISceneStoryboardFile</key>
+					<string>Main</string>
+				</dict>
+			</array>
+		</dict>
+	</dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
 	<key>CFBundleDisplayName</key>
diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift
new file mode 100644
index 0000000..2ceca09
--- /dev/null
+++ b/ios/Runner/SceneDelegate.swift
@@ -0,0 +1,59 @@
+import Flutter
+import UIKit
+
+class SceneDelegate: FlutterSceneDelegate {
+    override func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+        super.scene(scene, willConnectTo: session, options: connectionOptions)
+        
+        guard let window = self.window,
+              let controller = window.rootViewController as? FlutterViewController else {
+            return
+        }
+        
+        let channel: FlutterMethodChannel = FlutterMethodChannel(
+            name: "com.proxmox.app.pve_flutter_frontend/filesharing",
+            binaryMessenger: controller.binaryMessenger)
+
+        channel.setMethodCallHandler({
+            [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
+
+            guard call.method == "shareFile" else {
+                result(FlutterMethodNotImplemented)
+                return
+            }
+
+            let arguments = call.arguments as? [String: Any]
+            let path = arguments?["path"] as? String
+            let type = arguments?["type"] as? String
+
+            if let filePath = path, let _ = type  {
+                self?.shareFile(atPath: filePath, from: controller, result: result)
+            } else {
+                result(FlutterError(code: "FileNotFoundException", message: "File not found", details: nil))
+            }
+        })
+    }
+    
+    private func shareFile(atPath path: String, from controller: UIViewController, result: @escaping FlutterResult) {
+        let fileURL = URL(fileURLWithPath: path)
+        let activityVC = UIActivityViewController(
+            activityItems: [fileURL],
+            applicationActivities: nil,
+        )
+
+        // To avoid crashing in iPad
+        if let popover = activityVC.popoverPresentationController {
+            popover.sourceView = controller.view
+            popover.sourceRect = CGRect(
+                x: controller.view.bounds.midX,
+                y: controller.view.bounds.midY,
+                width: 0,
+                height: 0,
+            )
+        }
+
+        controller.present(activityVC, animated: true) {
+            result(nil)
+        }
+    }
+}
-- 
2.50.1





  parent reply	other threads:[~2026-04-10 15:09 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-10 15:09 [PATCH proxmox{_login_manager,_dart_api_client}/pve_flutter_frontend 0/7] upgrade dependencies based on flutter v3.41 and migrate deprecated members Shan Shaji
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 1/4] chore: upgrade dependencies based on flutter v3.41 Shan Shaji
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 2/4] fix: breaking changes due to the upgrade of font_awesome_flutter to v11 Shan Shaji
2026-04-10 15:09 ` Shan Shaji [this message]
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 4/4] chore: use latest ndkVersion from flutter Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_login_manager 1/1] chore: upgrade dependencies Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_dart_api_client 1/2] " Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_dart_api_client 2/2] deps: add objective_c dependency to access NSError's code property Shan Shaji

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260410150935.25870-4-s.shaji@proxmox.com \
    --to=s.shaji@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal