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".
Related
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)
}
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 am implementing a small logger, in which I am writing to a TXT file.
I wanted the last event to be at the top of the file but I'm having trouble getting this to work. All examples on the internet are using "fileHandle.seekToEndOfFile()" to write at the end of the file.
This is what I have:
private static func writeToFile(text: String) {
guard let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }
guard let writePath = NSURL(fileURLWithPath: path).appendingPathComponent(Logger.folderName) else { return }
let fileManager = FileManager.default
try? fileManager.createDirectory(atPath: writePath.path, withIntermediateDirectories: true)
let file = writePath.appendingPathComponent(Logger.fileName)
if !fileManager.fileExists(atPath: file.path) {
do {
try "".write(toFile: file.path, atomically: true, encoding: String.Encoding.utf8)
} catch _ {
}
}
let msgWithLine = text + "\n"
do {
let fileHandle = try FileHandle(forWritingTo: file)
//fileHandle.seekToEndOfFile()
fileHandle.write(msgWithLine.data(using: .utf8)!)
fileHandle.closeFile()
} catch {
print("Error writing to file \(error)")
}
}
With this code, I write in the first line but nevertheless, I am always rewriting what is on the first line.
How can I change this to whatever is in the first be taken to a line below and then write new content in the first line?
Thank you!!
This should be okay:
First, get the old data and after that append the new one and write everything.
let msgWithLine = text + "\n"
do {
let fileHandle = try FileHandle(forWritingTo: file)
fileHandle.seek(toFileOffset: 0)
let oldData = try String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
var data = msgWithLine.data(using: .utf8)!
data.append(oldData)
fileHandle.write(data)
fileHandle.closeFile()
} catch {
print("Error writing to file \(error)")
}
I didn't test the code it may have some problems.
Another possible solution is to write at the end of the file and to invert the file when you read it.
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)}
}
Edit 2:
I tried Zip from marmelroy (https://github.com/marmelroy/Zip) and it failed on the zip-file created witch ZipArchive too.
Then I created another zip-file with Zip and this one worked fine for unzipping.
There seems to be a problem with ZipArchive for me. I will use Zip instead and thats it for me ...
Thx for the answers !!
End edit 2.
I need to unzip certain files so I manually installed SSZipArchive.
Copied all 3 folders (SSZipArchive, minizip, aes) to project, added #import "ZipArchive.h" in my bridging.h and all builds nicely.
Edit:
Used Carthage as recommended, but same behavior.
End edit.
I use Xcode 8 / Swift 3
After a few tests with no unzipping any file, I created my own zip using SSZipArchive.
let file = "file.txt"
let text = "some text" //just a text
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let path = dir?.appendingPathComponent(file)
//writing
do {
try text.write(to: path!, atomically: false, encoding: String.Encoding.utf8)
}
catch {
print("Failed writing")
}
Here I print() the Documentsdirectory: ["file.txt"]
let zipPath = tempZipPath() //tempZipPath is taken from the SSZipArchiveExample except it uses DocumentDirectory instead of Cache...
print("zipPath: \(zipPath)")
print zipPath: /var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
let success = SSZipArchive.createZipFile(atPath: zipPath, withContentsOfDirectory: (dir?.absoluteString)!)
if success {
print("zipped")
} else {
print(" NOT zipped")
}
Again the Documentsdirectories entries: ["93428A1B-E5B6-434F-B049-632B7519B126.zip", "file.txt"]
// this is again taken from the original Example
guard let unzipPath = tempUnzipPath() else {
return
}
print("unzipPath: \(unzipPath)")
print unzipPath: /var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/E4FC7DE6-F21B-46D8-9953-DCBF86E2268E
Reading the entries of this new directory: []
let filePath = zipPath
var fileSize : UInt64 = 0
do {
let attr : NSDictionary? = try FileManager.default.attributesOfItem(atPath: filePath) as NSDictionary?
if let _attr = attr {
fileSize = _attr.fileSize();
print("fileSize: \(fileSize)")
}
} catch {
print("Error: \(error)")
}
this writes 22. I also tried to read the zip-file which works fine
From this part on I was desperate what to do
let url = URL(fileURLWithPath: zipPath)
print("url: \(url)")
print("url.path: \(url.path)")
print("url.absoluteString: \(url.absoluteString)")
print("url.absoluteURL: \(url.absoluteURL)")
print("url.absoluteURL.absoluteString: \(url.absoluteURL.absoluteString)")
The output of these lines look all good for me:
url:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.path:
/var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteString:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteURL:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteURL.absoluteString:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
And then I tried them all (Strings of course. No url allowed)
var success2 = SSZipArchive.unzipFile(atPath: zipPath, toDestination: unzipPath)
if !success2 {
print("zipPath")
}
success2 = SSZipArchive.unzipFile(atPath: url.path, toDestination: unzipPath)
if !success2 {
print("url.path")
}
success2 = SSZipArchive.unzipFile(atPath: url.absoluteString, toDestination: unzipPath)
if !success2 {
print("url.absoluteString")
}
success2 = SSZipArchive.unzipFile(atPath: url.absoluteURL.path, toDestination: unzipPath)
if !success2 {
print("url.absoluteURL.path")
}
And it prints every line, so they all failed. At the end here comes the "long" way with unipFileAtPath. I tried this one too with zipPath, ...
All the same results.
do {
success2 = try SSZipArchive.unzipFileAtPath(url.path, toDestination: unzipPath, overwrite: true, password: nil, delegate: nil)
if !success2 {
return
}
} catch {
print("error \(error)")
print("error \(error.localizedDescription)")
}
error Error Domain=SSZipArchiveErrorDomain Code=-1 "failed to open zip
file" UserInfo={NSLocalizedDescription=failed to open zip file} error
failed to open zip file
Here the to func to get the path-strings. btw. I modified tempZipPath so returns an URL or an URL.path
but hey: I was desperate
func tempZipPath() -> String {
var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
path += "/\(UUID().uuidString).zip"
return path
}
func tempUnzipPath() -> String? {
var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
path += "/\(UUID().uuidString)"
let url = URL(fileURLWithPath: path)
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return url.path
}
Any suggestions? Is there something to consider manually installing SSZipArchive?
The tip "use .path instead of .absoluteString" didn't work for me as u can see above.
a desperate tartsigam
I had the problem, too. If you use url.path instead of url.absolutString, it works.
Thanks to vadian for the hint!
Before zip don't extract file and return false.
After put this in Build Settings / Apple CLang Prepocessing / Preprocessor Macros Debug e Release works here
GCC_PREPROCESSOR_DEFINITIONS: HAVE_INTTYPES_H HAVE_PKCRYPT HAVE_STDINT_H HAVE_WZAES HAVE_ZLIB MZ_ZIP_NO_SIGNING $(inherited)
In my case, using ObjC and manual installation, I forgot to Add the following GCC_PREPROCESSOR_DEFINITIONS:
$(inherited)
HAVE_INTTYPES_H
HAVE_PKCRYPT
HAVE_STDINT_H
HAVE_WZAES
HAVE_ZLIB
After adding these GCC_PREPROCESSOR_DEFINITIONS it worked as expected.