diff --git a/osu.Framework.iOS/IOSFilePresenter.cs b/osu.Framework.iOS/IOSFilePresenter.cs new file mode 100644 index 0000000000..7b58da9d5b --- /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 +{ + internal class IOSFilePresenter : UIDocumentInteractionControllerDelegate + { + private readonly IOSWindow window; + private readonly UIDocumentInteractionController viewController = new UIDocumentInteractionController(); + + internal IOSFilePresenter(IOSWindow window) + { + this.window = window; + } + + public bool OpenFile(string filename) + { + setupViewController(filename); + + if (viewController.PresentPreview(true)) + return true; + + var gameView = window.UIWindow.RootViewController!.View!; + return viewController.PresentOpenInMenu(gameView.Bounds, gameView, true); + } + + public bool PresentFile(string filename) + { + setupViewController(filename); + + var gameView = window.UIWindow.RootViewController!.View!; + 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.UIWindow.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..f823ba3564 100644 --- a/osu.Framework.iOS/IOSGameHost.cs +++ b/osu.Framework.iOS/IOSGameHost.cs @@ -23,12 +23,19 @@ namespace osu.Framework.iOS { public class IOSGameHost : SDLGameHost { + 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) { @@ -42,9 +49,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); - public override bool PresentFileExternally(string filename) => false; + public override bool PresentFileExternally(string filename) => presenter.PresentFile(filename); 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, }; } }