iCloud Document Picker with Custom UTI - ios

Does UIDocumentPickerViewController initWithDocumentTypes require a public UTI to function?
I am trying to utilize iCloud Documents to allow users to import a proprietary file type from iCloud Drive. Testing works fine for public UTI, such as: #"public.text"
If I don't include a public UTI in the initWithDocumentTypes array, I get a screen indicating:
No Documents. Documents in iCloud Drive are not available because the
iCloud Documents & Data setting is disabled.
My Imported UTI is defined in Target > Info as "com.domain.file". I have to believe this is set up correctly, as I can select one of my proprietary files in another app (e.g. Dropbox) and my app is displayed in the Open In... options.
In my import action, I've tried every variation I can think of to get my custom UTI to display the picker.
- (IBAction)importDocumentPickerTapped:(id)sender
{
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc initWithDocumentTypes:#[#"com.domain.file", #"com.domain.app.file", #"iCloud.com.domain.file", #"iCloud.com.domain.app.file" ] inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
}
If I add #"public.text" to the initWithDocumentTypes array, then the iCloud Drive Locations picker screen displays as expected. I can select .txt and .rtf files, but my custom file types are grayed out and not selectable.
Note:
I have not created a Document Provider extension, as I don't believe this is required, and my file format it not common.
I see the following warning when I take any action on UIDocumentPickerViewController, even if it's Cancel and even if the action on a file (i.e. save .txt) works. I've spent quite a bit of time just trying to track down the source of this warning, to no avail.
plugin com.apple.UIKit.fileprovider.default invalidated

I had this issue and addressed it by having my custom exported UTI conform to the 'public.data' UTI.
You can set this by selecting your target and then the info tab. Scroll to the Exported UTIs section and expand it. Under your custom UTI there is a box to declare that it conforms.
See here for a list of system defined UTIs:
https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html

Related

Choose destination when saving file

Is it possible to make user able to choose a destination for the file that he wants to download, something like DocumentPicker which you can use when choosing a file to upload?
I want something like this:
Yes, for iOS 13 and later you can ask the user to select a directory via UIDocumentPickerViewController. You'll get back a security scoped url(s) for the directories selected by the user.
Details here: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
I've pasted the sample code from that page below, but you'll want to read the documentation carefully because security scoped URLs require careful handling :)
If you need iOS 12 or earlier the user can only select files so I'm unclear on a clean way to do this (but we're on iOS 14 and iOS 15 is about to come out so hopefully you don't have to support back past iOS 13).
Here's the sample code from the link above showing how this is done:
// Create a document picker for directories.
let documentPicker =
UIDocumentPickerViewController(forOpeningContentTypes: [.folder])
documentPicker.delegate = self
// Set the initial directory.
documentPicker.directoryURL = startingDirectory
// Present the document picker.
present(documentPicker, animated: true, completion: nil)

Showing a save file document picker in iOS without providing local file urls

I want to show a file picker for choosing a "save to file" location on iOS. The UIDocumentViewPickerController offers the method initWithURL:inMode: (https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller/1618684-initwithurl?language=objc) for saving files but it requires an url to a local file. Instead I want to get a file handle to which the data is written after picking. Does iOS offer such a function, or do I have to create a temporary local file first and then use the initWithURL method?
Regards,
iOS 13 offers means for picking directories (https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories). It is possible to create new items as well.
// Create a document picker for directories.
let documentPicker =
UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String],
in: .open)

How to share files for open-in-place on iOS

We have an iOS application that manages documents via Core Data. The actual files reside in the app's shared container so that the app's file provider extension can also access them for Files.app support. We want to give the user the option to open these files in third-party apps so that they can edit them in-place instead of sending a copy to the other app.
We provide a UIActivityViewController for sharing files with other apps. We also provide a UIActivity that shows a UIDocumentInteractionController which seems to work better in some cases. We give the UIActivityViewController the document's file URL, the raw text content, and printable data.
This works but all third-party editors are shown as Copy to … instead of Open in …
We've also set the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace properties to YES in the app's info.plist but they seem to be only relevant for open-in-place when sharing files residing in the app's Documents folder.
Now we've stumbled upon the NSItemProviderFileOptionOpenInPlace option for NSItemProvider. As we're already supporting a file provider extension and from Apple's documentation this seemed like a great place to accomplish just what we want.
Adding a "pure" NSItemProvider works, in a way, but shows fewer options than when also sharing the file URL and text in addition (which is expected). However, when we use -[NSItemProvider registerFileRepresentationForTypeIdentifier:fileOptions:visibility:loadHandler:] with the said option (or just zero, same result) and return the file URL in the loadHandler's completionHandler() nothing is shared anymore. E.g., Mail no longer attaches the file, Messages doesn't show the document for sending.
These are the relevant bits of the code:
NSMutableArray *items = [NSMutableArray array];
NSMutableArray <UIActivity *> *activities = [NSMutableArray array];
NSURL *fileURL = self.record.metadata.fileURL;
NSString *fileUTI = self.record.metadata.uti;
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem: fileURL typeIdentifier: fileUTI];
[itemProvider registerFileRepresentationForTypeIdentifier:fileUTI fileOptions:NSItemProviderFileOptionOpenInPlace visibility:YES loadHandler:^NSProgress * _Nullable(void (^ _Nonnull completionHandler)(NSURL * _Nullable, BOOL, NSError * _Nullable))
{
if (fileURL)
completionHandler(fileURL, YES, nil);
else
completionHandler(nil, YES, [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]);
return nil;
}];
[items addObject:itemProvider];
self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
[UIAppDelegate.splitViewController presentViewController:self.activityViewController animated:YES completion:nil];
The using the Share menu the item provider's load handler is correctly called and the file's actual URL returned.
Is that not how NSItemProviderFileOptionOpenInPlace is intended to be used? Or are we using it simply wrong? Apple's description is extremely sparse and we couldn't find any information elsewhere on the internet except for the official documentation.
I've found out what my problem was: Not deep enough understanding of the relationship between the activity view controller and file providers.
As all my files reside in the shared container and are published also through the file provider extension, what I need to share through the activity view controller is the exact same URL that is shared through the file provider extension. Technically then the app that opens the file accesses it through there file provider mechanism.

Files with imported UTIs are greyed out in UIDocumentPickerViewController

In my iOS app I am opening a UIDocumentPickerViewController to import .sty files (MIDI styles). For this I have declared a custom UTI in Imported UTIs and in Document Types.
The problem is that on my customer’s iPad Air 2, these files appear greyed out in the dialog so he cannot import them. On my iPad Air 2 they are not greyed out, and I can import them successfully. We can both see this when using iCloud Drive and Dropbox as file providers.
What could be different on our devices? My customer installs the app as an internal tester via TestFlight.
Also, are file extensions in the Import UTIs case-sensitive? I would not think so.
Then I wonder if I should add a document type icon or not. According to the reference the icon is not required.
iCloud Documents is enabled:
Here is the code that presents the UIDocumentPickerViewController:
let utis = [String](PlaylistItem.utiToType.keys)
let viewController = UIDocumentPickerViewController(documentTypes: utis, in: .import)
viewController.delegate = self
viewController.modalPresentationStyle = .formSheet
if #available(iOS 11, *) {
viewController.allowsMultipleSelection = true
}
self.present(viewController, animated: AppDelegate.isAnimationsEnabled, completion: nil)
with this definition of UTIs in the PlaylistItem class:
enum FileType: Int {
case other
case mid
case mp3
case m4a
case aiff // AIFF audio recording
case wave
case turboMidi // purchased MIDI file
case style
}
static let kUTTypeStyleYamaha = "com.turboreini.style" // matches document types in Info.plist
static let utiToType: [String: FileType] = [
kUTTypeMPEG4Audio as String: .m4a,
kUTTypeMP3 as String: .mp3,
kUTTypeMIDIAudio as String: .mid,
PlaylistItem.kUTTypeStyleYamaha: .style
]
This is the Document Type as captured from Xcode 9:
… and the Imported UTIs section:
Note that I picked an arbitrary identifier for style files because I couldn’t find an official one on the internet.
This issue drives me crazy. I cannot find anything wrong.
I wonder if it depends on other installed apps.
In addition to importing .sty files, the app also creates its own custom files with their own extension. For this I have defined an Exported UTI. It works fine, no files are greyed out. But the solution to importing .sty files should not be to move the entry from Imported to Exported UTIs. My app is only a “viewer” to .sty files.
Any ideas on this case would be much appreciated.
It turned out that on my customer’s iPad another app was installed that presumably also had its own UTI for the .sty extension. Once I installed it on my iPad I could reproduce the problem.
Unfortunately I was not able yet to peek into that app’s Info.plist to confirm.
However, thanks to Cocoanetics I found an API that allowed me to query the system:
let pathExt = "sty"
if let utiArray = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, pathExt as NSString, nil)?.takeRetainedValue() as? [String] {
print("Have UTIs for .\(pathExt):")
for uti in utiArray {
if let dict = UTTypeCopyDeclaration(uti as NSString)?.takeUnretainedValue() as? [String: Any] {
print("\(uti) = \(dict)")
}
}
}
This code would show me the declared UTIs from all the apps installed on my iPad, based on the file extension. And indeed the other app showed up. But I still could not see if it was declared as an exported or imported UTI.
The workaround is to uninstall the conflicting app.
To me this is a flaw in the UTI system. I will try and contact the makers of .sty files (MIDI styles, not LaTeX files), to ask them for a proper UTI which all developers should conform to.
It seems a global registry of UTIs is needed. Apple has already created a collection but only the most popular types are covered.

Missing "Edit" button for Browse other locations like to enable Dropbox, Google drive & Etc in UIDocumentPickerViewController implementtion

Missing Edit button for Access/Disable another cloud drives
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:types inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
//documentPicker.editButtonItem.enabled = true;
//documentPicker.modalPresentationStyle =UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
Can anyone suggest to me how to enable an Edit button on the top right side and do an Enable/Disable for other drives such as: (Dropbox, Google Drive, iCloud) from a list?(Those drives are already installed in my device).
Also how could I access only Excel and Google sheets files from drives?
Thank you in advance.
Adding an edit button is not possible. Please file a bug if you need it. The user can edit the list in the Files app and this will be reflected in your picker.
For the second question, you can set the UTIs of the files the user can select in the initializer of the picker (your « types# argument)

Resources