xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.49k stars 515 forks source link

Files shared for "open in place" editing with third-party apps do not work correctly in iOS17 #19352

Closed gavinschultz closed 1 year ago

gavinschultz commented 1 year ago

Steps to Reproduce

Our app has PDFs which we allow third-party PDF apps to "open in place" for editing. The mechanism works basically like this:

  1. Info.plist has all the necessary flags for opening a document, namely UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace, both set to true/YES.
  2. Store PDFs in our app in a subfolder of the Documents folder (Environment.SpecialFolder.MyDocuments); resolving ultimately to, for example: /var/mobile/Containers/Data/Application/788C7161-32EB-4AF0-964D-2A473257A791/Documents/7cafe7e7-9da9-40cd-b233-a85b0174fe00/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf.
  3. Via UIDocumentInteractionController.PresentOpenInMenu, allow the user to open the PDF for editing in a third-party app:
    
    private static UIDocumentInteractionController documentController;

public void OpenPdfFormFile(string filePath) { PresentOpenInMenu(NSUrl.FromFilename(filePath)); }

public void PresentOpenInMenu(NSUrl url) { documentController = UIDocumentInteractionController.FromUrl(url); var lastView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;

// Specify a frame for the Open In menu. Required for iPad (but can be run for all).
var frame = new CGRect(lastView.Frame.Width / 2, lastView.Frame.Bottom, 0, 0);

var menuPresented = documentController.PresentOpenInMenu(frame, lastView, true);
if (!menuPresented)
{
    Toast(NSBundle.MainBundle.GetLocalizedString("FileHelper_OpenPdfFailed"), Helpers.ToastLength.Long);
}

}


The absolute URL generated at this point is a simple variation of the original e.g. `file:///var/mobile/Containers/Data/Application/788C7161-32EB-4AF0-964D-2A473257A791/Documents/7cafe7e7-9da9-40cd-b233-a85b0174fe00/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf`

### Expected Behavior

The original PDF files in our app's Documents directory can be opened in third-party PDF applications, and saved without any additional work from the user.

### Actual Behavior

On iOS 16 and lower, it works as expected; the third-party app is able to read and automatically save changes to the PDF directly in our app's folder path. For example, the Xodo logs indicate that it's opening directly from our app's path:

> Xodo initialized document instance: <PTCoordinatedDocument: 0x282453a00> fileURL: file:///private/var/mobile/Containers/Data/Application/788C7161-32EB-4AF0-964D-2A473257A791/Documents/7cafe7e7-9da9-40cd-b233-a85b0174fe00/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf
> Xodo opening document: <PTCoordinatedDocument: 0x282453a00> fileURL: file:///private/var/mobile/Containers/Data/Application/788C7161-32EB-4AF0-964D-2A473257A791/Documents/7cafe7e7-9da9-40cd-b233-a85b0174fe00/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf

On iOS 17, this no longer works correctly, and although the third-party PDF editor can *read* our PDF, it's quite clear that it's actually using a copy. If not quite identical to what happens when `LSSupportsOpeningDocumentsInPlace` is false/NO, the end result is very similar:

> Xodo initialized document instance: <PTCoordinatedDocument: 0x2828aae00> fileURL: file:///var/mobile/Containers/Data/Application/6830EDC5-F776-4BEF-8C30-046BCA1F2DF0/Documents/Processed%20Files/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf
> Xodo opening document: <PTCoordinatedDocument: 0x2828aae00> fileURL: file:///var/mobile/Containers/Data/Application/6830EDC5-F776-4BEF-8C30-046BCA1F2DF0/Documents/Processed%20Files/624cbc1e-a7c5-4c0d-8937-b0a00013b2a9.pdf

The third-party app will immediately prompt the user to save the file, but regardless, the copy is now beyond the purview of our app so we can no longer see any changes made to it.

The only workaround is to have the user explicitly share the file from the third-party app back to our app (we have the code in place to handle this), but much preferred the seamless "open in place" integration which did not require this.

There have been vague allusions to changes in the file system in iOS 17, but I have not been able to find primary sources or much specific detail. For example:
* https://developer.apple.com/forums/thread/733642
* https://developer.apple.com/forums/thread/737971

I have not been able to figure out if this is a flaw in our code, or iOS 17, or Xamarin's implementation of the API. Note that we _are_ fairly sure this is not due to specific flaws in the third-party apps, as all of the ones we have tried (PDF Viewer, Xodo etc) exhibit basically the same behavior.

### Environment
<details>
<summary>Version information</summary>

Visual Studio Community 2022 for Mac Version 17.6.5 (build 417) Installation UUID: 2f2234ec-5313-440b-8113-4742c87050d4

Runtime .NET 7.0.3 (64-bit) Architecture: Arm64 Microsoft.macOS.Sdk 13.1.1007; git-rev-head:8afca776a0a96613dfb7200e0917bb57f9ed5583; git-branch:release/7.0.1xx-xcode14.2

Roslyn (Language Service) 4.6.0-3.23180.6+99e956e42697a6dd886d1e12478ea2b27cceacfa

NuGet Version: 6.4.0.117

.NET SDK (Arm64) SDK: /usr/local/share/dotnet/sdk/7.0.309/Sdks SDK Versions: 7.0.309 7.0.308 6.0.415 6.0.414 6.0.302 MSBuild SDKs: /Applications/Visual Studio.app/Contents/MonoBundle/MSBuild/Current/bin/Sdks

.NET SDK (x64) SDK Versions: 3.1.426 3.1.421

.NET Runtime (Arm64) Runtime: /usr/local/share/dotnet/dotnet Runtime Versions: 7.0.12 7.0.11 6.0.23 6.0.22 6.0.7

.NET Runtime (x64) Runtime: /usr/local/share/dotnet/x64/dotnet Runtime Versions: 3.1.32 3.1.27

Xamarin.Profiler Version: 1.8.0.49 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

Updater Version: 11

Apple Developer Tools Xcode: 15.0 22265 Build: 15A240d

Xamarin.Mac Not Installed

Xamarin.iOS Version: 16.4.0.18 Visual Studio Community Hash: 9d266025e Branch: xcode14.3 Build date: 2023-09-06 19:52:27-0400

Xamarin Designer Version: 17.6.3.9 Hash: 2648399ae8 Branch: remotes/origin/d17-6 Build date: 2023-10-04 18:09:13 UTC

Xamarin.Android Not Installed

Microsoft Build of OpenJDK Java SDK: Not Found

Eclipse Temurin JDK Java SDK: Not Found

Android SDK Manager Version: 17.6.0.50 Hash: a715dca Branch: HEAD Build date: 2023-10-04 18:09:18 UTC

Android Device Manager Version: 0.0.0.1309 Hash: 06e3e77 Branch: HEAD Build date: 2023-10-04 18:09:18 UTC

Build Information Release ID: 1706050417 Git revision: 6d6585a706becbd4a5be3b0e99ace260dfdf5748 Build date: 2023-10-04 18:07:26+00 Build branch: release-17.6 Build lane: release-17.6

Operating System Mac OS X 14.0.0 Darwin 23.0.0 Darwin Kernel Version 23.0.0 Fri Sep 15 14:41:34 PDT 2023 root:xnu-10002.1.13~1/RELEASE_ARM64_T8103 arm64



</details>

### Build Logs

N/A, build works fine

### Example Project (If Possible)

None
Eagle104 commented 1 year ago

I have been experiencing the same issue on Word with .docx documents they seem to only open in Read-Only mode. with ios17. I have been looking for a solution for some time now and cant seem to find what could be causing this.

rolfbjarne commented 1 year ago

My guess would be that this is an intentional change by Apple, but only they can confirm. Apple's documentation also isn't 100% clear that your code is supposed to work (the documentation for LSSupportsOpeningDocumentsInPlace seems to imply it's only for file provider (extension?)s, the fact that it worked for apps might not have been Apple's intention).

I would start diagnosing this by trying to replicate the problem in an Xcode project (which would rule out any problems in our bindings).

If the Xcode project works, please attach both the Xcode project and the Xamarin/.NET project here, reopen the issue, and we'll investigate to try to figure out what we're doing wrong.

If the Xcode project doesn't work, then I'd post a question on Apple's Developer Forums.

I'm closing this for now since it does not look like a bug on our side (as I said, feel free to reopen if you determine otherwise).

gavinschultz commented 1 year ago

Just a quick follow-up.

We posted a corresponding question in the Apple Developer Forum as suggested. No answers at time of writing, but one more "that's happening to me too" response.

We also submitted a formal TSI (Technical Support Incident) to Apple Developer Technical Support, but the response was, in short:

We’ve determined that your question should be addressed by the support channel for Xamarin - the third-party resource you are using. DTS supports developers using Apple-authored tools and frameworks only. We are unable to provide support for third-party tools, development environments, tutorials or other resources.

We agree with Rolf's advice that the next best step is to recreate this natively in an XCode project. Unfortunately we don't have the resourcing available to do this at the moment, so for now we'll keep our ear to the ground to see if a solution naturally emerges; perhaps eventually someone very important will have the same problem and Apple will jump on it.

Thank you for your responses to date.

gavinschultz commented 1 year ago

Also, just in regards to this:

Apple's documentation also isn't 100% clear that your code is supposed to work (the documentation for LSSupportsOpeningDocumentsInPlace seems to imply it's only for file provider (extension?)s

I may be wrong, but my reading of the File Provider documentation is that with the flags we set and to simply provide access to our files, we wouldn't need to implement a File Provider extension:

You don’t need a File Provider extension to allow access to documents that your app stores locally. In iOS, to give other apps access to the files in your Documents directory, set the following keys in your app’s Info tab or its Info.plist file. For document browser-based apps, set the UISupportsDocumentBrowser key. For all other apps, set both the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace keys.

MartinWegner commented 11 months ago

I came across the same problem. @Eagle104 and @gavinschultz. Did you make any progress on this problem? I'm stuck.

gavinschultz commented 11 months ago

@MartinWegner No further progress yet - sorry.

We are using the workaround of making the users share the file back from the third-party application to our application and re-saving it. It's not ideal to have the users perform that extra step, but the best we have for now.

As a starting point, we do this by overriding OpenUrl in the iOS AppDelegate (disclaimer: this is relatively old code and still Xamarin rather than MAUI - there could well be a more modern way of doing this in 2023):

[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
        //This function will be triggered when a document is shared from third-party application via "Open in" or "Copy to"
        public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
        {
           // re-save file from url
        }
}
MartinWegner commented 11 months ago

@gavinschultz Thank you for sharing the workaround. We have the same function in place and it works for now. But we struggle with the fact, that our initial opening of the file is read only. This isn't the case in iOS 16. Some apps like Microsoft Office offer a function to create a copy of the file to there own storage. Others like Collabora Office don't. So the user is stuck with a read only file he cant edit or save. Did you find any workaround for that? Seems like a Collabora Office problem, but I would like it to work like in iOS 16.

gavinschultz commented 11 months ago

@MartinWegner I feel your pain but, like you, I too would guess that Collabora is probably not doing enough in this case. It makes some intuitive sense that, without open-in-place working properly, the file being shared wouldn't be initially writeable to other apps unless they make the local copy themselves. We've been fortunate in our own case that we haven't had to deal with that problem, as the third-party PDF apps we typically suggest (PDF Viewer and Xodo) do behave more like what you describe for Microsoft Office. Sorry I can't be of more help!