UIDocumentPickerViewController - open Folder by directoryURL - ios

In short: I am currently working on an app with which you can scan a QR code. The QR code contains a path to a folder in Apple's own "Files" app. And in this folder I would like to view the PDF files and open them.
My problem: I want to access a PDF file in a folder just to open the PDF file. When opening the UIDocumentPickerViewController, I want to open the target folder directly in which the file is stored. With the directoryURL property I try to enter the target URL directly so that it is taken over by the DocumentPickerViewController, but this only opens the last directory. If I change the initializer of the Document PickerView to "for Opening ContentTypes: [.folder]", then it jumps to the right folder, but I can't see any PDF files because it only shows me the folder files. BUT! : When I test my code via the simulator, my function works. And when I run the App on my iPhone, the DomumentPickerView keeps jumping to the last directory. According to the Apple documentation about the property: "directoryURL" it says: "Set this property to specify the starting directory for the document picker. This property defaults to nil. If you specify a value, the document picker tries to start at the specified directory. Otherwise, it starts with the last directory chosen by the user.” But why does it show me the right folder when I search for content type [.folder] and when I search for content type [.pdf] only the last directory.
HEEEELP
struct DocumentPicker : UIViewControllerRepresentable {
#Binding var folderURL : URL?
#Binding var documentURL : URL?
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf], asCopy: true)
documentPicker.delegate = context.coordinator
if let url = folderURL {
documentPicker.directoryURL = url
}
return documentPicker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {
print("Show DocumentPickerViewController")
}
}

Don’t use type .folder ; this is to get access to a whole folder and is not implemented by many file providers. Use the actual type of the file you want to open (pdf) and populate the starting directory with the URL of the directory, not the URL of the PDF file. Also keep in mind that some file providers populate their tree lazily, so the folder is not guaranteed to exist the first time you ask for it. The actual file paths may also be device specific (some file providers use UUIDs) so sharing an URL with a QR code across devices may not work.

Related

Cannot access/import contents of iCloud file picked in iOS 11 document browser

Besides opening and saving its own custom documents, my iOS 11 document browser-based app should be able to import an appropriately formatted text file and convert it into a document of its own.
However, when picking a text file in the document browser, the app is able to access its contents (and then perform the conversion) only if the selected file is in local storage, but fails to access the contents when the file is stored in iCloud.
A simplified, commented version of the code I am using for the documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) delegate method is provided below:
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) {
guard let sourceURL = documentURLs.first else { return }
// Check extension to verify if it is a custom document
if sourceURL.pathExtension == "mydoc" {
// If it is a custom document, we present it directly
// (the presentDocument method is a standard implementation from Apple docs)
presentDocument(at: sourceURL)
} else {
// Otherwise, we suppose the file is a text file to be imported
// We first create a new document, then try to import the contents of the text file
let url = FileManager().temporaryDirectory.appendingPathComponent("New Document").appendingPathExtension("mydoc")
let doc = MyDocument(fileURL: url)
do {
let textFileContents = try String(contentsOf: sourceURL)
// It works fine if the text file is in local storage
// Here the document model is updated by converting the contents of the text file
// (code omitted, it is actually a method in the MyDocument class)
// ...
} catch {
// Produce error message: cannot access text file
// The error message is always produced if the text file is in iCloud storage!
}
// Save and close the document with standard code, e.g.:
doc.save(to: url, for: .forCreating) { (saveSuccess) in
guard saveSuccess else { return }
doc.close(completionHandler: { (closeSuccess) in
guard closeSuccess else { return }
})
// Reveal and import the document
self.revealDocument(at: url, importIfNeeded: true) { (revealedDocumentURL, error) in
if let error = error {
// Handle the error appropriately
return
}
// Present the Document View Controller for the revealed URL
self.presentDocument(at: revealedDocumentURL!)
}
}
}
The info.plist file declares both the custom document and public.text as Document Types (with the appropriate Role and Handler rank), and the custom document as Exported UTI. If the file picked is a custom document, everything works fine, no matter if the file is local or in iCloud.
As the importing works when the file is in local storage, I thought it may be an issue of iCloud permissions, even if Apple docs for UIDocumentBrowserViewController state:
The system automatically handles access to iCloud for you; you don't
need to enable your app’s iCloud capabilities.
However, trying to add iCloud capabilities to my app's entitlements did not solve the problem (and actually made it worse, in the sense that the exact same code was now saving newly created documents in the default iCloud container for the app, which is not the same iCloud Drive location used by the document browser, so that all documents became "invisible" to the browser - but I digress...).
Another "fun" element is that I also implemented a UIDocumentPickerViewController in the app to import additional content from a text file into an already open custom document. The code I use is basically the same as above... and it works perfectly, independently of whether the text file is in local storage or iCloud! This could reinforce the view that the problem is linked to a specific permission issue with UIDocumentBrowserViewController.
So, in summary: what could I do to access the content of text files stored in iCloud and convert them to custom documents of my app? Thank you in advance for any suggestions (and please be gentle if there are issues with the formulation of the question - this is my very first stackoverflow question after months of lurking!).
I’m not 100% sure but I believe you need to call startAccessingSecurityScopedResource on that URL to consume the sandbox extension vended to you by the document browser. Don’t forget to call stopAccessing once you are done in order not to leak kernel resources. Also, if you are not really editing that document, you might be looking for a UIDocumentPickerViewController instead? It’s weird for the user to open a file and then end up editing another. Isn’t it a better UX to start a new document, and then import the text file into it with the picker?
small drop-in snippet: (Swift 5.x)
public func documentBrowser(_ controller: UIDocumentBrowserViewController,
didPickDocumentsAt documentURLs: [URL]) {
dismiss(animated: true, completion: nil)
self.importAll(urls: documentURLs)
}
final func importAll(urls: [URL]) {
//TODO: use timer/loop/async stuff..
// for multiple files..
urls.forEach { (url: URL) in
let secured = url.startAccessingSecurityScopedResource()
= myImportInSessionFrom(url: url)
if secured { url.stopAccessingSecurityScopedResource() }
}
}

iOS reading from a file

I'm trying to get my iPhone app to load text from a file into a string array, with 1 line from the file per array element.
I've created an input file as a text file using sublime text. I dragged the file (which is located inside of a folder inside of my project directory) into xCode into a folder in the same location in the project heirarchy.
I also tried adding it as a bundle (by copying the folder and renaming it with the .bundle extension), to no avail. Currently, my app has the file in 2 places (Obviously I plan to delete the unneeded version, but I'm not sure how this will work so I've left it for now).
I've written a function that I want to read my file, and assemble its contents into an array:
func readFromFile(filename: String) -> [String]? {
guard let theFile = Bundle.main.path( forResource: fileName, ofType: "txt") else {
return nil // ALWAYS returns nil here: Seems 'filename' can't be found?????
}
do { // Extract the file contents, and return them as a split string array
let fileContents = try String(contentsOfFile: theFile)
return fileContents.components(separatedBy: "\n")
} catch _ as NSError {
return nil
}
}
As it stands, the function always returns nil at the location commented in the code.
I've been working on this for ~6hrs (and tried every suggestion I could find on StackOverflow, google etc) and I'm just getting more and more confused by the differences between the various versions of Swift and intricacies of iOS development. I can't seem to find a consistent answer anywhere. I've checked the apple documentation but it's too high level with no example code for me to understand at my swift beginner level.
I also tried naming the file with a ".txt" extension but that didn't help either.
The file must certainly be named alert01.txt if you are going to refer to it as forResource: "alert01", ofType: "txt".
Loading from a bundle will not work. The file needs to be part of your project as shown in the first entry.
However, your code is not going to work because you have created a folder reference. That means the folder PanicAlertFiles is being copied with all its contents into your bundle. Your code will need to dive into that folder in order to retrieve your file. Use path(forResource:ofType:inDirectory:) to do that, or (if you don't want to have to code the file name explicitly) get the folder and then use the FileManager to examine its contents.

Transfering a PDF File via Action Extension from Safari to App

I want to use an action extension to transfer a PDF file from Safari to my host application.
If the user opens a PDF file in Safari, the extension should copy the local file to my app.
Somehow the NSItemProvider only has Items conforming to the type 'public.url'.
This is the web-URL to the file, but not the local filepath that I want.
Can someone point me in the right direction here?
I came across the same scenario yesterday. What I did was
let url: URL = (result as? URL)!
let path: String = url.path
if path.hasSuffix(".pdf") {
//download the data from url and write to a pdf file
}

Permanent file directory in Xcode

In the documents directory for my app, I have a file Q1.dat which I use in my program. I access it through the path:
func createArrayPath () -> String? {
if let docsPath: String = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last {
return ((docsPath as NSString).stringByAppendingPathComponent("Q1") as NSString).stringByAppendingPathExtension("dat")
}
return nil
}
When I run the program on a different device however, a different documents directory is created, and the file is no longer present. Is there a place I can put the file so that I can always access it no matter what device I am running on?
The Documents directory is meant for data created by the user.
It sounds like you want to bundle a resource with your application. You can do this by simply dragging the file into Xcode, which will automatically set it up to be copied into your app bundle, and you can find it using URLForResource(_:withExtension:).
If the file is very large, you might want it to be downloaded separately from the main app. For this you can use the new On-Demand Resources feature.

retrieving and writing a file in iOS

I looked at many SO question where the user wanted to access a document in his app directory. I am new to all of this and just want some clarification on the matter. I am building an open-source content blocker for iOS 9.
You can see the file tree. I wonder if it is possible to access the file called blockerList.json which is in the folder Adblock Content Blocker. My goal was to write the json file based on what the user want to build. I need to be able to modify the content of this file. Is this possible or should I stop trying and leave it like that?
Thanks for any help.
The app bundle is readonly as #dan said. So you have to copy the file from bundle into the Document directory then you able to modify the file at anytime.
Copy file into document like this:
class func copyFile(fileName: String) {
let destPath: String = getPathInDocument(fileName)
var fromPath = NSBundle.mainBundle().pathForResource("blockerList", ofType: "json")!
var fileManager = NSFileManager.defaultManager()
if !fileManager.fileExistsAtPath(destPath) {
fileManager.copyItemAtPath(fromPath, toPath: destPath, error: nil)
} else {
println("The file allready exists at path:\(destPath)")
}
}
class func getPathInDocument(fileName: String) -> String {
return NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent(fileName)
}
Hope this will help you!
Note : You have readonly permission for application's bundle file.
Firstly copy file in NSDocumentDirectory with backup attribute. Use refrerence copy file to document directory iphone and for How do I prevent files from being backed up to iCloud and iTunes?
Check references ImportingJSONFile and Y6JSONFileManager-iOS to read and write changes in JSON File.

Resources