【问题标题】:Xamarin.iOS UIDocumentBrowser fails to create file in third-party containersXamarin.iOS UIDocumentBrowser 无法在第三方容器中创建文件
【发布时间】: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)
            {

            }
        }
    }

【问题讨论】:

标签: c# ios swift xamarin xamarin.ios


【解决方案1】:

UIDocumentBrowserViewController 可以处理本地存储的文件,也可以直接处理 iCloud 中的文件。

但是,似乎不能直接用于第三方存储服务。与apple document 核对一下。

第三方存储服务还可以通过实施文件提供程序扩展(iOS 11 或更高版本)来提供对他们管理的文档的访问。如需更多信息,请参阅File Provider

因此,您将需要一个文件提供程序扩展。您可以查看xamarin official sample 以了解如何实现这一目标。

【讨论】:

  • 好吧,所以如果我解释正确,我必须将文件提供程序扩展添加到我的应用程序中,以便在我创建文件时,第三方存储可以从我的应用到第三方存储?那有点……糟透了……您是否碰巧知道在存储提供商中创建新文件的更简单方法?无论如何,我很乐意为此使用文档选择器,我只使用文档浏览器,因为它可以选择创建文档...
  • @Sonic1015 是的,您需要一个文件提供程序扩展。我认为至少到目前为止还没有简单的方法来处理第三方存储服务。如果发现将在此处更新。
  • 文件在 iOS 设备上至少可以说是令人沮丧的。感谢您帮助确定问题以及潜在的解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-21
  • 2022-01-17
  • 1970-01-01
相关资源
最近更新 更多