【发布时间】:2020-12-30 22:17:42
【问题描述】:
我一直在尝试使用 UIDocumentBrowserViewController 在 Xamarin 中的 iOS 上实现各种“保存文件”对话框。一切都适用于在手机和 iCloud 中创建和选择文件。但是在创建文档时,它在 OneDrive 中静默失败(完全假装工作,直到您尝试在其他地方访问它)并在 Google Drive 中显示错误消息(“操作无法完成。(com.apple.DocumentManager 错误1.)")。
据我的阅读,如果它在 iCloud 中工作,它应该在 One Drive 和 Google Drive 中工作,但我似乎无法弄清楚我可能缺少什么。我已经在下面发布了代码;它松散地基于 Xam.Filepicker nuget。
public class SaveDocumentBrowser : UIDocumentBrowserViewControllerDelegate
{
//Request ID for current picking call
private int requestId;
//Task returned when all is completed
private TaskCompletionSource<FilePlaceholder> completionSource;
//Event which is invoked when a file was picked. Used elsewhere
internal EventHandler<FilePlaceholder> Handler { get; set; }
//Extensions used for choosing file names
private string[] _extensions;
//Called when a file has been chosen or created
private async void OnFilePicked(NSUrl destination, bool creation = false)
{
if (destination == null || !destination.IsFileUrl)
{
this.Handler?.Invoke(this, null);
return;
}
var document = new GenericDocument(destination);
var success = await document.OpenAsync();
if (!success)
{
this.Handler?.Invoke(this, null);
return;
}
async Task StreamSetter(Stream stream, FilePlaceholder placeholder)
{
document.DataStream = stream;
try
{
if (!await document.SaveAsync(destination, creation ? UIDocumentSaveOperation.ForCreating : UIDocumentSaveOperation.ForOverwriting))
{
throw new Exception("Failed to Save Document.");
}
}
finally
{
await document.CloseAsync();
}
}
var placeHolder = new FilePlaceholder(destination.AbsoluteString, destination.LastPathComponent, StreamSetter, b => document.Dispose());
this.Handler?.Invoke(null, placeHolder);
}
//Delegate for when user requests document creation
public override void DidRequestDocumentCreation(UIDocumentBrowserViewController controller, Action<NSUrl, UIDocumentBrowserImportMode> importHandler)
{
//this is a custom view for choosing a name for the new file
var editController = new FileNameInputViewController(_extensions);
void OnEditControllerOnOnViewDidDisappear(object sender, EventArgs args)
{
editController.OnViewDidDisappear -= OnEditControllerOnOnViewDidDisappear;
if (string.IsNullOrEmpty(editController.FileName))
{
importHandler(null, UIDocumentBrowserImportMode.None);
return;
}
try
{
var documentFolder = Path.GetTempPath();
var tempFileName = editController.FileName;
var path = Path.Combine(documentFolder, tempFileName);
var tempFile = File.Create(path);
tempFile.Dispose();
var url = NSUrl.CreateFileUrl(path, false, null);
importHandler(url, UIDocumentBrowserImportMode.Move);
}
catch(Exception e)
{
Debug.WriteLine("Failed to create temp doc: " + e);
var dialog = UIAlertController.Create("Error", "Error creating temp file: " + e.Message, UIAlertControllerStyle.Alert);
dialog.AddAction(UIAlertAction.Create("Done", UIAlertActionStyle.Cancel, null));
controller.PresentViewController(dialog, false, null);
importHandler(null, UIDocumentBrowserImportMode.None);
}
}
editController.OnViewDidDisappear += OnEditControllerOnOnViewDidDisappear;
controller.PresentViewController(editController, true, null);
}
//Delegate for when user picks file
public override void DidPickDocumentUrls(UIDocumentBrowserViewController controller, NSUrl[] documentUrls)
{
var dialog = UIAlertController.Create("Overwriting file", "Are you sure you want to overwrite this file?", UIAlertControllerStyle.Alert);
dialog.AddAction(UIAlertAction.Create("Yes", UIAlertActionStyle.Default, action => OnFilePicked(documentUrls[0])));
dialog.AddAction(UIAlertAction.Create("No", UIAlertActionStyle.Cancel, null));
controller.PresentViewController(dialog, false, null);
}
//Delegate for when user picks files (not used at this time)
public override void DidPickDocumentsAtUrls(UIDocumentBrowserViewController controller, NSUrl[] documentUrls)
{
var dialog = UIAlertController.Create("Overwriting file", "Are you sure you want to overwrite this file?", UIAlertControllerStyle.Alert);
dialog.AddAction(UIAlertAction.Create("Yes", UIAlertActionStyle.Default, action => OnFilePicked(documentUrls[0])));
dialog.AddAction(UIAlertAction.Create("No", UIAlertActionStyle.Cancel, null));
controller.PresentViewController(dialog, false, null);
}
//Delegate for when created document successfully import
public override void DidImportDocument(UIDocumentBrowserViewController controller, NSUrl sourceUrl, NSUrl destinationUrl)
{
OnFilePicked(destinationUrl);
}
//Delegate for when created document fails import
public override void FailedToImportDocument(UIDocumentBrowserViewController controller, NSUrl documentUrl, NSError error)
{
Debug.WriteLine("Failed to import doc: " + error);
var dialog = UIAlertController.Create("Error", "Error creating file: " + error, UIAlertControllerStyle.Alert);
dialog.AddAction(UIAlertAction.Create("Done", UIAlertActionStyle.Cancel, null));
controller.PresentViewController(dialog, false, null);
}
/// <summary>
/// File picking implementation
/// </summary>
/// <param name="allowedTypes">list of allowed types; may be null</param>
/// <returns>picked file data, or null when picking was cancelled</returns>
public Task<FilePlaceholder> PickMediaAsync(string[] allowedTypes)
{
var id = this.GetRequestId();
var ntcs = new TaskCompletionSource<FilePlaceholder>(id);
if (Interlocked.CompareExchange(ref this.completionSource, ntcs, null) != null)
{
throw new InvalidOperationException("Only one operation can be active at a time");
}
var allowedUtis = new string[]
{
UTType.Content,
UTType.Item,
"public.data"
};
if (allowedTypes != null)
{
allowedUtis = allowedTypes.Where(x => x[0] != '.').ToArray();
_extensions = allowedTypes.Where(x => x[0] == '.').ToArray();
}
else
{
_extensions = null;
}
//This is only custom so we can hook onto the dismissal event
var documentBrowser = new CustomDocumentBrowserViewController(allowedUtis)
{
AllowsDocumentCreation = true,
AllowsPickingMultipleItems = false,
Delegate = this,
};
void OnDocumentBrowserOnOnViewDidDisappear(object sender, EventArgs args)
{
OnFilePicked(null);
}
documentBrowser.OnViewDidDisappear += OnDocumentBrowserOnOnViewDidDisappear;
UIViewController viewController = GetActiveViewController();
viewController.PresentViewController(documentBrowser, false, null);
this.Handler = (sender, args) =>
{
documentBrowser.OnViewDidDisappear -= OnDocumentBrowserOnOnViewDidDisappear;
documentBrowser.DismissViewController(false, null);
var tcs = Interlocked.Exchange(ref this.completionSource, null);
tcs?.SetResult(args);
};
return this.completionSource.Task;
}
//Get current view controller for presentation
private static UIViewController GetActiveViewController()
{
UIWindow window = UIApplication.SharedApplication.KeyWindow;
UIViewController viewController = window.RootViewController;
while (viewController.PresentedViewController != null)
{
viewController = viewController.PresentedViewController;
}
return viewController;
}
//increment to a new id for Task completion sources
private int GetRequestId()
{
var id = this.requestId;
if (this.requestId == int.MaxValue)
{
this.requestId = 0;
}
else
{
this.requestId++;
}
return id;
}
//custom inheritance solely so we can see if user dismisses the view
private class CustomDocumentBrowserViewController : UIDocumentBrowserViewController
{
public event EventHandler OnViewDidDisappear;
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
OnViewDidDisappear?.Invoke(this, null);
}
public CustomDocumentBrowserViewController(string[] contentTypes) : base(contentTypes)
{
}
}
}
【问题讨论】:
-
您好,如果要使用 OneDrive 或 Google Drive,您使用他们的 api 方法吗? Xamarin.OneDrive.Api.iOS,Xamarin.Google.Drive.Api.iOS
-
我没有。 UIDocumentBrowser 应该适用于任何提供当前文件提供程序扩展的云存储。
标签: c# ios swift xamarin xamarin.ios