It’s a common occurrence in mobile applications to have a file be open in one app – but need another app to use it. For example, you browsed to a PDF and find you’d like to perform some modifications to it in another app. By tapping on that PDF, a “Open In” bar will appear across the top of the screen, allowing you to import the file in your desired app.
Sending files between apps sounds like a pretty operating system specific feature – something that would best be not attempted in Xamarin.Forms, right? Actually, while it is platform-specific, it’s pretty straight-forward, and the rest of this post will demonstrate how to get it up and running on iOS. (A future post will tackle Android … and if there’s any demand, we’ll cover UWP too.)
It should be noted that for this post I am NOT going to speak about creating an app extension to the iOS portion of your Forms solution, and then having your app appear when the user taps the share button (the little rectangle with an arrow coming out of it). For now this is purely about bringing a file into our Forms app – not “sharing” content between apps.
Our Scenario
The example that we’ll build out in this post is having our Xamarin.Forms app register as capable of opening PDF files and then displaying the PDF file in a WebView. That will demonstrate the basics of registering as being able to open for a file with iOS and a means to open them with Xamarin.Forms (that will be applicable to Android as well).
So let’s get to it.
iOS Registration
When I say our Forms app will be registered, I mean it will show up as in the following screenshots.
Safari is opened and I browsed to a PDF file on the web.
The app’s name is “OpenForms”, and in the first screenshot iOS is giving us the ability to directly open the file via the “Open In OpenForms” shortcut.
In the second screenshot, by clicking on the “More” menu on the upper left side, the bottom action sheet comes up and gives several options. The icon for our app is shown.
So … how do we register the app?
Messing with Info.plist
In order to get iOS to recognize that our app can handle PDF files, we need to insert some information into the Info.plist file. You will need to add a new top level key to the dictionary, and it can be accomplished either through the built-in editor that Xamarin Studio has, or by editing the file directly.
To use Xamarin Studio – double click on the Info.Plist and the built-in editor will pop up. Select the “Advanced” tab at the bottom of the screen. Underneath the “Document Types” section at the very top of the screen – click the Add Document Type button. That will add a section much like the screenshot below.
Here’s how you will want to fill that out.
- Name: Anything you want … it’s for your reference later.
- Types: This will turn into an array of strings … and they must be very specific types of strings, more on this later.
- Icons: Locations of icons that you want to have displayed in the more menu – instead of the app’s main icon. Again, this may turn into an array if there are more than one.
So filling that out will modify the Info.plist and allow the app to register to open other file types. But I always find it helps to understand what it’s changing in the Info.plist … and it turns out there are some additional keys as well that can be added that I haven’t found a way to do through the IDE.
Here’s the overall dictionary that is used to register for opening file types.
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>PDF</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
</array>
</dict>
</array>
Let’s break this down a bit … CFBundleDocumentTypes
is the top-level Info.plist dictionary key and it contains an array of other dictionary objects.
Within a single one of those dictionary objects, the following keys are found:
CFBundleTypeName
– the value to this key is a string – and it’s whatever you want to refer to it later as.CFBundleTypeRole
– the value to this key is a string – and it lets the operating system what our app is able to do to the file. In this case it is a Viewer.LSHandlerRank
– another string value; how important our app is to the file type. Microsoft Word would be the Default for Word files. Our app here is an Alternate.LSItemContentTypes
– an array of strings … and these indicate what file types the app can open.
So everything seems pretty straight forward except for the LSItemContentTypes
values … namely, where can those be found?? They seem like they’d be pretty hard to guess! The identifier text itself is called a Uniform Type Identifier and a list of publicly registered ones can be found on Apple’s documentation site.
Ok … that’s actually the difficult part … and now with that over – how will iOS let our app know that there’s a file waiting for it to be opened?
Responding to iOS Open Events
When somebody does select our app to open the PDF file with the following function will get invoked:
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
What happens is that iOS copies the file to be opened by our app into a storage spot which our app can access – specified via the NSUrl url
parameter. So it’s here that we should take action and pass that file over to the Xamarin.Forms core project.
This is what the full AppDelegate then looks like:
App mainForms;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
mainForms = new App();
LoadApplication(mainForms);
return base.FinishedLaunching(app, options);
}
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
mainForms.DisplayThePDF(url.Path);
return true;
}
A couple of things out of the ordinary. The first is that I am keeping a local variable of the Xamarin.Forms App
class called mainForms
in the AppDelegate. I am going to use this later on as a point of entry to display the PDF.
Then in the OpenUrl
function I am invoking a DisplayPDF()
function of the mainForms
variable. That will take care of displaying the PDF within Xamarin.Forms.
Ideally here I would make a copy of the PDF locally and then delete the one specified by the url
parameter – to keep space free. But I’m not in this example 🙂
Xamarin.Forms!
I spent a lot of time above talking about how to set things up on the iOS side. However, when you do this in a real project – the iOS specific portion really is nothing more than a formality. The real work begins over on the Xamarin.Forms side of things and how you’ll handle the incoming file.
In this case, I left the task on displaying the PDF to an article I found on the Xamarin website. It does involve a custom renderer, but overall not too bad.
What I want to focus on here is the entry point into the Forms solution where the platform data will go.
In the App.xaml.cs
file – or the App
class (because that’s the class the AppDelegate has access to) – I have a function defined there that takes care of the hand-off between the iOS project and the Forms project. The definition is as follows:
public void DisplayThePDF(string url)
{
var openFilePage = new OpenFilesPage(url);;
_navigationRoot.Navigation.PushModalAsync(new NavigationPage(openFilePage));
}
All this does is create a new Xamarin.Forms page, pass it the location of where the file is, and then display that page modally. (In the constructor of the App
class, I am putting everything into a NavigationPage
, and then storing that to the _navigationRoot
variable.)
By showing it modally, it will appear over the top of whatever else the app happened to be doing at the time, and won’t affect existing navigation stack (other than being displayed over the top). Of course this is geared to viewing a PDF file. In a real app, you’d want to check the state of your app – what’s currently being displayed (so as not to push a modal over a modal). Your app’s workflow may require something else, such as popping everything off the stack and starting over – or if you’re using tabs – switch to a new one … you get the point.
Then the OpenFilesPage
… it has a subclassed WebView
on it, with a custom renderer for each platform. That class then has a property for where the file it needs to display is located, and then the custom renderer takes care of displaying it.
Having the custom renderer take care of the actual display of the file is how I can get away with passing in the iOS specific path of the location of the PDF file. It really never goes into Xamarin.Forms at all. But again, this is only in this specific implementation scenario. The real point I wanted to make is that the App
class needs an entry point for the iOS project to access.
Summary
Overall, opening a file from one application in our Xamarin.Forms app isn’t too difficult. There is some formalities that one needs to go through on the iOS side to modify the Info.plist file to register our app as being able to handle certain file types. Then the iOS app needs to override the OpenUrl
function.
Once done however, then it’s all up to Forms. Ideally there is an entry point in the Xamarin.Forms App
class that the iOS AppDelegate can invoke to get the “shared” portion of the code invoked. Once there, it’s up to your situation on what you need to do with that file. You may need to write a custom renderer for display. Or it’s possible no further platform functionality will be needed at all, everything can be done in the shared project.
But once it’s in Forms – then you have it!
Comments