Quellcode durchsuchen

Add Application.TryGetFeature, re-add new feature - IActivatableLifetime (#14556)

* Implement IOptionalFeatureProvider on Application class

* Use IActivatableLifetime from the TryGetFeature instead of a lifetime

* Reimplement macOS IActivatableLifetime

* Reimplement iOS IActivatableLifetime

* Reimplement Browser IActivatableLifetime

* Reimplement Android IActivatableLifetime

* Make AndroidActivatableLifetime aware of activity re-creation

* Don't crash control catalog dialogs page

* Browser TryEnterBackground shouldn't return true
Max Katz vor 1 Jahr
Ursprung
Commit
b9ec339cfa

+ 1 - 0
Avalonia.sln.DotSettings

@@ -37,5 +37,6 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Activatable/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 2 - 1
samples/ControlCatalog/App.xaml.cs

@@ -3,6 +3,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
+using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Themes.Simple;
 using Avalonia.Themes.Fluent;
@@ -51,7 +52,7 @@ namespace ControlCatalog
                 singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
             }
 
-            if (ApplicationLifetime is IActivatableApplicationLifetime activatableApplicationLifetime)
+            if (this.TryGetFeature<IActivatableLifetime>() is {} activatableApplicationLifetime)
             {
                 activatableApplicationLifetime.Activated += (sender, args) =>
                     Console.WriteLine($"App activated: {args.Kind}");

+ 10 - 2
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -3,6 +3,7 @@ using System.Buffers;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
+using System.Security;
 using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Controls;
@@ -43,7 +44,7 @@ namespace ControlCatalog.Pages
                 {
                     lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
                 }
-                else
+                else if (!string.IsNullOrWhiteSpace(currentFolderBox.Text))
                 {
                     if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink))
                     {
@@ -52,7 +53,14 @@ namespace ControlCatalog.Pages
 
                     if (folderLink is not null)
                     {
-                        lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
+                        try
+                        {
+                            lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
+                        }
+                        catch (SecurityException)
+                        {
+                            
+                        }
                     }
                 }
             };

+ 3 - 1
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -5,6 +5,7 @@ using Avalonia.Controls;
 using Avalonia.Android;
 using Avalonia.Android.Platform;
 using Avalonia.Android.Platform.Input;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.OpenGL.Egl;
@@ -81,7 +82,8 @@ namespace Avalonia.Android
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
                 .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
-                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
+                .Bind<IActivatableLifetime>().ToConstant(new AndroidActivatableLifetime());
 
             var graphics = InitializeGraphics(Options);
             if (graphics is not null)

+ 11 - 1
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@@ -1,5 +1,9 @@
 #nullable enable
 
+using Avalonia.Android.Platform;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform;
+
 namespace Avalonia.Android
 {
     partial class AvaloniaMainActivity<TApp> where TApp : Application, new()
@@ -36,11 +40,17 @@ namespace Avalonia.Android
             {
                 var builder = CreateAppBuilder();
 
-                builder.SetupWithLifetime(new SingleViewLifetime(this));
+                builder.SetupWithLifetime(new SingleViewLifetime());
 
                 s_appBuilder = builder;
             }
 
+            if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
+                is AndroidActivatableLifetime activatableLifetime)
+            {
+                activatableLifetime.Activity = this;
+            }            
+
             View = new AvaloniaView(this);
             if (ViewContent != null)
             {

+ 47 - 0
src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs

@@ -0,0 +1,47 @@
+using System;
+using Android.App;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Android.Platform;
+
+internal class AndroidActivatableLifetime : IActivatableLifetime
+{
+    private IAvaloniaActivity _activity;
+
+    public IAvaloniaActivity Activity
+    {
+        get => _activity;
+        set
+        {
+            if (_activity is not null)
+            {
+                _activity.Activated -= ActivityOnActivated;
+                _activity.Deactivated -= ActivityOnDeactivated;
+            }
+
+            _activity = value;
+
+            if (_activity is not null)
+            {
+                _activity.Activated += ActivityOnActivated;
+                _activity.Deactivated += ActivityOnDeactivated;
+            }
+        }
+    }
+    
+    public event EventHandler<ActivatedEventArgs> Activated;
+    public event EventHandler<ActivatedEventArgs> Deactivated;
+
+    public bool TryLeaveBackground() => (_activity as Activity)?.MoveTaskToBack(true) == true;
+    public bool TryEnterBackground() => false;
+
+    private void ActivityOnDeactivated(object sender, ActivatedEventArgs e)
+    {
+        Deactivated?.Invoke(this, e);
+    }
+
+    private void ActivityOnActivated(object sender, ActivatedEventArgs e)
+    {
+        Activated?.Invoke(this, e);
+    }
+}

+ 2 - 21
src/Android/Avalonia.Android/SingleViewLifetime.cs

@@ -1,25 +1,11 @@
-using System;
-using Android.App;
-using Avalonia.Controls;
+using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 
 namespace Avalonia.Android
 {
-    internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
+    internal class SingleViewLifetime : ISingleViewApplicationLifetime
     {
-        private readonly Activity _activity;
         private AvaloniaView _view;
-
-        public SingleViewLifetime(Activity activity)
-        {
-            _activity = activity;
-
-            if (activity is IAvaloniaActivity activableActivity)
-            { 
-                activableActivity.Activated += (_, args) => Activated?.Invoke(this, args);
-                activableActivity.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
-            }
-        }
         
         public AvaloniaView View
         {
@@ -36,10 +22,5 @@ namespace Avalonia.Android
         }
 
         public Control MainView { get; set; }
-        public event EventHandler<ActivatedEventArgs> Activated;
-        public event EventHandler<ActivatedEventArgs> Deactivated;
-
-        public bool TryLeaveBackground() => _activity.MoveTaskToBack(true);
-        public bool TryEnterBackground() => false;
     }
 }

+ 5 - 2
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs

@@ -1,22 +1,25 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
 
+// TODO12: move to Avalonia namespace. 
 namespace Avalonia.Platform;
 
 public interface IOptionalFeatureProvider
 {
     /// <summary>
-    /// Queries for an optional feature
+    /// Queries for an optional feature.
     /// </summary>
-    /// <param name="featureType">Feature type</param>
+    /// <param name="featureType">Feature type.</param>
     public object? TryGetFeature(Type featureType);
 }
 
 public static class OptionalFeatureProviderExtensions
 {
+    /// <inheritdoc cref="IOptionalFeatureProvider.TryGetFeature"/>
     public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class =>
         (T?)provider.TryGetFeature(typeof(T));
 
+    /// <inheritdoc cref="IOptionalFeatureProvider.TryGetFeature"/>
     public static bool TryGetFeature<T>(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv)
         where T : class
     {

+ 3 - 7
src/Avalonia.Controls/AppBuilder.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Linq;
@@ -58,6 +59,8 @@ namespace Avalonia
         /// <summary>
         /// Gets a method to override a lifetime factory.
         /// </summary>
+        [Obsolete("This property has no effect", true)]
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
         public Func<Type, IApplicationLifetime?>? LifetimeOverride { get; private set; }
 
         /// <summary>
@@ -244,13 +247,6 @@ namespace Avalonia
             return Self;
         }
 
-        [PrivateApi]
-        public AppBuilder UseLifetimeOverride(Func<Type, IApplicationLifetime?> func)
-        {
-            LifetimeOverride = func;
-            return Self;
-        }
-
         /// <summary>
         /// Configures platform-specific options
         /// </summary>

+ 31 - 4
src/Avalonia.Controls/Application.cs

@@ -30,7 +30,7 @@ namespace Avalonia
     /// method.
     /// - Tracks the lifetime of the application.
     /// </remarks>
-    public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents
+    public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents, IOptionalFeatureProvider
     {
         /// <summary>
         /// The application-global data templates.
@@ -62,7 +62,7 @@ namespace Avalonia
         /// <inheritdoc/>
         public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
 
-        [Obsolete("Cast ApplicationLifetime to IActivatableApplicationLifetime instead.")]
+        [Obsolete("Use Application.Current.TryGetFeature<IActivatableLifetime>() instead.")]
         public event EventHandler<UrlOpenedEventArgs>? UrlsOpened;
 
         /// <inheritdoc/>
@@ -204,7 +204,7 @@ namespace Avalonia
         /// which should always be preferred over a global one,
         /// as specific top levels might have different settings set-up. 
         /// </remarks>
-        public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
+        public IPlatformSettings? PlatformSettings => this.TryGetFeature<IPlatformSettings>();
         
         event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
         {
@@ -329,7 +329,34 @@ namespace Avalonia
             get => _name;
             set => SetAndRaise(NameProperty, ref _name, value);
         }
-        
+
+        /// <summary>
+        /// Queries for an optional feature.
+        /// </summary>
+        /// <param name="featureType">Feature type.</param>
+        /// <remarks>
+        /// Features currently supported by <see cref="Application.TryGetFeature"/>:
+        /// <list type="bullet">
+        /// <item>IPlatformSettings</item>
+        /// <item>IActivatableApplicationLifetime</item>
+        /// </list>
+        /// </remarks>
+        public object? TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IPlatformSettings))
+            {
+                return AvaloniaLocator.Current.GetService<IPlatformSettings>();
+            }
+
+            if (featureType == typeof(IActivatableLifetime))
+            {
+                return AvaloniaLocator.Current.GetService<IActivatableLifetime>();
+            }
+
+            // Do not return just any service from AvaloniaLocator.
+            return null;
+        }
+
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);

+ 1 - 2
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -217,8 +217,7 @@ namespace Avalonia
         private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args,
             Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder)
         {
-            var lifetime = builder.LifetimeOverride?.Invoke(typeof(ClassicDesktopStyleApplicationLifetime)) as ClassicDesktopStyleApplicationLifetime 
-                ?? new ClassicDesktopStyleApplicationLifetime();
+            var lifetime = new ClassicDesktopStyleApplicationLifetime();
             lifetime.SubscribeGlobalEvents();
 
             lifetime.Args = args;

+ 8 - 1
src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs

@@ -1,13 +1,20 @@
 using System;
+using System.Diagnostics;
 using Avalonia.Metadata;
 
 namespace Avalonia.Controls.ApplicationLifetimes;
 
+[NotClientImplementable]
+[Obsolete("This interface has no effect. Instead use Application.Current.TryGetFeature<IActivatableLifetime>().", true)]
+public interface IActivatableApplicationLifetime : IActivatableLifetime {
+
+}
+
 /// <summary>
 /// An interface for ApplicationLifetimes where the application can be Activated and Deactivated.
 /// </summary>
 [NotClientImplementable]
-public interface IActivatableApplicationLifetime
+public interface IActivatableLifetime
 {
     /// <summary>
     /// An event that is raised when the application is Activated for various reasons

+ 8 - 8
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@@ -9,18 +9,18 @@ namespace Avalonia.Native
     internal class AvaloniaNativeApplicationPlatform : NativeCallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
     {
         public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
-        
+
         void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
         {
-            ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
+            ((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray());
         }
-        
+    
         void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls)
         {
             // Raise the urls opened event to be compatible with legacy behavior.
-            ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
+            ((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray());
 
-            if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
+            if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
             {
                 foreach (var url in urls.ToStringArray())
                 {
@@ -34,7 +34,7 @@ namespace Avalonia.Native
 
         void IAvnApplicationEvents.OnReopen()
         {
-            if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
+            if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
             {
                 lifetime.RaiseActivated(ActivationKind.Reopen);    
             }
@@ -42,7 +42,7 @@ namespace Avalonia.Native
 
         void IAvnApplicationEvents.OnHide()
         {
-            if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
+            if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
             {
                 lifetime.RaiseDeactivated(ActivationKind.Background);    
             }
@@ -50,7 +50,7 @@ namespace Avalonia.Native
 
         void IAvnApplicationEvents.OnUnhide()
         {
-            if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
+            if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
             {
                 lifetime.RaiseActivated(ActivationKind.Background);    
             }

+ 3 - 1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Runtime.InteropServices;
 using Avalonia.Compatibility;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
@@ -112,7 +113,8 @@ namespace Avalonia.Native
                 .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
                 .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
                 .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
-                .Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()));
+                .Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()))
+                .Bind<IActivatableLifetime>().ToSingleton<MacOSActivatableLifetime>();
 
             var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt);
             hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));

+ 1 - 3
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@@ -23,9 +23,7 @@ namespace Avalonia
                             platform.SetupApplicationName();
                             platform.SetupApplicationMenuExporter();
                         });
-                })
-                .UseLifetimeOverride(type => type == typeof(ClassicDesktopStyleApplicationLifetime)
-                    ? new MacOSClassicDesktopStyleApplicationLifetime() : null);
+                });
 
             return builder;
         }

+ 1 - 2
src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs → src/Avalonia.Native/MacOSActivatableLifetime.cs

@@ -6,8 +6,7 @@ namespace Avalonia.Native;
 
 #nullable enable
 
-internal class MacOSClassicDesktopStyleApplicationLifetime : ClassicDesktopStyleApplicationLifetime,
-    IActivatableApplicationLifetime
+internal class MacOSActivatableLifetime : IActivatableLifetime
 {
     /// <inheritdoc />
     public event EventHandler<ActivatedEventArgs>? Activated;

+ 36 - 0
src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs

@@ -0,0 +1,36 @@
+using System;
+using Avalonia.Browser.Interop;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+
+namespace Avalonia.Browser;
+
+internal class BrowserActivatableLifetime : IActivatableLifetime
+{
+    public BrowserActivatableLifetime()
+    {
+        bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible =>
+        {
+            initiallyVisible = null;
+            (visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
+        });
+    
+        // Trigger Activated as an initial state, if web page is visible, and wasn't hidden during initialization.
+        if (initiallyVisible == true)
+        {
+            _ = Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                if (initiallyVisible == true)
+                {
+                    Activated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
+                }
+            }, DispatcherPriority.Background);
+        }
+    }
+    
+    public event EventHandler<ActivatedEventArgs>? Activated;
+    public event EventHandler<ActivatedEventArgs>? Deactivated;
+
+    public bool TryLeaveBackground() => false;
+    public bool TryEnterBackground() => false;
+}

+ 1 - 19
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@@ -2,24 +2,12 @@
 using System.Diagnostics.CodeAnalysis;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
-using System.Runtime.Versioning;
 using Avalonia.Browser;
-using Avalonia.Browser.Interop;
-using Avalonia.Threading;
 
 namespace Avalonia;
 
-internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
+internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
 {
-    public BrowserSingleViewLifetime()
-    {
-        bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible =>
-        {
-            initiallyVisible = null;
-            (visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
-        });
-    }
-    
     public AvaloniaView? View;
 
     public Control? MainView
@@ -44,10 +32,4 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, IActi
             throw new InvalidOperationException("Browser lifetime was not initialized. Make sure AppBuilder.StartBrowserApp was called.");
         }
     }
-
-    public event EventHandler<ActivatedEventArgs>? Activated;
-    public event EventHandler<ActivatedEventArgs>? Deactivated;
-
-    public bool TryLeaveBackground() => false;
-    public bool TryEnterBackground() => false;
 }

+ 3 - 1
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Browser.Interop;
 using Avalonia.Browser.Skia;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Platform;
@@ -43,7 +44,8 @@ internal class BrowserWindowingPlatform : IWindowingPlatform
             .Bind<IWindowingPlatform>().ToConstant(instance)
             .Bind<IPlatformGraphics>().ToConstant(new BrowserSkiaGraphics())
             .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
-            .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
+            .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
+            .Bind<IActivatableLifetime>().ToSingleton<BrowserActivatableLifetime>();
 
         if (AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() is { } options
             && options.RegisterAvaloniaServiceWorker)

+ 18 - 0
src/iOS/Avalonia.iOS/ActivatableLifetime.cs

@@ -0,0 +1,18 @@
+using System;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.iOS;
+
+internal class ActivatableLifetime : IActivatableLifetime
+{
+    public ActivatableLifetime(IAvaloniaAppDelegate avaloniaAppDelegate)
+    {
+        avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args);
+        avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
+    }
+
+    public event EventHandler<ActivatedEventArgs>? Activated;
+    public event EventHandler<ActivatedEventArgs>? Deactivated;
+    public bool TryLeaveBackground() => false;
+    public bool TryEnterBackground() => false;
+}

+ 2 - 2
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@@ -42,9 +42,9 @@ namespace Avalonia.iOS
         [Export("application:didFinishLaunchingWithOptions:")]
         public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
         {
-            var builder = AppBuilder.Configure<TApp>().UseiOS();
+            var builder = AppBuilder.Configure<TApp>().UseiOS(this);
 
-            var lifetime = new SingleViewLifetime(this);
+            var lifetime = new SingleViewLifetime();
 
             builder.AfterSetup(_ =>
             {

+ 13 - 3
src/iOS/Avalonia.iOS/Platform.cs

@@ -1,8 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
+using Avalonia.iOS;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Rendering.Composition;
@@ -39,13 +41,15 @@ namespace Avalonia
 
     public static class IOSApplicationExtensions
     {
-        public static AppBuilder UseiOS(this AppBuilder builder)
+        public static AppBuilder UseiOS(this AppBuilder builder, IAvaloniaAppDelegate appDelegate)
         {
             return builder
                 .UseStandardRuntimePlatformSubsystem()
-                .UseWindowingSubsystem(iOS.Platform.Register, "iOS")
+                .UseWindowingSubsystem(() => iOS.Platform.Register(appDelegate), "iOS")
                 .UseSkia();
         }
+
+        public static AppBuilder UseiOS(this AppBuilder builder) => UseiOS(builder, null!);
     }
 }
 
@@ -58,7 +62,7 @@ namespace Avalonia.iOS
         public static DisplayLinkTimer? Timer;
         internal static Compositor? Compositor { get; private set; }
 
-        public static void Register()
+        public static void Register(IAvaloniaAppDelegate? appDelegate)
         {
             Options = AvaloniaLocator.Current.GetService<iOSPlatformOptions>() ?? new iOSPlatformOptions();
 
@@ -77,6 +81,12 @@ namespace Avalonia.iOS
                 .Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance)
                 .Bind<IKeyboardDevice>().ToConstant(keyboard);
 
+            if (appDelegate is not null)
+            {
+                AvaloniaLocator.CurrentMutable
+                    .Bind<IActivatableLifetime>().ToConstant(new ActivatableLifetime(appDelegate));
+            }
+
             Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
             AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
         }

+ 2 - 13
src/iOS/Avalonia.iOS/SingleViewLifetime.cs

@@ -4,14 +4,8 @@ using Avalonia.Controls.ApplicationLifetimes;
 
 namespace Avalonia.iOS;
 
-internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
-{
-    public SingleViewLifetime(IAvaloniaAppDelegate avaloniaAppDelegate)
-    {
-        avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args);
-        avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
-    }
-            
+internal class SingleViewLifetime : ISingleViewApplicationLifetime
+{       
     public AvaloniaView? View;
 
     public Control? MainView
@@ -19,9 +13,4 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatable
         get => View!.Content;
         set => View!.Content = value;
     }
-
-    public event EventHandler<ActivatedEventArgs>? Activated;
-    public event EventHandler<ActivatedEventArgs>? Deactivated;
-    public bool TryLeaveBackground() => false;
-    public bool TryEnterBackground() => false;
 }