I manually created a file amutha.txt in the documents folder. I tried to write data to that file. code which I used is
let string="Amuthapriya"
try string.write(to:fileName, atomically: true, encoding: String.Encoding.utf8)
This is executed correctly means having no exceptions or errors. But then When I open amutha.txt the file is empty. Why the string is not written in that file? What Mistake I am doing?
My code is:
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
#objc func buttonPressed(sender:UIButton) {
print(sender.titleLabel?.text)
let documentUrl:URL = getDocumentsDirectory()
let fileName=documentUrl.appendingPathComponent("priya.txt", isDirectory: false)
let filePath=fileName.path
print( FileManager.default.fileExists(atPath: filePath))
do {
let string="Amuthapriya"
try string.write(to:fileName, atomically: true, encoding: String.Encoding.utf8)
print("written successfully")
print("filePath: \(filePath)")
} catch {
}
}
Just try to print path of your file in console because you are watching wrong file and check that file if it is available there or not and check content
do {
let string="Amuthapriya"
try string.write(to:fileName, atomically: true, encoding: String.Encoding.utf8)
print("written successfully")
print("filePath: \(filePath)") // check file here
} catch {
}
I manually created a file amutha.txt in the documents folder. But then When I open amutha.txt the file is empty.
But the text is being written to priya.txt.
let fileName=documentUrl.appendingPathComponent("priya.txt", isDirectory: false)
So you are looking in the wrong file.
(Also do not change from file URL to file path string. Use file URL only.)
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 am wondering what the work-around is for downloading files with irregular filenames using Swift's FileManager. For example, downloading a file named "Hello/Goodbye" where the file path looks like:
let filePath = documentDirectory.appendingPathComponent("\(fileName).m4a")
will result in the file downloading to a folder inside documentDirectory named 'Hello' since filePath is "documentDirectory/Hello/Goodbye.m4a". Instead, I want the file to be downloaded under documentDirectory as 'Hello/Goodbye.m4a'. Is there anyway to encode these special characters so that the file path ignores them?
If you need to add a slash "/" to your filename you need to replace it by a colon ":":
let desktopDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!
let fileName = "Hello/Goodbye.txt".replacingOccurrences(of: "/", with: ":")
let file = desktopDirectory.appendingPathComponent(fileName)
do {
try "SUCCESS".write(to: file, atomically: true, encoding: .utf8)
} catch {
print(error)
}
extension URL {
func appendingFileName(_ fileName: String, withExtension: String) -> URL {
appendingPathComponent(fileName.replacingOccurrences(of: "/", with: ":")).appendingPathExtension(withExtension)
}
}
let fileName = "Hello/Goodbye"
let pathExtension = "txt"
let file = desktopDirectory.appendingFileName(fileName, withExtension: pathExtension)
do {
try "SUCCESS".write(to: file, atomically: true, encoding: .utf8)
} catch {
print(error)
}
I have a data.json file in the root of my Swift 5 project to which I would like to write some data. The file is already filled with data beforehand, but I would like to overwrite it.
This is the function I use to encode an array of Task structs into JSON Data:
func encode(task: Task){
loadJSON()
t.append(task)
if let json = try? JSONEncoder().encode(t){
saveJSON(json: json)
}
}
loadJSON() loads the JSON data into the t array from the data.json file, then the new Task is appended to the array, and then I create a json constant with encoded data from the t array, and call the saveJSON function.
func saveJSON(json: Data){
if let path = Bundle.main.path(forResource: "data", ofType: "json"){
let url = URL(fileURLWithPath: path)
do{
try json.write(to: url, options: .atomicWrite)
}
catch{
print(error)
}
}
}
The path and the url are set, then the do is executed and it just falls through without doing anything. The data.json file is unchanged and json.write doesn't throw any errors.
I'm not entirely sure why am I unable to write to the file. I've tried to write a simple string instead of a data set, with similar results.
The problem seems to be caused by your attempting to write to the bundle directory, which cannot be written to directly (from all accounts, it seems). Moving your file to a writable directory, like the user's documents directory, will allow you to modify or delete the file as you wish.
You can copy your resource file into the documents directory so it can be accessed locally, and made available to the user as well. Here's an example helper method that copies a file (in your case, pass the string data.json as the sourceFile argument) from the Bundle into the documents directory:
func copyFileFromBundleToDocumentsFolder(sourceFile: String, destinationFile: String = "") {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let documentsURL = documentsURL {
let sourceURL = Bundle.main.bundleURL.appendingPathComponent(sourceFile)
// Use the same filename if destination filename is not specified
let destURL = documentsURL.appendingPathComponent(!destinationFile.isEmpty ? destinationFile : sourceFile)
do {
try FileManager.default.removeItem(at: destURL)
print("Removed existing file at destination")
} catch (let error) {
print(error)
}
do {
try FileManager.default.copyItem(at: sourceURL, to: destURL)
print("\(sourceFile) was copied successfully.")
} catch (let error) {
print(error)
}
}
}
After doing that -- if you need to make modifications to the newly created file, you can create another function that overwrites the file with your specific json data using almost exactly the same logic.
You can adapt your loadJSON() and saveJSON() functions to read from or write to this new file (inside the document directory). For saving, something like this:
func saveJSONDataToFile(json: Data, fileName: String) {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let documentsURL = documentsURL {
let fileURL = documentsURL.appendingPathComponent(fileName)
do {
try json.write(to: fileURL, options: .atomicWrite)
} catch {
print(error)
}
}
}
EDIT:
Changes to the Document Directory should persist. Here is code to retrieve the list of all files in that directory. It should help to check that the file in question is being persisted properly:
func listDocumentDirectoryFiles() {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let url = documentsURL {
do {
let contents = try FileManager.default.contentsOfDirectory(atPath: url.path)
print("\(contents.count) files inside the document directory:")
for file in contents {
print(file)
}
} catch {
print("Could not retrieve contents of the document directory.")
}
}
}
Hope this helps. Feel free to ask in the comments if anything is unclear. Good luck!
I'm trying to read and write a file from a path (ex: "/Desktop/folder"). If this can't be done, then from Documents (ex: "/Documents/folder"). I saw and tried several examples, but the problem is that the file is located in a location such:
file:///Users/name/Library/Developer/CoreSimulator/Devices/AE6A47DE-D6D0-49AE-B39F-25C7A2335DC8/data/Containers/Data/Application/09F890C1-081F-46E7-88BC-F8453BAFC1CB/Documents/Test.txt"
0x00006000000af780
Even if i have the "Test.txt" in Documents and even in project.
Here's the code which reads and writes a file at the above location:
let file = "Test.txt" //this is the file. we will write to and read from it
let text = "some text" //just a text
var text2 = ""
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(file)
//writing
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {/* error handling here */print(error)}
//reading
do {
text2 = try String(contentsOf: fileURL, encoding: .utf8)
}
catch {/* error handling here */ print(error)}
}
Is it possible to read and write file from path i need (ex: "Documents/Folder")?
So, like you're doing now, take the documents dir, and append the path you need:
let file = "Test.txt" //this is the file. we will write to and read from it
guard let dir = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first { else return }
let subDir = dir.appendingPathComponent("Folder", isDirectory: true)
let fileURL = subDir.appendingPathComponent(file)
Note that trying to write to that file URL will fail if the sub-folder "Folder" doesn't already exist. You'd have to use one of the file manager createDirectory calls to create the "Folder" directory if it doesn't exist.
I find the solution:
let file = "Test.txt" //this is the file. we will write to and read from it
let text = "some text" //just a text
var text2 = ""
let fileURL = URL(fileURLWithPath: "/Users/name/Documents/Folder/Test.txt")
//writing
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {/* error handling here */print(error)}
//reading
do {
text2 = try String(contentsOf: fileURL, encoding: .utf8)
var s = ""
}
catch {/* error handling here */ print(error)}
}
I'm trying to save a CSV file to the Documents folder on iOS. The write(to:atomically:encoding:) doesn't throw an error, but when I got to load the file I get the error:
The file “test.csv” couldn’t be opened because there is no such file.
How can I resolve this? Also, if I try to put the file in a folder in the Documents directory, it consistently fails writing the file saying there is "no folder named "test.csv"". I tried using .txt instead of .csv and got the same error.
My write block
let dobString = NEKFormatter.dateOfBirth.string(from: dob)
// TODO: File Naming Scheme
let fileName = "test.csv"
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
let destDir = documentsDirectory
let destPath = destDir.appendingPathComponent(fileName)
// Attempt to save file to patient folder. If no patient folder exists, create it.
do {
if FileManager.default.fileExists(atPath: destDir.absoluteString) {
try FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: false, attributes: nil)
}
print(destPath)
try csvString.write(to: destPath, atomically: true, encoding: .utf8)
filePath = destPath.absoluteString // Global variable
} catch {
debugPrint("Error writing file: \(error.localizedDescription),")
}
My load block
let rawDataPath = URL(fileURLWithPath: filePath)
do {
let csvString = try String(contentsOf: rawDataPath, encoding: .utf8)
} catch {
debugPrint(error.localizedDescription, #file, #line)
}