mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-11-03 16:32:22 +00:00
fix(ios): adopt the UIScene life cycle (#16776)
* fix(ios): adopt the UIScene life cycle UIKit refuses to launch an app linked against the iOS 27 SDK that has not adopted the UIScene life cycle: it traps in _UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption with "UIScene life cycle is required for apps built with this SDK". 1.5.1 (69) is the first build made with Xcode 27.1, so it crashes at launch on every iOS 27 device. Flutter 3.24.5 has no FlutterSceneDelegate, so add a minimal one: - Info.plist declares a single-scene manifest that loads Main.storyboard. - SceneDelegate hands the storyboard window to AppDelegate when the scene connects. FlutterAppDelegate finds the engine for plugin registration through its window, which no longer exists in didFinishLaunching, so registration moves there. - The launch URL now arrives in the scene connection options and later URLs in scene(_:openURLContexts:). Forward both to plugins through the app delegate callbacks uni_links listens on, so rustdesk:// links keep working on cold and warm start. Verified on the iOS 27.1 simulator with a Flutter 3.24.5 app that has the same Runner setup: the app launches, plugin channels respond, the initial link and stream links arrive, and lifecycle states reach Dart. Fixes #16766 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ios): drive the status bar from the view controller under UIScene With UIViewControllerBasedStatusBarAppearance=false, Flutter 3.24.5 hides and styles the status bar through the app-level UIApplication setters. UIKit ignores those once the app uses the UIScene life cycle, so SystemChrome.setEnabledSystemUIMode(manual, overlays: []) no longer hides the status bar during a remote session. Set the key to true so Flutter goes through FlutterViewController's prefersStatusBarHidden / preferredStatusBarStyle instead. Checked on the iOS 27.1 simulator in scene mode: hiding, showing again and the light overlay style all take effect. The Dart side only uses SystemChrome, so it does not depend on this key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
33a154a5d7
commit
0d6265bb26
4 files changed
+60
-2
No files matched your search
@@ -10,6 +10,7 @@
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
5CE7AD012EB4000000A1B2C3 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE7AD002EB4000000A1B2C3 /* SceneDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
@@ -40,6 +41,7 @@
|
||||
4151ACC476A12FDC49BC7860 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5CE7AD002EB4000000A1B2C3 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -104,6 +106,7 @@
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
5CE7AD002EB4000000A1B2C3 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
@@ -279,6 +282,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
5CE7AD012EB4000000A1B2C3 /* SceneDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -8,7 +8,6 @@ import AVFoundation
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
do {
|
||||
// Mix remote audio with other apps while still respecting silent mode.
|
||||
try AVAudioSession.sharedInstance().setCategory(.ambient)
|
||||
@@ -18,6 +17,19 @@ import AVFoundation
|
||||
dummyMethodToEnforceBundling();
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
// With the UIScene life cycle, the FlutterViewController only exists once the scene
|
||||
// connects, and FlutterAppDelegate reaches its engine through `window`. A launch URL
|
||||
// also arrives with the scene instead of in launchOptions, so forward it to plugins here.
|
||||
func sceneWillConnect(window: UIWindow?, launchURL: URL?) {
|
||||
self.window = window
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
var launchOptions: [UIApplication.LaunchOptionsKey: Any] = [:]
|
||||
if let launchURL = launchURL {
|
||||
launchOptions[.url] = launchURL
|
||||
}
|
||||
_ = super.application(UIApplication.shared, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
public func dummyMethodToEnforceBundling() {
|
||||
dummy_method_to_enforce_bundling();
|
||||
|
||||
@@ -47,6 +47,27 @@
|
||||
<string>This app needs camera access to scan QR codes</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app needs photo library access to get QR codes from image</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
@@ -71,7 +92,7 @@
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>io.flutter.embedded_views_preview</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(
|
||||
_ scene: UIScene,
|
||||
willConnectTo session: UISceneSession,
|
||||
options connectionOptions: UIScene.ConnectionOptions
|
||||
) {
|
||||
(UIApplication.shared.delegate as? AppDelegate)?.sceneWillConnect(
|
||||
window: window, launchURL: connectionOptions.urlContexts.first?.url)
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||
for context in URLContexts {
|
||||
_ = appDelegate.application(UIApplication.shared, open: context.url, options: [:])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user