Reading from txt file in Swift 3 - swift

I want to know how to read from a txt file and print out specific parts of the file?
For example, "test.txt" will contain:
'''Jason 16 male self programing
Josh 15 male friend art'''
So I am looking for a way to print each word and line separately. Such as only printing:
"Jason"
"Jason is 16"
"Josh likes art"
This is what I got so far from searching around
if let filepath = Bundle.main.path(forResource: "test", ofType: "txt")
{
do
{
let contents = try String(contentsOfFile: filepath)
print(contents[0])
}
catch
{
// contents could not be loaded
}
}
else
{
// example.txt not found!
}
Thank you for your support.

Once you have read your file into contents you can break it into lines and words with code like:
let lines = contents.components(separatedBy: "\n")
for line in lines {
let words = line.components(separatedBy: " ")
print("\(words[0]) is \(words[1]) and likes \(words[4])")
}

Related

Problem with editing and adding text files to project

This is my code that works perfectly fine when I add someones txt file.
DispatchQueue.global().async { [self] in
//ovo oznacava level1↓ jer je pocetna vrednost poseda level = 1
if let levelFileURL = Bundle.main.url(forResource: "text1", withExtension: "txt"){
if let levelContents = try? String(contentsOf: levelFileURL){
var lines = levelContents.components(separatedBy: "\n")
lines.shuffle()
for (index,line) in lines.enumerated(){
let parts = line.components(separatedBy: " — ")
let answer = parts[0]
let clue = parts [1]
But if I try to eddit that txt file or even add my own txt file I always get an error for parts[1] saying: Thread 2: Fatal error: Index out of range
example of text file I want to use:
Afghanistan — Kabul
Albania — Tirana
Algeria — Algiers
Your code seems to work with the sample file you've given:
import Foundation
let levelContents = """
Afghanistan — Kabul
Albania — Tirana
Algeria — Algiers
"""
var lines = levelContents.components(separatedBy: "\n")
lines.shuffle()
for (index, line) in lines.enumerated() {
let parts = line.components(separatedBy: " — ")
debugPrint(parts)
let answer = parts[0]
let clue = parts [1]
print("answers \(answer), clue: \(clue)")
}
Notice in this sample I use debugPrint to show me the array of parts that gets generated. You could put a similar line in your code to find out what the parts array contains in cases where the code fails.

Swift Converting Format in Text File (String Parsing)

I was wondering what is the best way to take a text file containing lines in this format:
Last_name:First_name:Number_of_cats:Number_of_dogs:Number_of_fish:Number_of_other_pets
and produce a text file containing lines in this format:
First_name Last_name:Total_number_of_pets
For example, it would take a text file containing this:
Apple:Tim:0:0:3:0
Jobs:Steve:0:0:5:2
Da Kid:Billie:0:1:0:1
White:Walter:2:1:1:0
Bond:James:2:2:3:0
Stark:Tony:0:1:2:0
Wayne:Bruce:0:0:0:0
and output a text file that would look like this:
Tim Apple:3
Steve Jobs:7
Bille Da Kid:2
Walter White:4
James Bond:7
Tony Stark:3
Bruce Wayne:0
Here's what I've been trying so far without any success:
let namesFile = "Names"
let dir = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(namesFile).appendingPathExtension("txt") {
print (fileURL)
var petSum = 0;
do {
let entriesString = try String(contentsOf: fileURL)
let entries = entriesString.components(separatedBy: "\n")
for entry in entries {
let namePets = entry.components(separatedBy: ":")
if (namePets.indices.contains(1)) {
var sum = 0;
print(namePets[1])
print(namePets[0])
for namePet in namePets {
if let intArg = Int(namePet) {
sum = sum + intArg
}
}
print (sum)
print ("\n")
}
}
} catch {
print("Failed reading from URL, Error: " + error.localizedDescription)
}
}
else {
print("didn't work")
}
/*
// Read from the file
var inString = ""
// Write to the file named Test
let outString = "Write this text to the file"
do {
try outString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
*/
Would appreciate any help, thank you!
Split the text into lines.
Map each line
Split the line into components separated by colon.
Convert field 2 through end to Int and sum them up.
Return the string in the new format.
Join the result.
let text = """
Apple:Tim:0:0:3:0
Jobs:Steve:0:0:5:2
Da Kid:Billie:0:1:0:1
White:Walter:2:1:1:0
Bond:James:2:2:3:0
Stark:Tony:0:1:2:0
Wayne:Bruce:0:0:0:0
"""
let lines = text.components(separatedBy: .newlines)
let convertedLines = lines.map { line in
let components = line.components(separatedBy: ":")
return "\(components[1]) \(components[0]):\(components[2...].compactMap(Int.init).reduce(0, +))"
}
let result = convertedLines.joined(separator: "\n")

Using Swift to write a character to a file

I'm trying to write a Swift program that writes a single character to a file. I've researched this but so far haven't figured out how to do this (note, I'm new to Swift). Note that the text file I'm reading and writing to can contain a series of characters, one per line. I want to read the last character and update the file so it only contains that last character.
Here's what I have so far:
let will_file = "/Users/willf/Drobox/foo.txt"
do {
let statusStr = try String(contentsOfFile: will_file, encoding: .utf8)
// find the last character in the string
var strIndex = statusStr.index(statusStr.endIndex, offsetBy: -1)
if statusStr[strIndex] == "\n" {
// I need to access the character just before the last \n
strIndex = statusStr.index(statusStr.endIndex, offsetBy: -2)
}
if statusStr[strIndex] == "y" {
print("yes")
} else if statusStr[strIndex] == "n" {
print("no")
} else {
// XXX deal with error here
print("The char isn't y or n")
}
// writing
// I get a "cannot invoke 'write with an arg list of type (to: String)
try statusStr[strIndex].write(to: will_file)
}
I would appreciate advice on how to write the character returned by statusStr[strIndex].
I will further point out that I have read this Read and write a String from text file but I am still confused as to how to write to a text file under my Dropbox folder. I was hoping that there was a write method that could take an absolute path as a string argument but I have not found any doc or code sample showing how to do this that will compile in Xcode 9.2. I have also tried the following code which will not compile:
let dir = FileManager.default.urls(for: .userDirectory, in: .userDomainMask).first
let fileURL = dir?.appendingPathComponent("willf/Dropbox/foo.txt")
// The compiler complains about extra argument 'atomically' in call
try statusStr[strIndex].write(to: fileURL, atomically: false, encoding: .utf8)
I have figured out how to write a character as a string to a file thanks to a couple answers on stack overflow. The key is to coerce a character type to a string type because the string object supports the write method I want to use. Note that I used both the answers in Read and write a String from text file and in Swift Converting Character to String to come up with the solution. Here is the Swift code:
import Cocoa
let will_file = "/Users/willf/Dropbox/foo.txt"
do {
// Read data from will_file into String object
let statusStr = try String(contentsOfFile: will_file, encoding: .utf8)
// find the last character in the string
var strIndex = statusStr.index(statusStr.endIndex, offsetBy: -1)
if statusStr[strIndex] == "\n" {
// I need to access the character just before the last \n
strIndex = statusStr.index(statusStr.endIndex, offsetBy: -2)
}
if statusStr[strIndex] != "n" && statusStr[strIndex] != "y" {
// XXX deal with error here
print("The char isn't y or n")
}
// Update file so it contains only the last status char
do {
// String(statusStr[strIndex]) coerces the statusStr[strIndex] character to a string for writing
try String(statusStr[strIndex]).write(toFile: will_file, atomically: false, encoding: .utf8)
} catch {
print("There was a write error")
}
} catch {
print("there is an error!")
}

How can I bring in data from a text file on disk without adding it to the project?

I'm trying to read in a file from disk and parse its data into a nice format. However, the function is not returning anything. It returns an empty array. Why is this?
Note: I've been tinkering around with this and I've managed to simplify the previous 2 functions to just this one.
The function:
func openFile(_ fileName:String, _ fileType:String) -> [(Double, Double)] {
let file = fileName + fileType //this is the file. we will write to and read from it
var text2: String = ""
if let dir = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
//reading
do {
text2 = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
var pairs = [(Double,Double)]()
var words: [String] = []
for line in (text2.components(separatedBy: "\n").dropFirst()){
if line != "" {
words = line.components(separatedBy: "\t")
pairs.append((Double(words[0])!,Double(words[1])!))
}
}
return pairs
}

Remove suffix from filename in Swift

When trying to remove the suffix from a filename, I'm only left with the suffix, which is exactly not what I want.
What (how many things) am I doing wrong here:
let myTextureAtlas = SKTextureAtlas(named: "demoArt")
let filename = (myTextureAtlas.textureNames.first?.characters.split{$0 == "."}.map(String.init)[1].replacingOccurrences(of: "\'", with: ""))! as String
print(filename)
This prints png which is the most dull part of the whole thing.
If by suffix you mean path extension, there is a method for this:
let filename = "demoArt.png"
let name = (filename as NSString).deletingPathExtension
// name - "demoArt"
Some people here seem to overlook that a filename can have multiple periods in the name and in that case only the last period separates the file extension. So this.is.a.valid.image.filename.jpg and stripping the extension should return this.is.a.valid.image.filename and not this (as two answers here would produce) or anything else in between. The regex answer works correctly but using a regex for that is a bit overkill (probably 10 times slower than using simple string processing). Here's a generic function that works for everyone:
func stripFileExtension ( _ filename: String ) -> String {
var components = filename.components(separatedBy: ".")
guard components.count > 1 else { return filename }
components.removeLast()
return components.joined(separator: ".")
}
print("1: \(stripFileExtension("foo"))")
print("2: \(stripFileExtension("foo.bar"))")
print("3: \(stripFileExtension("foo.bar.foobar"))")
Output:
foo
foo
foo.bar
You can also split the String using componentsSeparatedBy, like this:
let fileName = "demoArt.png"
var components = fileName.components(separatedBy: ".")
if components.count > 1 { // If there is a file extension
components.removeLast()
return components.joined(separator: ".")
} else {
return fileName
}
To clarify:
fileName.components(separatedBy: ".")
will return an array made up of "demoArt" and "png".
In iOS Array start with 0 and you want name of the file without extension, so you have split the string using ., now the name will store in first object and extension in the second one.
Simple Example
let fileName = "demoArt.png"
let name = fileName.characters.split(".").map(String.init).first
If you don't care what the extension is. This is a simple way.
let ss = filename.prefix(upTo: fileName.lastIndex { $0 == "." } ?? fileName.endIndex))
You may want to convert resulting substring to String after this. With String(ss)
#Confused with Swift 4 you can do this:
let fileName = "demoArt.png"
// or on your specific case:
// let fileName = myTextureAtlas.textureNames.first
let name = String(fileName.split(separator: ".").first!)
print(name)
Additionally you should also unwrapp first but I didn't want to complicate the sample code to solve your problem.
Btw, since I've also needed this recently, if you want to remove a specific suffix you know in advance, you can do something like this:
let fileName = "demoArt.png"
let fileNameExtension = ".png"
if fileName.hasSuffix(fileNameExtension) {
let name = fileName.prefix(fileName.count - fileNameExtension.count)
print(name)
}
How about using .dropLast(k) where k is the number of characters you drop from the suffix ?
Otherwise for removing extensions from path properly from filename, I insist you to use URL and .deletingPathExtension().lastPathComponent.
Maybe a bit overhead but at least it's a rock solid Apple API.
You can also use a Regexp to extract all the part before the last dot like that :
let fileName = "test.png"
let pattern = "^(.*)(\\.[a-zA-Z]+)$"
let regexp = try! NSRegularExpression(pattern: pattern, options: [])
let extractedName = regexp.stringByReplacingMatches(in: fileName, options: [], range: NSMakeRange(0, fileName.characters.count), withTemplate: "$1")
print(extractedName) //test
let mp3Files = ["alarm.mp3", "bubbles.mp3", "fanfare.mp3"]
let ringtonsArray = mp3Files.flatMap { $0.components(separatedBy: ".").first }
You can return a new string removing a definite number of characters from the end.
let fileName = "demoArt.png"
fileName.dropLast(4)
This code returns "demoArt"
One liner:
let stringWithSuffixDropped = fileName.split(separator: ".").dropLast().joined(separator: ".")