sandeep
sandeep

Reputation: 77

Xamarin IOS file picker : unable to get selected file from icloud drive

I've installed filepicker control from Nuget and added tried adding reference from MonoTouch10 folder and later from github to my xamarin.ios project.

FileData file = await CrossFilePicker.Current.PickFile();

if (file != null) { }

this is the code i added to my browse button, after selecting a file from iCloud drive, control never comes to "if condition".

and again when i click on browse button for second time, app crashes saying "only one operation can be active at a time".

Upvotes: 2

Views: 2172

Answers (3)

sandeep
sandeep

Reputation: 77

To answer my own question, i customized in plugin, like

  1. Used DocumentPicker_DidPickDocumentAtUrls event instead of DocumentPicker_DidPickDocument.

  2. while returning selected file used

     new FileData(e.FilePath, e.FileName, () =>
            {
                var url = new Foundation.NSUrl(e.FilePath);
                return new FileStream(url.Path, FileMode.Open, FileAccess.Read);
            })
    

This solved my issue. Thanks.

Upvotes: 2

vividos
vividos

Reputation: 6748

There are several forks of the FilePicker Xamarin plugin. I recommend the following project, since it's the most actively maintained one: https://github.com/jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows (note: I'm one of the contributors to the project).

With this version of the plugin file picking should work. The example code from sandeep's answer was already incorporated into the latest version of the plugin. Be sure to read the README.md's Troubleshooting page in case you're getting problems.

Upvotes: 0

Led Machine
Led Machine

Reputation: 7552

Modifying the source code of the FilePickerImplementation plugin for the iOS platform worked, in this way:

using Foundation;
using MobileCoreServices;
using Plugin.FilePicker.Abstractions;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UIKit;
using System.Diagnostics;

namespace Plugin.FilePicker
{
    /// <summary>
    /// Implementation for FilePicker
    /// </summary>
    public class FilePickerImplementation : NSObject, IUIDocumentMenuDelegate, IFilePicker
    {
        private int _requestId;
        private TaskCompletionSource<FileData> _completionSource;

        /// <summary>
        /// Event which is invoked when a file was picked
        /// </summary>
        public EventHandler<FilePickerEventArgs> Handler
        {
            get;
            set;
        }

        private void OnFilePicked(FilePickerEventArgs e)
        {
            Handler?.Invoke(null, e);
        }

        public void DidPickDocumentPicker(UIDocumentMenuViewController documentMenu, UIDocumentPickerViewController documentPicker)
        {
            documentPicker.DidPickDocument += DocumentPicker_DidPickDocument;
            documentPicker.WasCancelled += DocumentPicker_WasCancelled;
            documentPicker.DidPickDocumentAtUrls += DocumentPicker_DidPickDocumentAtUrls;
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(documentPicker, true, null);
        }

        private void DocumentPicker_DidPickDocumentAtUrls(object sender, UIDocumentPickedAtUrlsEventArgs e)
        {
            var control = (UIDocumentPickerViewController)sender;
            foreach (var url in e.Urls)
                DocumentPicker_DidPickDocument(control, new UIDocumentPickedEventArgs(url));

            control.Dispose();
        }

        private void DocumentPicker_DidPickDocument(object sender, UIDocumentPickedEventArgs e)
        {
            var securityEnabled = e.Url.StartAccessingSecurityScopedResource();
            var doc = new UIDocument(e.Url);
            var data = NSData.FromUrl(e.Url);
            var dataBytes = new byte[data.Length];

            System.Runtime.InteropServices.Marshal.Copy(data.Bytes, dataBytes, 0, Convert.ToInt32(data.Length));

            string filename = doc.LocalizedName;
            string pathname = doc.FileUrl?.ToString();

            // iCloud drive can return null for LocalizedName.
            if (filename == null)
            {
                // Retrieve actual filename by taking the last entry after / in FileURL.
                // e.g. /path/to/file.ext -> file.ext

                // filesplit is either:
                // 0 (pathname is null, or last / is at position 0)
                // -1 (no / in pathname)
                // positive int (last occurence of / in string)
                var filesplit = pathname?.LastIndexOf('/') ?? 0;

                filename = pathname?.Substring(filesplit + 1);
            }

            OnFilePicked(new FilePickerEventArgs(dataBytes, filename, pathname));
        }

        /// <summary>
        /// Handles when the file picker was cancelled. Either in the
        /// popup menu or later on.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void DocumentPicker_WasCancelled(object sender, EventArgs e)
        {
            {
                var tcs = Interlocked.Exchange(ref _completionSource, null);
                tcs.SetResult(null);
            }
        }

        /// <summary>
        /// Lets the user pick a file with the systems default file picker
        /// For iOS iCloud drive needs to be configured
        /// </summary>
        /// <returns></returns>
        public async Task<FileData> PickFile()
        {
            var media = await TakeMediaAsync();

            return media;
        }

        private Task<FileData> TakeMediaAsync()
        {
            var id = GetRequestId();

            var ntcs = new TaskCompletionSource<FileData>(id);

            if (Interlocked.CompareExchange(ref _completionSource, ntcs, null) != null)
                throw new InvalidOperationException("Only one operation can be active at a time");

            var allowedUtis = new string[] {
                UTType.UTF8PlainText,
                UTType.PlainText,
                UTType.RTF,
                UTType.PNG,
                UTType.Text,
                UTType.PDF,
                UTType.Image,
                UTType.UTF16PlainText,
                UTType.FileURL
            };

            var importMenu =
                new UIDocumentMenuViewController(allowedUtis, UIDocumentPickerMode.Import)
                {
                    Delegate = this,
                    ModalPresentationStyle = UIModalPresentationStyle.Popover
                };

            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(importMenu, true, null);

            var presPopover = importMenu.PopoverPresentationController;

            if (presPopover != null)
            {
                presPopover.SourceView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
                presPopover.PermittedArrowDirections = UIPopoverArrowDirection.Down;
            }

            Handler = null;

            Handler = (s, e) => {
                var tcs = Interlocked.Exchange(ref _completionSource, null);

                tcs?.SetResult(new FileData(e.FilePath, e.FileName, () => { var url = new Foundation.NSUrl(e.FilePath); return new FileStream(url.Path, FileMode.Open, FileAccess.Read); }));
            };

            return _completionSource.Task;
        }

        public void WasCancelled(UIDocumentMenuViewController documentMenu)
        {
            var tcs = Interlocked.Exchange(ref _completionSource, null);

            tcs?.SetResult(null);
        }

        private int GetRequestId()
        {
            var id = _requestId;

            if (_requestId == int.MaxValue)
                _requestId = 0;
            else
                _requestId++;

            return id;
        }

        public async Task<bool> SaveFile(FileData fileToSave)
        {
            try
            {
                var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                var fileName = Path.Combine(documents, fileToSave.FileName);

                File.WriteAllBytes(fileName, fileToSave.DataArray);

                return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                return false;
            }
        }

        public void OpenFile(NSUrl fileUrl)
        {
            var docControl = UIDocumentInteractionController.FromUrl(fileUrl);

            var window = UIApplication.SharedApplication.KeyWindow;
            var subViews = window.Subviews;
            var lastView = subViews.Last();
            var frame = lastView.Frame;

            docControl.PresentOpenInMenu(frame, lastView, true);
        }

        public void OpenFile(string fileToOpen)
        {
            var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            var fileName = Path.Combine(documents, fileToOpen);

            if (NSFileManager.DefaultManager.FileExists(fileName))
            {
                var url = new NSUrl(fileName, true);
                OpenFile(url);
            }
        }

        public async void OpenFile(FileData fileToOpen)
        {
            var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            var fileName = Path.Combine(documents, fileToOpen.FileName);

            if (NSFileManager.DefaultManager.FileExists(fileName))
            {
                var url = new NSUrl(fileName, true);

                OpenFile(url);
            }
            else
            {
                await SaveFile(fileToOpen);
                OpenFile(fileToOpen);
            }
        }
    }
}

Upvotes: 3

Related Questions