From ac37bd6353dfffb5c48c4011f433a8a421c046d0 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 20 Jan 2026 09:39:27 +0100 Subject: [PATCH] Implement scene delegate for iOS (#20454) * Implement scene delegate for iOS * Move OS check to its own method to make analyzer happy --- src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs | 44 ++++++++++++------- src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs | 36 +++++++++++++++ src/iOS/Avalonia.iOS/SingleViewLifetime.cs | 15 +++---- 3 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs index 2618dad857..70e35e3973 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Versioning; using Foundation; using Avalonia.Controls.ApplicationLifetimes; using UIKit; @@ -27,6 +28,7 @@ namespace Avalonia.iOS add { _onActivated += value; } remove { _onActivated -= value; } } + event EventHandler IAvaloniaAppDelegate.Deactivated { add { _onDeactivated += value; } @@ -39,6 +41,17 @@ namespace Avalonia.iOS [Export("window")] public UIWindow? Window { get; set; } + [Export("application:configurationForConnectingSceneSession:options:")] + [SupportedOSPlatform("ios13.0")] + [SupportedOSPlatform("tvos13.0")] + [SupportedOSPlatform("maccatalyst")] + public UISceneConfiguration GetConfiguration(UIApplication application, UISceneSession connectingSceneSession, UISceneConnectionOptions options) + { + var config = new UISceneConfiguration(null, connectingSceneSession.Role); + config.DelegateType = typeof(AvaloniaSceneDelegate); + return config; + } + [Export("application:didFinishLaunchingWithOptions:")] public bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions) { @@ -46,28 +59,27 @@ namespace Avalonia.iOS builder = CustomizeAppBuilder(builder); var lifetime = new SingleViewLifetime(); - - builder.AfterApplicationSetup(_ => - { - Window = new UIWindow(); - - var view = new AvaloniaView(); - lifetime.View = view; - var controller = new DefaultAvaloniaViewController - { - View = view - }; - Window.RootViewController = controller; - view.InitWithController(controller); - }); - + builder.AfterApplicationSetup(_ => CreateAndInitWindow(lifetime)); builder.SetupWithLifetime(lifetime); - Window!.MakeKeyAndVisible(); + Window?.MakeKeyAndVisible(); return true; } + private void CreateAndInitWindow(SingleViewLifetime lifetime) + { + if (OperatingSystem.IsIOSVersionAtLeast(13) || + OperatingSystem.IsTvOSVersionAtLeast(13) || + OperatingSystem.IsMacCatalyst()) + { + return; + } + + Window = new UIWindow(); + AvaloniaSceneDelegate.InitWindow(Window, lifetime); + } + [Export("application:openURL:options:")] public bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { diff --git a/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs new file mode 100644 index 0000000000..e4bbb4843e --- /dev/null +++ b/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs @@ -0,0 +1,36 @@ +using Foundation; +using UIKit; + +namespace Avalonia.iOS; + +internal sealed class AvaloniaSceneDelegate : UIResponder, IUIWindowSceneDelegate +{ + [Export("window")] + public UIWindow? Window { get; set; } + + [Export("scene:willConnectToSession:options:")] + public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions) + { + if (session.Configuration.Name is not null || + scene is not UIWindowScene windowScene || + Application.Current?.ApplicationLifetime is not SingleViewLifetime lifetime) + { + return; + } + + Window = new UIWindow(windowScene); + InitWindow(Window, lifetime); + + Window.MakeKeyAndVisible(); + } + + internal static void InitWindow(UIWindow window, SingleViewLifetime lifetime) + { + var view = new AvaloniaView(); + lifetime.View = view; + + var controller = new DefaultAvaloniaViewController { View = view }; + window.RootViewController = controller; + view.InitWithController(controller); + } +} diff --git a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs index 7c56904fdc..913148197d 100644 --- a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs +++ b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.iOS; @@ -10,9 +8,9 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe private Control? _mainView; private AvaloniaView? _view; - public AvaloniaView View + public AvaloniaView? View { - [return: MaybeNull] get => _view!; + get => _view; internal set { if (_view != null) @@ -21,7 +19,7 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe _view.Dispose(); } _view = value; - _view.Content = _mainView; + _view?.Content = _mainView; } } @@ -33,10 +31,7 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe if (_mainView != value) { _mainView = value; - if (_view != null) - { - _view.Content = _mainView; - } + _view?.Content = _mainView; } } }