Skip to content

Commit

Permalink
Safe platform specific API usage on mac/ios
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkatz6 committed Jun 22, 2022
1 parent a3b8790 commit a1ef467
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -101,13 +102,15 @@
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
AB7A61EC2147C814003C5833 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */,
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */,
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */,
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
Expand All @@ -122,6 +125,7 @@
AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup;
children = (
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */,
522D5958258159C1006F7F7A /* Carbon.framework */,
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */,
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
Expand Down
30 changes: 17 additions & 13 deletions native/Avalonia.Native/src/OSX/SystemDialogs.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "common.h"
#include "INSWindowHolder.h"
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
{
Expand Down Expand Up @@ -199,22 +200,25 @@ virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile];
}

if(filters != nullptr)
if (@available(macOS 11.0, *))
{
auto filtersString = [NSString stringWithUTF8String:filters];

if(filtersString.length > 0)
if(filters != nullptr)
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
NSMutableArray *mapped = [NSMutableArray arrayWithCapacity:[allowedTypes count]];
[allowedTypes enumerateObjectsUsingBlock:^(NSString obj, NSUInteger idx, BOOL *stop) {
auto utType = [UTType init:obj]
[mapped addObject:utType];
}];
auto filtersString = [NSString stringWithUTF8String:filters];

if(filtersString.length > 0)
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
NSMutableArray *mapped = [NSMutableArray arrayWithCapacity:[allowedTypes count]];
[allowedTypes enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
id utType = [UTType typeWithIdentifier:obj];
[mapped addObject:utType];
}];

panel.allowedContentTypes = allowedTypes;
panel.allowsOtherFileTypes = false;
panel.extensionHidden = false;
panel.allowedContentTypes = mapped;
panel.allowsOtherFileTypes = false;
panel.extensionHidden = false;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/iOS/Avalonia.iOS/AvaloniaView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public TopLevelImpl(AvaloniaView view)
{
_view = view;
NativeControlHost = new NativeControlHostImpl(_view);
StorageProvider = new IOSStorageProvider(view.Window.RootViewController);
StorageProvider = new IOSStorageProvider(view);
}

public void Dispose()
Expand Down
4 changes: 2 additions & 2 deletions src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem

protected IOSStorageItem(NSUrl url)
{
Url = url;
Url = url ?? throw new ArgumentNullException(nameof(url));

using (var doc = new UIDocument(url))
{
Expand Down Expand Up @@ -84,7 +84,7 @@ public Task ReleaseBookmark()
public bool TryGetUri([NotNullWhen(true)] out Uri uri)
{
uri = Url;
return true;
return uri is not null;
}

public void Dispose()
Expand Down
106 changes: 79 additions & 27 deletions src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using UIKit;
using MobileCoreServices;
using Foundation;
using UniformTypeIdentifiers;
using UTTypeLegacy = MobileCoreServices.UTType;
using UTType = UniformTypeIdentifiers.UTType;

#nullable enable

namespace Avalonia.iOS.Storage;

internal class IOSStorageProvider : IStorageProvider
{
private readonly UIViewController _uiViewController;
public IOSStorageProvider(UIViewController uiViewController)
private readonly AvaloniaView _view;
public IOSStorageProvider(AvaloniaView view)
{
_uiViewController = uiViewController;
_view = view;
}

public bool CanOpen => true;
Expand All @@ -28,26 +31,60 @@ public IOSStorageProvider(UIViewController uiViewController)

public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
var allowedUtis = options.FileTypeFilter?.SelectMany(f => f.AppleUniformTypeIdentifiers ?? Array.Empty<string>())
.ToArray() ?? new[]
UIDocumentPickerViewController documentPicker;
if (OperatingSystem.IsIOSVersionAtLeast(14))
{
UTType.Content,
UTType.Item,
"public.data"
};

using var documentPicker = new UIDocumentPickerViewController(allowedUtis, UIDocumentPickerMode.Open)
var allowedUtis = options.FileTypeFilter?.SelectMany(f =>
{
// We check for OS version outside of the lambda, it's safe.
#pragma warning disable CA1416
if (f.AppleUniformTypeIdentifiers?.Any() == true)
{
return f.AppleUniformTypeIdentifiers.Select(id => UTType.CreateFromIdentifier(id));
}
if (f.MimeTypes?.Any() == true)
{
return f.MimeTypes.Select(id => UTType.CreateFromMimeType(id));
}
return Array.Empty<UTType>();
#pragma warning restore CA1416
})
.Where(id => id is not null)
.ToArray() ?? new[]
{
UTTypes.Content,
UTTypes.Item,
UTTypes.Data
};
documentPicker = new UIDocumentPickerViewController(allowedUtis!, false);
}
else
{
DirectoryUrl = GetUrlFromFolder(options.SuggestedStartLocation)
};
var allowedUtis = options.FileTypeFilter?.SelectMany(f => f.AppleUniformTypeIdentifiers ?? Array.Empty<string>())
.ToArray() ?? new[]
{
UTTypeLegacy.Content,
UTTypeLegacy.Item,
"public.data"
};
documentPicker = new UIDocumentPickerViewController(allowedUtis, UIDocumentPickerMode.Open);
}

if (OperatingSystem.IsIOSVersionAtLeast(11, 0))
using (documentPicker)
{
documentPicker.AllowsMultipleSelection = options.AllowMultiple;
if (OperatingSystem.IsIOSVersionAtLeast(13))
{
documentPicker.DirectoryUrl = GetUrlFromFolder(options.SuggestedStartLocation);
}

if (OperatingSystem.IsIOSVersionAtLeast(11, 0))
{
documentPicker.AllowsMultipleSelection = options.AllowMultiple;
}

var urls = await ShowPicker(documentPicker);
return urls.Select(u => new IOSStorageFile(u)).ToArray();
}

var urls = await ShowPicker(documentPicker);
return urls.Select(u => new IOSStorageFile(u)).ToArray();
}

public Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
Expand All @@ -70,11 +107,16 @@ public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpe

public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
using var documentPicker = new UIDocumentPickerViewController(new string[] { UTType.Folder }, UIDocumentPickerMode.Open)
using var documentPicker = OperatingSystem.IsIOSVersionAtLeast(14) ?
new UIDocumentPickerViewController(new[] { UTTypes.Folder }, false) :
new UIDocumentPickerViewController(new string[] { UTTypeLegacy.Folder }, UIDocumentPickerMode.Open);

if (OperatingSystem.IsIOSVersionAtLeast(13))
{
DirectoryUrl = GetUrlFromFolder(options.SuggestedStartLocation)
};
if (OperatingSystem.IsIOSVersionAtLeast(11, 0))
documentPicker.DirectoryUrl = GetUrlFromFolder(options.SuggestedStartLocation);
}

if (OperatingSystem.IsIOSVersionAtLeast(11))
{
documentPicker.AllowsMultipleSelection = options.AllowMultiple;
}
Expand All @@ -85,9 +127,17 @@ public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPic

private static NSUrl? GetUrlFromFolder(IStorageFolder? folder)
{
return folder is IOSStorageFolder iosFolder
? iosFolder.Url
: folder?.TryGetUri(out var fullPath) == true ? fullPath : null;
if (folder is IOSStorageFolder iosFolder)
{
return iosFolder.Url;
}

if (folder?.TryGetUri(out var fullPath) == true)
{
return fullPath;
}

return null;
}

private Task<NSUrl[]> ShowPicker(UIDocumentPickerViewController documentPicker)
Expand All @@ -101,7 +151,9 @@ private Task<NSUrl[]> ShowPicker(UIDocumentPickerViewController documentPicker)
new UIPresentationControllerDelegate(() => tcs.TrySetResult(Array.Empty<NSUrl>()));
}

_uiViewController.PresentViewController(documentPicker, true, null);
var controller = _view.Window?.RootViewController ?? throw new InvalidOperationException("RootViewController wasn't initialized");
controller.PresentViewController(documentPicker, true, null);

return tcs.Task;
}

Expand Down

0 comments on commit a1ef467

Please sign in to comment.