I use this method to show an UIDocumentPicker:
func showDocumentPicker(){
let docPicker = UIDocumentPickerViewController(documentTypes: ["public.composite-content "], inMode: UIDocumentPickerMode.Import)
docPicker.delegate = self
docPicker.modalPresentationStyle = UIModalPresentationStyle.FullScreen
self.presentViewController(docPicker, animated: true, completion: nil)
}
The UIDocumentPicker gets displayed nicely, but it always shows
No Document, Documents in iCloud Drive are not available because the iCloud Documents & Data setting is disabled
But when I check the iCloud Status, iCloud Drive is enabled! (My App even shows up in the settings there, also enabled!)
This happens in Simulator and on the device (via a Prerelease distributed by Apple TestFlight)
This error can be caused due to invalid UTI-constants:
Make sure to double-check the UTIs you pass to the documentTypes parameter. In this case, note the blank space in the public.composite-content string
Related
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)
I have implemented an AppGroup in my app in preparation for sharing data with another app. I have successfully moved files to that App Group from the default app documents directory.
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.xxx.mydata")! as NSURL
Now I would like to select from the files in that container, using UIDocumentPickerViewController. In iOS 13,I should be able to set which directory the document picker starts in. My documentPicker looks like this:
#IBAction func fileAction(_ sender: UIButton)
{
// open a document picker, select a file
let importFileMenu = UIDocumentPickerViewController(documentTypes: ["public.data"],
in: UIDocumentPickerMode.import)
importFileMenu.delegate = self
if #available(iOS 13.0, *) {
print("File iOS 13+")
importFileMenu.directoryURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.xxx.mydata")!
} else {
// Fallback on earlier versions
print("File iOS <=12")
}
importFileMenu.modalPresentationStyle = .formSheet
self.present(importFileMenu, animated: true, completion: nil)
}
When I run the app, it acts as it did before iOS13, opening in the default app documents directory, and the App Group is not shown as a possibility for selection. The print statement shows "File iOS 13+".
Am I missing permissions to read from that container, or is there something else that I've missed?
Thanks!
No, sorry, it can't be done. Apple says that selecting from an AppGroup is not what UIDocumentPickerViewController is supposed to do. I spent one of my "Apple Developer Tech Support" uses on this, and that was their answer. I gave up on that for now, and went a different direction. You should be able to build your own list of files in the AppGroup and select them, just not by using UIDocumentPickerViewController.
I have a UIDocumentPickerViewController with a filetype of "public.folder" (I've also tried kUTTypeFolder), where a user can pick a default directory for files to be saved. It pulls up the correct UI for selecting folders, however, all third-party providers (Google Drive, Dropbox, etc.) are all grayed out and can't be selected. I can select iCloud Drive and On My iPhone just fine.
Here's my function for showing the controller:
#IBAction func pickDefaultDirectory(_ sender: Any) {
let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.folder"], in: .open)
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
This is what every "pick folders via UIDocumentPickerViewController" article I've found says to do, but I've had no luck. Does anyone know why this is happening? Am I just forgetting something in there?
According to Dropbox developers, this isn't supported at the moment.
The Dropbox document picker doesn't support opening folders, but I'll pass this along as a feature request. I can't promise if or when that might be implemented though.
Dropboxforum
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.
I have a UISwitch in my app for users to switch on/off iCloud. I want to achieve the following: When a user turns off iCloud, all contents in the ubiquitous container will be removed and copied to a local directory as backups. However, as soon as the files have been removed from the ubiquitous container, copies on the iCloud server are also removed. This basically clear everything on iCloud.
I have the following questions:
How can files on the ubiquitous container be removed without affecting copies on the iCloud server?
What is the best or standard practice to remove files from the ubiquitous container before disabling iCloud?
Can iCloud be disabled at all after it has been initialised/enabled?
Thank you.
After reading Apple's documents and others suggestions, here is my understanding. I am not 100% sure if they are correct. Comments and corrections are most welcome:
Anything added to or removed from the ubiquity container will be synced with the iCloud server. The app has no control of this.
Once iCloud document storage has been enabled in Settings app by the user, it cannot be disabled by the app. The app's responsibility is to provide UI (assuming a UISwitch) to let user indicate where they want their documents synced with the iCloud for the app.
If the user turns off iCloud by turning off the UISwitch in the app (not in Settings), what the app should do is to stop querying metadata, stop listening to NSMetadataQueryDidUpdateNotification, and stop accessing files in the ubiquity container (as mentioned by crizzis above). If later the user turns iCloud on again, files already in the ubiquity container will be synced with iCloud automatically, and no manual merging should be needed unless unresolved conflicts occur.
Using evictUbiquitousItem(at url:)
See point #1. I'm not sure why you would want to do that, though. Can't you just stop accessing the local copies the second the switch is off?
I don't think it can be disabled programmatically. On a positive side, if you want to avail the users of a possibility to disable iCloud, it's already there. iCloud is supposed to be disabled via the Settings app, and all you really need to do is handle that fact within the app by listening to NSUbiquityIdentityDidChangeNotification
UPDATE
Amin Negm-Awad suggested that evictUbiquitousItem(at url:) forces a reload, and so the local copy is not permanently deleted. However, I've done a little testing just out of curiosity, and haven't found that to be the case. The following test:
func runTest(ubiURL: URL) {
self.query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
self.query.predicate = NSPredicate(format: "%K like '*'", NSMetadataItemFSNameKey)
NotificationCenter.default.addObserver(self, selector: #selector(self.metadataQueryDidUpdate(_:)), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: self.query)
self.query.start()
self.query.enableUpdates()
let fileURL = ubiURL.appendingPathComponent("Documents/file.txt")
FileManager.default.createFile(atPath: fileURL.path, contents: "Hello".data(using: .utf8))
do {
try FileManager.default.startDownloadingUbiquitousItem(at: fileURL)
} catch {
print("startDownloadingUbiquitousItem: \(error.localizedDescription)")
}
}
var updateCount = 0
func metadataQueryDidUpdate(_ notification: Notification) {
print("######################")
print("update #\(updateCount)")
for file in query.results as! [NSMetadataItem] {
guard let fileURL = file.value(forAttribute: NSMetadataItemURLKey) as? URL, let fileStatus = file.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String else {
print("Invalid item!")
return
}
if fileStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent {
print("URL to evict: \(fileURL)")
do {
try FileManager.default.evictUbiquitousItem(at: fileURL)
print("Eviction result: successful")
} catch {
print("evictUbiquitousItem: \(error.localizedDescription)")
}
}
print("File exists at URL: \(FileManager.default.fileExists(atPath: fileURL.path))")
}
updateCount = updateCount + 1
}
Yielded:
ubiURL is file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~example~blabla3/
######################
update #0
URL to evict: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~example~blabla3/Documents/file.txt
evictUbiquitousItem: The file “file.txt” couldn’t be saved in the folder “blabla”.
File exists at URL: true
######################
...
update #3
URL to evict: file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~example~blabla3/Documents/file.txt
Eviction result: successful
File exists at URL: true
######################
update #4
File exists at URL: false
(no further updates followed)
I don't think the remote file will end up on the device unless startDownloadingUbiquitousItemAtUrl: is called with the evicted file's URL. Not sure if this behavior can be relied upon, though.