Creating document directory file paths when file names include irregular characters - ios

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)
}

Related

Can not write data to file in iOS Swift

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.)

display in a textView a .log file from a filePath

I'm trying to display a .log file in a textview but can't seem to access the filepath properly. When I do it returns nil. I'm using a pod called "SwiftyBeaver" to do the logging. This is what the fileURL looks like:
file:///var/mobile/Containers/Data/Application/5C92E3E6-9E45-4869-9142-AB9E70EE4FCC/Library/Caches/swiftybeaver.log
This is the function I'm using to turn the .log into a string so I can display it in a textView
private func loadTextWithFileName(_ fileName: String) -> String? {
guard let path = Bundle.main.path(forResource: fileName, ofType: "log"),
let contents = try? String(contentsOfFile: path) else {return nil}
return contents
}
This is how I'm displaying the text to the textView
self.loggingTextView.text =
self.loadTextWithFileName(self.file.logFileURL!.absoluteString)
The method that you are using Bundle.main.path() is mainly to search for files in your Bundle.
But it seems your log file is going to be in your Cache directory.
Here is how you can look for a file in your Cache directory of your app
private func loadTextWithFileName(_ fileName: String) -> String? {
if let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(fileName)
guard let text = try? String(contentsOf: fileURL, encoding: .utf8) else {
return nil
}
return text
}
return nil
}
You can add a catch block to your try case to check what the error is, in case you don't get the log file details.

How to read a file from certain path

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)}
}

Writing string to file doesn't throw error, but no file exists

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)
}

Read file in swift, iOS playground

Having searched through the many (many!) swift playground questions to even craft this code, I'm still struggling.
I've placed a text file in the Resources folder of package contents, and it appears as an alias (link) in the running temp files generated by the playground (/var/folders/ ...).
import UIKit
let bundle = NSBundle.mainBundle()
let myFilePath = bundle.pathForResource("dict1", ofType: "txt")
println(myFilePath) // <-- this is correct, there is a shortcut to the Resource file at this location
var error:NSError?
var content = String(contentsOfFile:myFilePath!, encoding:NSUTF8StringEncoding, error: &error)
println(content!) // <-- this is *NOT* the file contents [EDIT: see later note]
// Demonstrate there's no error
if let theError = error {
print("\(theError.localizedDescription)")
} else {
print("No error")
}
The problem being, that content is shown in the playground output as being Some "apple\ngame\nhow\nswift\ntoken", rather than the file contents as expected.
It's finding the file, because if I change the filename, it errors. Any advice on getting the file contents?
Xcode 6.1
EDIT:
So, the actual problem was that I wasn't expecting the playground output (including, println) to be escaped. That, combined with fatigue and other stupidities led me to believe there was a problem, when none existed.
Interestingly, not everything seems to be escaped in playground:
println("foo\nbar") // Outputs "foo\nbar", escaped
println("\\n") // Outputs "\n", unescaped
You can try creating a class for opening and saving your files:
edit/update: Swift 5 or later
class File {
class func open(
_ path: String,
encoding: String.Encoding = .utf8
) throws -> String {
guard FileManager.default.fileExists(atPath: path) else {
throw NSError(
domain: "NSCocoaErrorDomain",
code: 260,
userInfo: [
"NSUnderlyingError": #"Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory""#,
"NSFilePath": path
]
) as Error
}
return try String(
contentsOfFile: path,
encoding: encoding
)
}
class func save(
_ path: String,
_ content: String,
encoding: String.Encoding = .utf8
) throws {
try content.write(
toFile: path,
atomically: true,
encoding: encoding
)
}
}
Usage: File.save
let stringToSave: String = "Your text"
if let fileURL = FileManager.default.urls(
for: .desktopDirectory,
in: .userDomainMask
).first?.appendingPathComponent("file.txt") {
do {
try File.save(fileURL.path, stringToSave)
print("file saved")
} catch {
print("Error saving file:", error)
}
}
Usage: File.open
if let fileURL = FileManager.default.urls(
for: .desktopDirectory,
in: .userDomainMask
).first?.appendingPathComponent("file.txt") {
do {
let loadedText = try File.open(fileURL.path)
print("Loaded text:", loadedText)
} catch {
print("Error reading file:", error)
}
}
Or if you prefer extending StringProtocol:
extension StringProtocol {
func open(from directory: FileManager.SearchPathDirectory = .documentDirectory,
in domain: FileManager.SearchPathDomainMask = .userDomainMask,
encoding: String.Encoding = .utf8) throws -> String {
let directory = try FileManager.default.url(
for: directory,
in: domain,
appropriateFor: nil,
create: true
)
return try String(
contentsOf: directory.appendingPathComponent(.init(self)),
encoding: encoding
)
}
func save(as fileName: String,
to directory: FileManager.SearchPathDirectory = .documentDirectory,
in domain: FileManager.SearchPathDomainMask = .userDomainMask,
encoding: String.Encoding = .utf8) throws {
let directory = try FileManager.default.url(
for: directory,
in: domain,
appropriateFor: nil,
create: true
)
try write(to: directory.appendingPathComponent(fileName),
atomically: true,
encoding: encoding)
}
}
Usage iOS (saving/loading from documents directory):
let stringToSave: String = "Your text"
let fileName = "file.txt"
do {
try stringToSave.save(as: fileName)
print("Text saved!!!")
let loadedText = try fileName.open()
print("Text loaded:", loadedText)
} catch {
print("Error:", error)
}
Usage macOS (saving/loading from desktop directory):
let string = "Your text"
let fileName = "file.txt"
do {
try string.save(as: fileName, to: .desktopDirectory)
print("Text saved!!!")
let loadedText = try fileName.open(from: .desktopDirectory)
print("Text loaded:", loadedText)
} catch {
print("Error:", error)
}
I have seen this problem with .txt files created from .rtf files using TextEdit.
I loaded a text.txt file in the resources folder of my playground using similar code to you. The file contents was "hello there" and was made by converting an .rtf file to .txt by changing the extension.
let path = NSBundle.mainBundle().pathForResource("text", ofType: "txt")//or rtf for an rtf file
var text = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: nil)!
println(text)
The output was:
{\rtf1\ansi\ansicpg1252\cocoartf1343\cocoasubrtf140
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural
\f0\fs24 \cf0 hello there}
So the "hello there" is embedded. This is the problem with all the layout information in a .rtf file.
I went back to TextEdit and created a true .txt file. After opening a file - Format|Make Plain Text
Now this same code gave the console output "hello there".

Resources