Trying to create a file using createFile API on iOS.
createFile(path, data, attr)
Document says,
Return Value:
true if the operation was successful or if the item already exists, otherwise false.
I wonder if the operation was not successful, how can I get the exact cause of the failure?. Expected some API like,
createFile(path, data, attr, &error)
Use Data.write(to:options:) instead, it throws.
I use FileManager.default.createDirectory to create folders. I create Data that I want to write, and then I use Data.write to save it to disk.
var docDir = URL()
do {
docDir = try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
} catch {
fatalError("\(error)")
}
let subDir = docDir.appendPathComponents("mySubDir")
do {
try FileManager.default.createDirectory(
at: subdir,
withIntermediateDirectories: true,
attributes: nil
)
} catch {
if (error as NSError).code == 516 {
// "The file “x” couldn’t be saved in the folder “y” because a file with the same name already exists."
// This is OK.
} else {
fatalError("\(error)")
}
}
let filePath = subdir.appendingPathComponent("yourFileName")
let object:Codable = Whatever()
let data = try! JSONEncoder().encode(object)
do {
try data.write(to: filePath)
} catch {
fatalError("\(error)")
}
I do not know why Apple devs chose FileManager.createFile to be different. It seems strange to me.
Related
I have an issues in changing the file path at every launch of the app.
I have a file("AppConstant.json") in application bundle, and this file I need to copy into application document directory. I am successfully saving "AppConstant.json" file inside the created user folder "MyFolder" on Document directory.
But the problem is when I relaunch the application second time, it's not showing the same path. Also I am using relativepath, but still it not getting.
here is the code
// calling the directory
let stringAppConstant = copyFileFromBundleToDocumentDirectory(resourceFile: "AppConstant", resourceExtension: "json")
// saving or get exit file path
func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) -> String
{
var stringURLPath = "Error_URLPath"
let fileManager = FileManager.default
let docURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
let fileName = "\(resourceFile).\(resourceExtension)"
guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
return stringURLPath
}
if !fileManager.fileExists(atPath: newDestPath.path) {
do {
try fileManager.createDirectory(atPath: newDestPath.path,withIntermediateDirectories: true, attributes: nil)
print("Created folder successfully in :::", newDestPath.path)
} catch {
print("Error in creating folder :::",error.localizedDescription);
}
}
else {
print("Folder is already exist!")
}
if fileManager.fileExists(atPath: fullDestPath.path) {
print("File is exist in ::: \(fullDestPath.path)")
stringURLPath = fullDestPath.path
}
else {
do {
try fileManager.copyItem(atPath: sourcePath, toPath: fullDestPath.path)
print("Saved file successfully in :::", fullDestPath.path)
stringURLPath = fullDestPath.path
} catch {
print("Error in creating file ::: \(error.localizedDescription)")
}
}
return stringURLPath
}
Please help me, where I need to save the path in Sandbox. Is this right way what I implemented.
I am running in device and simulator, both path are different while relaunch
this is the path for first time launch:
/var/mobile/Containers/Data/Application/81B568A7-0932-4C3E-91EB-9DD62416DFE8/Documents/MyFolder/AppConstant.json
relaunch the application I am getting new path:
/var/mobile/Containers/Data/Application/3DAABAC3-0DF5-415B-82A5-72B204311904/Documents/MyFolder/AppConstant.json
NOTE: I create a sample project and I use this same code and it's working. But in existing project it's not working. I am using the same bundle id and profile only for both sample and project. Checked the file added reference, settings, version all are same.
Any idea?
The behavior that the container path changes periodically is normal.
These lines
let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
let fileName = "\(resourceFile).\(resourceExtension)"
guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
return stringURLPath
}
contain a lot of mistakes
URL(string is the wrong API for file paths, it's URL(fileURLWithPath).
The second parameter of path(forResource:ofType:) must not have a leading dot.
The API absoluteString is wrong as parameter of URL(fileURLWithPath
Not a real mistake but don't use NSURL in Swift.
It's highly recommended to use always the URL related API to concatenate paths and get the documents folder from FileManager. Further it's good practice to make the method throw the real error rather than returning a meaningless literal string. And NSSearchPathForDirectoriesInDomains is outdated and should not be used in Swift.
func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) throws -> URL
{
let sourceURL = Bundle.main.url(forResource: resourceFile, withExtension: resourceExtension)!
let fileManager = FileManager.default
let destFolderURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("MyFolder")
let fullDestURL = destFolderURL.appendingPathComponent(resourceFile).appendingPathExtension(resourceExtension)
if !fileManager.fileExists(atPath: destFolderURL.path) {
try fileManager.createDirectory(at: destFolderURL, withIntermediateDirectories: true, attributes: nil)
print("Created folder successfully in :::", destFolderURL.path)
try fileManager.copyItem(at: sourceURL, to: fullDestURL)
print("Saved file successfully in :::", fullDestURL.path)
} else {
print("Folder already exists!")
if fileManager.fileExists(atPath: fullDestURL.path) {
print("File exists in ::: \(fullDestURL.path)")
} else {
try fileManager.copyItem(at: sourceURL, to: fullDestURL)
print("Saved file successfully in :::", fullDestURL.path)
}
}
return fullDestURL
}
Edit 1:
Hi I created the new project and use the same code I posted in main, and it's working. But in the real project it not working.
Not sure what exactly going on in your project, try to debug it. It's part of development as well. :)
If you are in hurry to fix this issue in this weekend try to use the following code snippet.
// collect data from bundle
let constFileURL = Bundle.main.url(forResource: "AppConst", withExtension: "json")!
let data = try! Data(contentsOf: constFileURL)
// try to write data in document directory
do {
let constFileURL = try saveFileInDocumentDirectory(filePath: "MyFolder/AppConst.json", data: data)
// use your `constFileURL`
} catch (let error as FileOperationError) {
switch error {
case .fileAlreadyExists(let url):
let data = try! Data(contentsOf: url)
print(String(data: data, encoding: .utf8))
case .IOError(let error):
print("IO Error \(error)")
}
} catch {
print("Unknown Error \(error)")
}
// Helpers
enum FileOperationError: Error {
case fileAlreadyExists(url: URL)
case IOError(Error)
}
func saveFileInDocumentDirectory(filePath: String, data: Data) throws -> URL {
// final destination path
let destURLPath = fullURLPathOf(filePath, relativeTo: .documentDirectory)
// check for file's existance and throw error if found
guard FileManager.default.fileExists(atPath: destURLPath.path) == false else {
throw FileOperationError.fileAlreadyExists(url: destURLPath)
}
// Create Intermidiate Folders
let intermidiateDicPath = destURLPath.deletingLastPathComponent()
if FileManager.default.fileExists(atPath: intermidiateDicPath.path) == false {
do {
try FileManager.default.createDirectory(at: intermidiateDicPath, withIntermediateDirectories: true, attributes: nil)
} catch {
throw FileOperationError.IOError(error)
}
}
// File Writing
do {
try data.write(to: destURLPath, options: .atomic)
} catch {
throw FileOperationError.IOError(error)
}
return destURLPath
}
func fullURLPathOf(_ relativePath: String, relativeTo dic:FileManager.SearchPathDirectory ) -> URL {
return FileManager.default.urls(for: dic, in: .userDomainMask).first!.appendingPathComponent(relativePath)
}
Original Answer
Why don't you just return "MyFolder/\(fileName)" on successful file operation? If you need to access the path later you can always do that using FileManager APIs.
let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let constFilePath = docDir.appendingPathComponent("MyFolder/\(fileName)")
// Access const file data
do {
let fileData = try Data(contentsOf: constFilePath)
// Use you data for any further checking
} catch {
// Error in reading file data
print("Error in file data access : \(error)")
}
I'm retrieving a plist file, updating it, and writing it to disk.
1) Retrieval
func pListURL() -> URL? {
guard let result = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("somePlist.plist") else { return nil }
return result
}
2) Update
var data: [String: Int] = [:]
if let url = pListURL() {
do {
let dataContent = try Data(contentsOf: url)
if let dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
data = dict
}
} catch {
print(error)
}
}
// update code
3) Write
if let path = pListURL() {
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: data, format: .xml, options: 0)
try plistData.write(to: path)
} catch {
print(error)
}
}
The odd thing is I get an error that says:
The file “somePlist.plist” couldn’t be opened because there is no such file.
even though when I check the plist, it's actually properly created and updated as it should be. As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.
As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.
No, it means the directory is created but the file is not created.
In the update part ignore the couldn’t be opened error and write the (new) data to disk or check if the file exists with fileExists(atPath.
And you can declare the return value in pListURL as non-optional. It's guaranteed that the folder Documents exists
func pListURL() -> URL {
return try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somePlist.plist")
}
Update:
These are reliable versions of update and write
func update(dictionary: [String:Int]) {
let url = pListURL()
guard FileManager.default.fileExists(atPath: url.path) else { write(dictionary: dictionary); return }
do {
let dataContent = try Data(contentsOf: url)
if var dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
for (key, value) in dictionary {
dict.updateValue(value, forKey: key)
}
write(dictionary: dict)
} else {
write(dictionary: dictionary)
}
} catch {
print(error)
}
}
func write(dictionary: [String:Int]) {
let url = pListURL()
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try plistData.write(to: url)
} catch {
print(error)
}
}
As the titel rleaves. I'm facing the issue, that I have no clue how to save the csv file to the document of the devices so I can get accesse outside my app. Can any body help?
So far I tried this code.
func creatCSV() -> Void {
let fileName = "Tasks.csv"
do {
guard let path = try? FileManager.default.url(
for: .documentDirectory,
in: .allDomainsMask,
appropriateFor: nil,
create: false
).appendingPathComponent(fileName) as NSURL else {
return
}
var csvText = "Date,Task Name,Time Started,Time Ended\n"
for task in taskArr {
let newLine = "\(task.date),\(task.name),\(task.startTime),\(task.endTime)\n"
csvText.append(newLine)
}
try csvText.write(to: path as URL, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print("\(error)")
}
print("not found")
}
But I have no clue where I finde the file after I called the funtion.
Long story short: You have to set in "info.plist" "Application supports iTunes file sharing" to "YES"
I want to access the Sqlite database backing CoreData, make a copy of it, and send it to an endpoint for remote troubleshooting. Is this possible?
My solution...
let fileManager = FileManager.default
let libraryURL = NSPersistentContainer.defaultDirectoryURL()
guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
// handle error...
return
}
// creating a folder in documents for ease of zipping and later removal
let dbURL = documentsURL.appendingPathComponent("db")
do {
try fileManager.createDirectory(at: dbURL, withIntermediateDirectories: true, attributes: nil)
} catch {
// handle error...
return
}
do {
let fileURLs = try fileManager.contentsOfDirectory(at: libraryURL, includingPropertiesForKeys: nil, options: [])
for fileURL in fileURLs.filter({ $0.absoluteString.contains("<NAME_OF_CORE_DATA_STORE>") }) {
let fileName = fileURL.lastPathComponent
do {
try fileManager.copyItem(at: fileURL, to: URL(string: "\(documentsURL.absoluteString)db/\(fileName)")!)
} catch {
// handle error...
}
}
} catch {
// handle error...
}
// finally, do something with the file (I zip and send to an endpoint)
Yes, possible. Just don’t try and treat it as writable outside the core data ecosystem. Readable, no worries.
Copy all the parts from where it’s written to. The default SQLite mode is journaled so you will get a wal file written as a counterpart.
You can interrogate the NSPersistentStore.url for the write location of your database
I am trying to save a file inside a subfolder in Documents directory but it won't save. I can't seem to find what's wrong. Here is what I've tried:
if let audioUrl = NSURL(string: "http://pillar.foundationu.com/wp-content/plugins/pillar-data-sync/php/htmlBreakdownResult.json") {
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
do {
let directoryContents = try NSFileManager.defaultManager().contentsOfDirectoryAtURL(documentsUrl, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions())
print(directoryContents[0].path!)
if NSFileManager().fileExistsAtPath(String(audioUrl.path!)) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
if let myAudioDataFromUrl = NSData(contentsOfURL: audioUrl){
// after downloading your data you need to save it to your destination url
if myAudioDataFromUrl.writeToURL(NSURL(fileURLWithPath: directoryContents[0].path!), atomically: true) {
print("file saved")
} else {
print("error saving file")
}
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
Here is where I want to save the file:
/Users/rendell/Library/Developer/CoreSimulator/Devices/5A052CC5-FD34-44FD-B060-24D6F1970860/data/Containers/Data/Application/37753B0B-FAB0-478D-A7F8-98E3039D07DD/Documents/MyFolder2
But it keeps on giving me "error saving file".
First of all, use always writeToURL:options:error to get a (more) descriptive error message.
The issue is quite simple: You forgot to provide a file name.
Technically you're going to overwrite an existing folder with data. That's not possible.