From 868ad553d4ac3b45dfa21fbba31fec01f31a4239 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 07:35:49 -0500 Subject: [PATCH 1/3] Add support for presenting/opening files on iOS --- osu.Framework.iOS/IOSFilePresenter.cs | 61 +++++++++++++++++++++++++++ osu.Framework.iOS/IOSGameHost.cs | 8 +++- osu.Framework.iOS/IOSWindow.cs | 18 ++++---- 3 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 osu.Framework.iOS/IOSFilePresenter.cs diff --git a/osu.Framework.iOS/IOSFilePresenter.cs b/osu.Framework.iOS/IOSFilePresenter.cs new file mode 100644 index 0000000000..f80c907d0b --- /dev/null +++ b/osu.Framework.iOS/IOSFilePresenter.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using Foundation; +using UIKit; +using UniformTypeIdentifiers; + +namespace osu.Framework.iOS +{ + public class IOSFilePresenter : UIDocumentInteractionControllerDelegate + { + private readonly UIDocumentInteractionController viewController = new UIDocumentInteractionController(); + + private UIWindow? window; + + public bool OpenFile(string filename, UIWindow window) + { + this.window ??= window; + + var gameView = window.RootViewController!.View!; + setupViewController(filename); + + if (viewController.PresentPreview(true)) + return true; + + return viewController.PresentOpenInMenu(gameView.Bounds, gameView, true); + } + + public bool PresentFile(string filename, UIWindow window) + { + this.window ??= window; + + var gameView = window.RootViewController!.View!; + setupViewController(filename); + + return viewController.PresentOptionsMenu(gameView.Bounds, gameView, true); + } + + private void setupViewController(string filename) + { + var url = NSUrl.FromFilename(filename); + + viewController.Url = url; + viewController.Delegate = this; + + if (OperatingSystem.IsIOSVersionAtLeast(14)) + viewController.Uti = UTType.CreateFromExtension(Path.GetExtension(filename))?.Identifier ?? UTTypes.Data.Identifier; + } + + public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller) => window!.RootViewController!; + + public override void WillBeginSendingToApplication(UIDocumentInteractionController controller, string? application) + { + // this path is triggered when a user opens the presented document in another application, + // the menu does not dismiss afterward and locks the game indefinitely. dismiss it manually. + viewController.DismissMenu(true); + } + } +} diff --git a/osu.Framework.iOS/IOSGameHost.cs b/osu.Framework.iOS/IOSGameHost.cs index 7b696fd97f..f39286e9fb 100644 --- a/osu.Framework.iOS/IOSGameHost.cs +++ b/osu.Framework.iOS/IOSGameHost.cs @@ -23,6 +23,10 @@ namespace osu.Framework.iOS { public class IOSGameHost : SDLGameHost { + private readonly IOSFilePresenter presenter = new IOSFilePresenter(); + + private IOSWindow iosWindow => (IOSWindow)Window; + public IOSGameHost() : base(string.Empty) { @@ -42,9 +46,9 @@ protected override void SetupConfig(IDictionary defaul public override Storage GetStorage(string path) => new IOSStorage(path, this); - public override bool OpenFileExternally(string filename) => false; + public override bool OpenFileExternally(string filename) => presenter.OpenFile(filename, iosWindow.UIWindow); - public override bool PresentFileExternally(string filename) => false; + public override bool PresentFileExternally(string filename) => presenter.PresentFile(filename, iosWindow.UIWindow); public override void OpenUrlExternally(string url) { diff --git a/osu.Framework.iOS/IOSWindow.cs b/osu.Framework.iOS/IOSWindow.cs index 1583ffccb8..66bc29697f 100644 --- a/osu.Framework.iOS/IOSWindow.cs +++ b/osu.Framework.iOS/IOSWindow.cs @@ -18,7 +18,9 @@ namespace osu.Framework.iOS { internal class IOSWindow : SDL3MobileWindow { - private UIWindow? window; + private UIWindow? uiWindow; + + public UIWindow UIWindow => uiWindow!; public override Size Size { @@ -27,7 +29,7 @@ protected set { base.Size = value; - if (window != null) + if (uiWindow != null) updateSafeArea(); } } @@ -43,7 +45,7 @@ public override void Create() base.Create(); - window = Runtime.GetNSObject(WindowHandle); + uiWindow = Runtime.GetNSObject(WindowHandle); updateSafeArea(); } @@ -73,14 +75,14 @@ private static void runFrame(IntPtr userdata) private void updateSafeArea() { - Debug.Assert(window != null); + Debug.Assert(uiWindow != null); SafeAreaPadding.Value = new MarginPadding { - Top = (float)window.SafeAreaInsets.Top * Scale, - Left = (float)window.SafeAreaInsets.Left * Scale, - Bottom = (float)window.SafeAreaInsets.Bottom * Scale, - Right = (float)window.SafeAreaInsets.Right * Scale, + Top = (float)uiWindow.SafeAreaInsets.Top * Scale, + Left = (float)uiWindow.SafeAreaInsets.Left * Scale, + Bottom = (float)uiWindow.SafeAreaInsets.Bottom * Scale, + Right = (float)uiWindow.SafeAreaInsets.Right * Scale, }; } } From 46f3cb14cc308f6f40a27c8b81bb08e1daba7062 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 10 Dec 2024 03:30:39 -0500 Subject: [PATCH 2/3] Move `UIWindow` passage to constructor --- osu.Framework.iOS/IOSFilePresenter.cs | 20 ++++++++++---------- osu.Framework.iOS/IOSGameHost.cs | 9 +++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Framework.iOS/IOSFilePresenter.cs b/osu.Framework.iOS/IOSFilePresenter.cs index f80c907d0b..5612bdd7a8 100644 --- a/osu.Framework.iOS/IOSFilePresenter.cs +++ b/osu.Framework.iOS/IOSFilePresenter.cs @@ -11,30 +11,30 @@ namespace osu.Framework.iOS { public class IOSFilePresenter : UIDocumentInteractionControllerDelegate { + private readonly UIWindow window; private readonly UIDocumentInteractionController viewController = new UIDocumentInteractionController(); - private UIWindow? window; - - public bool OpenFile(string filename, UIWindow window) + public IOSFilePresenter(UIWindow window) { - this.window ??= window; + this.window = window; + } - var gameView = window.RootViewController!.View!; + public bool OpenFile(string filename) + { setupViewController(filename); if (viewController.PresentPreview(true)) return true; + var gameView = window.RootViewController!.View!; return viewController.PresentOpenInMenu(gameView.Bounds, gameView, true); } - public bool PresentFile(string filename, UIWindow window) + public bool PresentFile(string filename) { - this.window ??= window; - - var gameView = window.RootViewController!.View!; setupViewController(filename); + var gameView = window.RootViewController!.View!; return viewController.PresentOptionsMenu(gameView.Bounds, gameView, true); } @@ -49,7 +49,7 @@ private void setupViewController(string filename) viewController.Uti = UTType.CreateFromExtension(Path.GetExtension(filename))?.Identifier ?? UTTypes.Data.Identifier; } - public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller) => window!.RootViewController!; + public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller) => window.RootViewController!; public override void WillBeginSendingToApplication(UIDocumentInteractionController controller, string? application) { diff --git a/osu.Framework.iOS/IOSGameHost.cs b/osu.Framework.iOS/IOSGameHost.cs index f39286e9fb..f0b4038cbc 100644 --- a/osu.Framework.iOS/IOSGameHost.cs +++ b/osu.Framework.iOS/IOSGameHost.cs @@ -23,10 +23,11 @@ namespace osu.Framework.iOS { public class IOSGameHost : SDLGameHost { - private readonly IOSFilePresenter presenter = new IOSFilePresenter(); - private IOSWindow iosWindow => (IOSWindow)Window; + private IOSFilePresenter? backingPresenter; + private IOSFilePresenter presenter => backingPresenter ??= new IOSFilePresenter(iosWindow.UIWindow); + public IOSGameHost() : base(string.Empty) { @@ -46,9 +47,9 @@ protected override void SetupConfig(IDictionary defaul public override Storage GetStorage(string path) => new IOSStorage(path, this); - public override bool OpenFileExternally(string filename) => presenter.OpenFile(filename, iosWindow.UIWindow); + public override bool OpenFileExternally(string filename) => presenter.OpenFile(filename); - public override bool PresentFileExternally(string filename) => presenter.PresentFile(filename, iosWindow.UIWindow); + public override bool PresentFileExternally(string filename) => presenter.PresentFile(filename); public override void OpenUrlExternally(string url) { From 7c7529dee8710da03ba9d4688b2be3f5be786a0c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 01:16:58 -0500 Subject: [PATCH 3/3] Simplify further --- osu.Framework.iOS/IOSFilePresenter.cs | 12 ++++++------ osu.Framework.iOS/IOSGameHost.cs | 12 +++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Framework.iOS/IOSFilePresenter.cs b/osu.Framework.iOS/IOSFilePresenter.cs index 5612bdd7a8..7b58da9d5b 100644 --- a/osu.Framework.iOS/IOSFilePresenter.cs +++ b/osu.Framework.iOS/IOSFilePresenter.cs @@ -9,12 +9,12 @@ namespace osu.Framework.iOS { - public class IOSFilePresenter : UIDocumentInteractionControllerDelegate + internal class IOSFilePresenter : UIDocumentInteractionControllerDelegate { - private readonly UIWindow window; + private readonly IOSWindow window; private readonly UIDocumentInteractionController viewController = new UIDocumentInteractionController(); - public IOSFilePresenter(UIWindow window) + internal IOSFilePresenter(IOSWindow window) { this.window = window; } @@ -26,7 +26,7 @@ public bool OpenFile(string filename) if (viewController.PresentPreview(true)) return true; - var gameView = window.RootViewController!.View!; + var gameView = window.UIWindow.RootViewController!.View!; return viewController.PresentOpenInMenu(gameView.Bounds, gameView, true); } @@ -34,7 +34,7 @@ public bool PresentFile(string filename) { setupViewController(filename); - var gameView = window.RootViewController!.View!; + var gameView = window.UIWindow.RootViewController!.View!; return viewController.PresentOptionsMenu(gameView.Bounds, gameView, true); } @@ -49,7 +49,7 @@ private void setupViewController(string filename) viewController.Uti = UTType.CreateFromExtension(Path.GetExtension(filename))?.Identifier ?? UTTypes.Data.Identifier; } - public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller) => window.RootViewController!; + public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller) => window.UIWindow.RootViewController!; public override void WillBeginSendingToApplication(UIDocumentInteractionController controller, string? application) { diff --git a/osu.Framework.iOS/IOSGameHost.cs b/osu.Framework.iOS/IOSGameHost.cs index f0b4038cbc..f823ba3564 100644 --- a/osu.Framework.iOS/IOSGameHost.cs +++ b/osu.Framework.iOS/IOSGameHost.cs @@ -23,17 +23,19 @@ namespace osu.Framework.iOS { public class IOSGameHost : SDLGameHost { - private IOSWindow iosWindow => (IOSWindow)Window; - - private IOSFilePresenter? backingPresenter; - private IOSFilePresenter presenter => backingPresenter ??= new IOSFilePresenter(iosWindow.UIWindow); + private IOSFilePresenter presenter = null!; public IOSGameHost() : base(string.Empty) { } - protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => new IOSWindow(preferredSurface, Options.FriendlyGameName); + protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) + { + var window = new IOSWindow(preferredSurface, Options.FriendlyGameName); + presenter = new IOSFilePresenter(window); + return window; + } protected override void SetupConfig(IDictionary defaultOverrides) {