I'm trying to replace the first N bytes in a file in Swift with my own data, leaving the rest of the file unchanged, e.g. I have the string "OOPS", the file (of any length) currently contains Look, a daisy, and I want it to contain OOPS, a daisy. The built-in functions I've found don't do what I want:
try "OOPS".write(to: path, atomically: false, encoding: String.Encoding.utf8)
replaces the entire file,
let outputStream = OutputStream(url: outputURL, append: false)
outputStream.write("OOPS", maxLength: 4)
behaves the same way, and setting append to true obviously appends my text to the end of the file. Is there an easy way to get the behavior I want?
Use FileHandle.
let handle = FileHandle(forWritingTo: outputURL)
handle.seek(toFileOffset: 0)
handle.write("OOPS".data(using: .utf8))
handle.closeFile()
I leave it to the reader to deal with handling optionals and needing to catch errors.
Related
I have been playing with a json file in a playground and I've seen examples of reading the file like this:
do {
let jsonData = try String(contentsOf: url).data(using: .utf8)
} catch {
...
}
And like this:
do {
let jsonData = try Data(contentsOf: url)
} catch {
...
}
Is there a difference in the data? The only difference I see is the String data method is being formatted as UTF8 when read, where I am assuming the Data method is reading with a default format (UTF8 also??)? I can't see a difference in the data, however, but just want to make sure.
The difference is that String(contentsOf: url) tries to read text from that URL, whereas Data(contentsOf: url) reads the raw bytes.
Therefore, if the file at the URL is not a plain text file, String(contentsOf: url) could throw an error, whereas Data(contentsOf: url) would read it successfully.
Regarding the encoding, String(contentsOf: url) is undocumented, but from its implementation, we can see that it calls NSString.init(contentsOf:usedEncoding:):
public init(
contentsOf url: __shared URL
) throws {
let ns = try NSString(contentsOf: url, usedEncoding: nil)
self = String._unconditionallyBridgeFromObjectiveC(ns)
}
NSString.init(contentsOf:usedEncoding:) is documented:
Returns an NSString object initialized by reading data from a given URL and returns by reference the encoding used to interpret the data.
So apparently the encoding is guessed (?) and returned by reference, which is then ignored by String.init(contentsOf:), as it passed nil for the usedEncoding parameter.
This means that for some non-UTF-8 files, there is a chance of String(contentsOf:) guessing the correct encoding, and then data(using: .utf8) encodes the string to UTF-8 bytes, making the rest of your code work. If you had used Data(contentsOf:), you would be reading in the wrong encoding, and though it wouldn't throw an error, the JSON-parsing code later down the line probably would.
That said, JSON is supposed to be exchanged in UTF-8 (See RFC), so an error when you read a non-UTF-8 file is probably desired.
So basically, if we are choosing between these two options, just use Data(contentsOf:). It's simpler and less typing. You don't need to worry about thing like wrong encodings, or that the file is not plain text. If anything like that happens, it is not JSON, and the JSONDecoder later down the line would throw.
I need help figuring out how to write repeatedly to the same, open, output file.
I am using Swift 4.2. My searches and tests have turned up only code that writes a single text string to a file and then closes the file. The next opening overwrites the last one. An example is shown below.
The problem is that I need to be able to write large numbers of records (say, 1.5 million) and perform calculations on each record just before it is written to a file. That’s not feasible when the code will only write once before closing. I'm calling this "writing line by line", much like the opposite, to "read line by line."
I tried to find an option in various Swift write statements and SO posts, but everything seems to be geared toward writing once then closing the file. I tried an open for append, but that did not work and anyway it seems inefficient to open, close, reopen-append each time I want to write to a file. I tried some C code in Swift, using open(… and freopen(… but could not get something that the compiler wouldn't complain about. Hopefully, there is a way to do this all in Swift. The following code works nicely for one write.
let file0 = “test_file.txt”
let s0 = ("This is a test line of text")
do {
try s0.write(to: NSURL(fileURLWithPath: file0) as URL, atomically: false, encoding: String.Encoding.utf8)
} catch {
print("Problem writing to file0")
}
How can I adapt this code snippet to write a string, and then another and another etc, and before closing the file when it’s all done? If not with this, is there Swift code that will do the job?
Following are the essential code components needed to write to a file, line-by-line in Swift. First is some file management code to create a file if it does not exist, then there is code to print a series of example statements, followed by code to print to the file in a loop, and finally close the file. This code worked correctly in Swift 4.2. The difference between this and the method in the question is that the write statements in this code use a method of fileHandle! and the question shows a method of a Swift string.
print("Swift_Write_to_File_Test_1")
var outFilename: NSString = "test_file.txt"
// Begin file manager segment
// Check for file presence and create it if it does not exist
let filemgr = FileManager.default
let path = filemgr.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).last?.appendingPathComponent(outFilename as String)
if !filemgr.fileExists(atPath: (path?.absoluteString)!) {
filemgr.createFile(atPath: String(outFilename), contents:Data(" ".utf8), attributes: nil)
}
// End file manager Segment
// Open outFilename for writing – this does not create a file
let fileHandle = FileHandle(forWritingAtPath: outFilename as String)
if(fileHandle == nil)
{
print("Open of outFilename forWritingAtPath: failed. \nCheck whether the file already exists. \nIt should already exist.\n");
exit(0)
}
var str: NSString = "2. Test string from NSString.\n";
var str0: String = "3. Test string from a Swift String.\n"
var str1: NSString = "4. Test string from NSString.\n";
fileHandle!.write("1. Text String in-line with code statement.\n".data(using: .utf8)!)
fileHandle!.write(String(str).data(using: .utf8)!)
fileHandle!.write(str0.data(using: .utf8)!)
fileHandle!.write(String(str1).data(using: .utf8)!)
fileHandle!.write("5. Text String in-line with code statement.\n".data(using: .utf8)!)
fileHandle!.write("6. Text in a loop follows: \n".data(using: .utf8)!)
for i in 0...5
{
//Assemble a string then write it to the file.
var s0: String = ""
s0 = String(i)
//s0.append(" ... some text here.\n") // See improvement below
s0 += " ... some text here.\n" // This is a better than .append
fileHandle!.write(s0.data(using: .utf8)!)
}
// End of file-writing segment
fileHandle!.closeFile()
This worked for me in Swift 5:
func writeFile() -> Bool
{
let outFilename: String = "test_file.txt"
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let outFilePath = documentsURL!.appendingPathComponent(outFilename).path
let fileManager = FileManager.default
// If file exists, remove it
if fileManager.fileExists(atPath: outFilePath)
{
do { try fileManager.removeItem(atPath: outFilePath) }
catch { return false }
}
// Create file and open it for writing
fileManager.createFile(atPath: outFilePath, contents:Data(" ".utf8), attributes: nil)
let fileHandle = FileHandle(forWritingAtPath: outFilePath)
if fileHandle == nil
{
return false
}
else
{
// Write data
fileHandle!.write("Test line 1\n".data(using: .utf8)!)
fileHandle!.write("Test line 2\n".data(using: .utf8)!)
fileHandle!.write("Test line 3\n".data(using: .utf8)!)
// Close file
fileHandle!.closeFile()
return true
}
}
I want to get a string from a file. I've researched how to do it, and I've found the next code:
import Foundation
// Read data from this file.
let path = "/Users/samallen/file.txt"
do {
// Use contentsOfFile overload.
// ... Specify ASCII encoding.
// ... Ignore errors.
var data = try NSString(contentsOfFile: path,
encoding: String.Encoding.ascii.rawValue)
// If a value was returned, print it.
print(data)
}
The important part are the lines:
var data = try NSString(contentsOfFile: path,
encoding: String.Encoding.ascii.rawValue)
I looked in Apple's documentation about this and found init(contentsOfFile:usedEncoding:)
What I don't get is why you can use String(contentsOfFile:usedEncoding:) instead of init(contentsOfFile:usedEncoding:). Why can you replace String for init? I have seen somthing similar with UIImage.
Thanks in advance
I have 2 problems:
I need to open the text file
I need to read in data (organized as 2 columns) and store into either 2 arrays or a multi-dimensional array. The 2 columns are numeric x-y pairs (see below for screenshot of the data), so whatever is easier will work for me, I just need to make sure that the first x-value corresponds to the first y-value.
My attempt:
I tried using this code that I found on this website, but it's giving me errors that I can't figure out:
let path:String = Bundle.mainBundle().pathForResource("README", ofType: "txt")!
textView.text = String(contentsOfFile: path,
encoding: NSUTF8StringEncoding,
error: nil)
Errors for the code for the first problem:
Cannot call value of non-function type 'Bundle'
Use of unresolved identifier 'textView'
For the first problem this works:
let url = Bundle.main.url(forResource: "myResource", withExtension: "txt")
let text = try? String(contentsOf: url!)
print(text ?? "")
for the unresolved identifier part make sure the definition of textView is correct and the source code has your application as its target
for your second problem:
let's say we have these three lines in our text file:
first-one\n
second-two\n
third-three
var pairs = [(Double,Double)]()
for line in (text?.components(separatedBy: "\n").dropFirst())!{
if line != "" {
let sep = # separator is here
let words = line.components(separatedBy: sep)
pairs.append((Double(words[0])!,Double(words[1])!))
}
}
// for reading of the values
for pair in pairs{
print(pair) // equivalent to : (pair.0,pair.1)
}
I'm trying to read large data from file. It is a text file. The following line is successful, data is read into memory:
if let data = NSData(contentsOfFile: path, options: NSDataReadingOptions(), error: &error)
This line return an error:
if let data = String(contentsOfFile:path, encoding: NSUTF8StringEncoding, error: &error)
But why? File is there, only Function is different. And I like to use second one, because I want to split all rows into separate strings in an array:
var array = data.componentsSeparatedByString("\n")
Any hint what to do?
Additional information: There are german umlauts, so code is larger than 127. But the file was saved as UTF-8. How could I load/use non ascii text?
I tried out every option and found the solution I didn't expect:
NSMacOSRomanStringEncoding
This setting accepts also german umlauts!