I have an iOS app and I need to be able to pick a file on my iCloud Drive, modify the file, and save the modified file with a new extension. I've tried lots of things but I still can't write the new file. Here is my latest code:
documentPickerViewController = DocumentPickerViewController(documentTypes: ["public.item"], in: .open)
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let fileURL = urls[0]
do {
// Read the file
_ = fileURL.startAccessingSecurityScopedResource()
let fileData = try Data(contentsOf: fileURL, options: .uncached)
fileURL.stopAccessingSecurityScopedResource()
// write the file
let fileCopyURL = fileURL.appendingPathExtension("copy")
_ = fileCopyURL.startAccessingSecurityScopedResource()
try fileData.write(to: fileCopyURL)
fileCopyURL.stopAccessingSecurityScopedResource()
}
catch {
print(error.localizedDescription)
}
}
When I pick a file on my iCloud Drive I get the following error:
You don’t have permission to save the file “TestFile.txt.copy” in the folder “Test Files”.
How can a save the modified file?
First, I think you should make sure that you have the correct entitlements/permissions to write to iCloud Drive:
If this still doesn't work, try updating the CFBundleVersion or Build Number of your app and make sure that your Bundle Identifier (in the first section of Signing & Capabilities) is correctly registered on your Apple Developer account.
According to your code, what you're trying to accomplish is feasible but the issue comes from elsewhere.
Let me know if you have an issue following these steps. Good Luck!
Related
I am looking for an alternative export menu other then UIActivityViewController for a Mac Catalyst app. While this works, the user can not choose where they want to save the file (the file is a JSON file of all the items in a list) and I would like the user to be able to choose the directory they want to save the JSON to. I have tried the following code but it gives the error "Error Domain=NSCocoaErrorDomain Code=260 'The file 'name.json' couldn’t be opened because there is no such file'" when you try to save a file.
The Code:
let fileManager = FileManager.default
do {
let fileURL2 = fileManager.temporaryDirectory.appendingPathComponent("\(detailedList.lname!).json")
// Write the data out into the file
try jsonData.write(to: fileURL2)
// Present the save controller. We've set it to `exportToService` in order
// to export the data -- OLD COMMENT
let controller = UIDocumentPickerViewController(url: fileURL2, in: UIDocumentPickerMode.exportToService)
present(controller, animated: true) {
// Once we're done, delete the temporary file
try? fileManager.removeItem(at: fileURL2)
}
} catch {
print("error creating file")
}
I have tried Googling other ways or ways to get this to work but I cannot find anything that will work on Mac Catalyst. I know you can do this because I have seen other apps and examples do it but nothing works for me. So what would be a possible alternative way of doing this or a solution to this code?
The problem is that you are removing the file you wish to save before the user has a chance to choose where they want to save it.
The completion handler where you call try? fileManager.removeItem(at: fileURL2) is called as soon as the document picker is displayed.
The proper solution is to delete the file in the UIDocumentPickerDelegate methods, not when the picker is presented.
I'm trying to move a file from a URL to another, however I get an error with code 513. I understand that this is a NSFileWriteNoPermissionError. I don't see how this possible considering I created the folder in the first place.
//self.video!.folderURL.path = /var/mobile/Containers/Data/Application/066BDB03-FD47-48D8-B6F8-932AFB174DF7/Documents/AV/769c504203024bae95b47d78d8fe9029
try? fileManager.createDirectory(atPath: self.video!.folderURL.path, withIntermediateDirectories: true, attributes: nil)
// Update permission for AV folder
do {
let parentDirectoryPath = self.video!.folderURL.deletingLastPathComponent().path
let result = try fileManager.setAttributes([FileAttributeKey.posixPermissions: 0o777], ofItemAtPath: parentDirectoryPath)
print(result)
} catch {
print("Error = \(error)")
}
// sourceURL = file:///private/var/mobile/Containers/Data/PluginKitPlugin/50D8B8DB-19D3-4DD6-93DD-55F37CF87EA7/tmp/trim.6D37DFD2-5F27-4AB9-B478-5FED8AA6ABD7.MOV
// self.url = file:///var/mobile/Containers/Data/Application/066BDB03-FD47-48D8-B6F8-932AFB174DF7/Documents/AV/0b0b6803e891780850152eeab450a2ae.mov
do {
try fileManager.moveItem(at: sourceURL, to: self.url)
} catch {
QLogTools.logError("Error moving video clip file \(error)")
}
Error
Error Domain=NSCocoaErrorDomain Code=513 "“trim.90A10B33-B884-419F-BAD9-65583531C3C3.MOV” couldn’t be moved because you don’t have permission to access “AV”." UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Containers/Data/PluginKitPlugin/0849234B-837C-43ED-BEDD-DE4F79E7CE96/tmp/trim.90A10B33-B884-419F-BAD9-65583531C3C3.MOV, NSUserStringVariant=(
Move
I tried altering the permission of the folder I created in the first place to 0o777, but when I print out its attributes it returns 511 as its permissions.
P.S:This only happens on iOS 13.
I solved my issue, but it may be different to what you were experiencing: I still had issues when changing to copyItem. I'm adding my findings here as an answer in case anyone else stumbles upon this question as I did and it helps them out.
My app loads custom files that can be downloaded from my website. With iOS 13's download manager, these are copied to the Downloads folder first, and can then be opened by my app. Yet my app was having permission errors when attempting to access these files in the same way as iOS 12.
This answer to a different question clued me in. I need to call startAccessingSecurityScopedResource() and stopAccessingSecurityScopedResource() as follows:
// source and destination are URLs
if source.startAccessingSecurityScopedResource() {
try FileManager.default.moveItem(at: source, to: destination)
}
source.stopAccessingSecurityScopedResource()
The Apple documentation doesn't really suggest that this is new with iOS 13 but I think just the different approach to file downloading means sandboxing considerations need to be made.
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() }
}
}
In iOS 9+ I get a nil on any attempt to read from file. The file in this case is a image file path.
using
NSData(contentsOfFile: stringpath, options: NSDataReadingOptions.DataReadingUncached)
or
NSData(contentsOfFile: stringpath)
Actions:
I have removed the "file://" from the path and it now has a permissions issue.
Error Domain=NSCocoaErrorDomain Code=257 "The file “IMG_0048.JPG” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/var/mobile/Media/DCIM/100APPLE/IMG_0048.JPG, NSUnderlyingError=0x13f978f50 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
I have added the NSAllowArbitraryLoads and set it to true.
I have tried to look for the file myself using "NSSearchPathDirectory" however the paths do not match in any way
I encountered this error because I was attempting to access multiple files in the same block. The fix that worked for me was changing the code structure such that each file url was obtained, then read from, before attempting to get the next file url.
You are most likely getting this error, because iOS apps only have access to files within its sandbox. See Apple documentation on file systems for details.
In your Application, you don't have permission to access the file of /var/mobile/Media/DCIM/100APPLE/IMG_0048.JPG, because of Sandboxing.
So, whatever you do, you can't initialize NSData or UIImage with the file path. But you can access the file of /var/mobile/Media/DCIM/100APPLE/xxx.mov with AVURLAsset. In my application, I extracted the data rather than URL from gallery by Photos kit and initialized UIImage with the data.
PHImageManager.default().requestImageData(
for: assetObject!, options: options,
resultHandler: {
data, _, _, _ in
if data != nil {
self.assetUrl = movieMaker.createMovieFrom(imageData: data!, duration: Int(CXPreparetValue.imageDuration))
}
})
it works for me! If you have other opinions, please tell me.
In my case, the file permissions were too restrictive, so I couldn't read the file.
Adding read+write permissions to the file before accessing it solved it.
do {
// Retrieve any existing attributes
var attrs = try FileManager.default.attributesOfItem(atPath: stringpath)
let existing = (attrs as NSDictionary).filePosixPermissions()
// Set the read+write value in the attributes dict
attrs[.posixPermissions] = existing | 0b110000000
// Update attributes
try FileManager.default.setAttributes(attrs, ofItemAtPath: stringpath)
// Read data from file
let data = try Data(contentsOf: URL(fileURLWithPath: stringpath, isDirectory: false), options: .uncached)
print("success: \(data.count)")
} catch {
print(error)
}
That works if you're in a folder with enough permissions, as you can change the files permissions even if you didn't had read permission on the file previously. This solution was applied at https://github.com/ZipArchive/ZipArchive/issues/293.
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.