diff --git a/samples/ControlCatalog.iOS/Info.plist b/samples/ControlCatalog.iOS/Info.plist
index b4c7c07eb6..a1aa23e506 100644
--- a/samples/ControlCatalog.iOS/Info.plist
+++ b/samples/ControlCatalog.iOS/Info.plist
@@ -16,7 +16,6 @@
1
2
- 3
UIRequiredDeviceCapabilities
@@ -38,5 +37,7 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ com.apple.security.files.user-selected.read-write
+
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
index 4c1bf97c6f..79d88c13b0 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
@@ -26,7 +26,7 @@ internal class IOSStorageProvider : IStorageProvider
public bool CanOpen => true;
- public bool CanSave => false;
+ public bool CanSave => true;
public bool CanPickFolder => true;
@@ -161,10 +161,72 @@ internal class IOSStorageProvider : IStorageProvider
return Task.FromResult(new IOSStorageFolder(uri, wellKnownFolder));
}
- public Task SaveFilePickerAsync(FilePickerSaveOptions options)
+ public async Task SaveFilePickerAsync(FilePickerSaveOptions options)
{
- return Task.FromException(
- new PlatformNotSupportedException("Save file picker is not supported by iOS"));
+ /*
+ This requires a bit of dialog here...
+ To save a file, we need to present the user with a document picker
+ This requires a temp file to be created and used to "export" the file to.
+ When the user picks the file location and name, UIDocumentPickerViewController
+ will give back the URI to the real file location, which we can then use
+ to give back as an IStorageFile.
+ https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller
+ Yes, it is weird, but without the temp file it will explode.
+ */
+
+ // Create a temporary file to use with the document picker
+ var tempFileName = StorageProviderHelpers.NameWithExtension(
+ options.SuggestedFileName ?? "document",
+ options.DefaultExtension,
+ options.FileTypeChoices?.FirstOrDefault());
+
+ var tempDir = NSFileManager.DefaultManager.GetTemporaryDirectory().Append(Guid.NewGuid().ToString(), true);
+ if (tempDir == null)
+ {
+ throw new InvalidOperationException("Failed to get temporary directory for save file picker");
+ }
+
+ var isDirectoryCreated = NSFileManager.DefaultManager.CreateDirectory(tempDir, true, null, out var error);
+ if (!isDirectoryCreated)
+ {
+ throw new InvalidOperationException("Failed to create temporary directory for save file picker");
+ }
+
+ var tempFileUrl = tempDir.Append(tempFileName, false);
+
+ // Create an empty file at the temp location
+ NSData.FromBytes(0, 0).Save(tempFileUrl, false);
+
+ UIDocumentPickerViewController documentPicker;
+ if (OperatingSystem.IsIOSVersionAtLeast(14))
+ {
+ documentPicker = new UIDocumentPickerViewController(new[] { tempFileUrl }, asCopy: true);
+ }
+ else
+ {
+#pragma warning disable CA1422
+ documentPicker = new UIDocumentPickerViewController(tempFileUrl, UIDocumentPickerMode.ExportToService);
+#pragma warning restore CA1422
+ }
+
+ using (documentPicker)
+ {
+ if (OperatingSystem.IsIOSVersionAtLeast(13))
+ {
+ documentPicker.DirectoryUrl = GetUrlFromFolder(options.SuggestedStartLocation);
+ }
+
+ documentPicker.Title = options.Title;
+
+ var tcs = new TaskCompletionSource();
+ documentPicker.Delegate = new PickerDelegate(urls => tcs.TrySetResult(urls));
+ var urls = await ShowPicker(documentPicker, tcs);
+
+ // Clean up the temporary directory
+ NSFileManager.DefaultManager.Remove(tempDir, out _);
+
+ return urls.FirstOrDefault() is { } url ? new IOSStorageFile(url) : null;
+ }
}
public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)