How to open downloaded file on iOS

Viewed 3531

We're developing mobile application in Xamarin. How can next be accomplished in iOS:

  • user downloads file from url (http request is made to REST API, secured with Authentication Basic username:secretKey)
  • file is saved to iOS
  • user opens a file (allowed are jpg, png, pdf, doc, docx, png)
  • file is opened in default application (e.g. for images image viewer)

As file operations are platform specific, here's interface definition:

public interface IFileHelper
{
  void DownloadFileAndSave(Models.DocumentModel document);
}

Android implementation:

public class FileHelper : IFileHelper
{
  // download file and view status in download manager
  public void DownloadFileAndSave(Models.DocumentModel document)
  {
    DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService(Context.DownloadService);
    string url = WebApiUtils.GetBaseUrl() + string.Format("Api/v1/Dms/{0}", document.UniqueId);
    DownloadManager.Request request = new Android.App.DownloadManager.Request(Android.Net.Uri.Parse(url)));

    request.AddRequestHeader("Authorization", "Basic " + WebApiUtils.GetEncodedCredentials(Auth.Users.Current));

    var downloadFile = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
    string path = Path.Combine(downloadFile.AbsolutePath, document.FileName);
    request.SetDestinationUri(Android.Net.Uri.FromFile(new Java.IO.File(path)));
    request.SetMimeType(document.ContentType);  
    request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);

    dm.Enqueue(request);
}

In Android file is simply stored on the filesystem and with File Explorer which is by default installed on any Android (i.e. My Files -> device storage -> Download), the file is opened in default application for file's mime type. Everything fine on Android.

Apple iOS implementation:

public class FileHelper : IFileHelper
{
  public void DownloadFileAndSave(Models.DocumentModel document)
  {
    WebClient webClient = new WebClient();

    webClient.Headers.Add(HttpRequestHeader.Authorization, "Basic " + WebApiUtils.GetEncodedCredentials(Auth.Users.Current));
    webClient.DownloadDataAsync(new System.Uri(WebApiUtils.GetBaseUrl() + string.Format(Consts.ApiUrls.GetDocument, document.UniqueId)));

    webClient.DownloadDataCompleted += (sender, e) =>
    {
      byte[] content = e.Result;
      string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), document.FileName);

      // doesn't throw exception therefore saved ok
      File.WriteAllBytes(path, content);

      Uri uri = new Uri(String.Format("file://{0}", path));

      // doesn't work.
      Device.OpenUri(uri);
    };
  }
}

Is there any other way to open downloaded file in default application. If I open url e.g. http://example.com/files/file1.png it opens the file in safari, but I can't put Authorization: Basic headers in Device.OpenUri.

I read about Load Non-Web Documents with WebView but you would have to build each file as BundleResource.

1 Answers

As Code Warrior commented there is one approach posted on link: https://forums.xamarin.com/discussion/36964/why-is-it-that-nothing-is-working-to-open-an-existing-local-pdf-file-in-the-ios-portion-of-my-pcl.

But Save image action doesn't work, everything else seems to work.

public void DownloadFileAndSave(Models.DocumentModel document)
{
  WebClient webClient = new WebClient();
  webClient.Headers.Add(HttpRequestHeader.Authorization, "Basic " + WebApiUtils.GetEncodedCredentials(Auth.Users.Current));

  string tempPath = Path.GetTempPath();
  string localFilename = Path.GetFileName(document.FileName);
  string localPath = Path.Combine(tempPath, localFilename);

  webClient.DownloadFileCompleted += (sender, e) =>
  {
    Device.BeginInvokeOnMainThread(() =>
    {
      QLPreviewItemFileSystem prevItem = new QLPreviewItemFileSystem(localFilename, localPath); // ql = quick look
      QLPreviewController previewController = new QLPreviewController()
      {
        DataSource = new PreviewControllerDS(prevItem)
      };
      UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null);
    });
  };

  // download file
  Uri uri = new System.Uri(WebApiUtils.GetBaseUrl() + string.Format(Consts.ApiUrls.GetDocument, document.UniqueId));
  webClient.DownloadFileAsync(uri, localPath);
}

When Save image is triggered I get next error:

2017-10-03 13:45:56.797 MyApp.iOS[477:61030] Video /private/var/mobile/Containers/Data/Application/33D7139A-53E0-4A2E-8C78-D3D13A2259B0/tmp/water-h2o-md.png cannot be saved to the saved photos album: Error Domain=AVFoundationErrorDomain Code=-11828 "Cannot Open" UserInfo={NSUnderlyingError=0x1c0445d60 {Error Domain=NSOSStatusErrorDomain Code=-12847 "(null)"}, NSLocalizedFailureReason=This media format is not supported., NSURL=file:///private/var/mobile/Containers/Data/Application/33D7139A-53E0-4A2E-8C78-D3D13A2259B0/tmp/water-h2o-md.png, NSLocalizedDescription=Cannot Open}

iOS treates image as video? Is this a bug on iOS or am I something missing.

UPDATE

It turns out that next permissions was missing in Info.plist file:

<key>NSPhotoLibraryUsageDescription</key>
<string>Application needs to access photos</string>

<!-- for iOS 11 -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Application needs to access photos</string>

Now Save Image action is working ok. But seriously Apple could return more appropriate error than Video image.jpg cannot be saved ...

Related