Problem with editing and adding text files to project - swift

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.

Related

Fatal error in dictionary - Index out of range / Swift

I have a txt text file. The text file contains the text attached to each date.
Example:
30.10.2022 : Hello Stack Overflow.
31.10.2022 : Hello programmers.
01.11.2022 : We are the best.
02.11.2022 : Swift, is the best programming language.
etc…
I need to bind each sentence to date and show only the sentence that corresponds to today's date.
Example:
If today is 30.10.2022 - show in textView only the result: Hello Stack Overflow. And the same for every day... If the date changes from 30.10.2022 to 31.10.2022, we will see the result in textView - Hello programmers!
I tried to split my text from file text to dictionary and got an error - Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range.
I want to note that yesterday everything was working, but today it is not working, despite the fact that I did not change the code. Got confused. I can't understand what I missed.
Code with error...
import Foundation
class NameDaysModel {
private var dataArray: [String] = []
func loadDictionary() -> Dictionary<String, String> {
let pathToFile = Bundle.main.path(forResource: "NameDays", ofType: "txt")
if let path = pathToFile {
let countriesString = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
dataArray = countriesString.components(separatedBy: "\n")
}
var dictionary = [String: String]()
for line in dataArray {
let components = line.components(separatedBy: " : ")
dictionary[components[0]] = components[1] // = Thread 1: Fatal error: Index out of range
}
return dictionary
}
private func getDate() -> String {
let date = NSDate()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.MM.yyyy"
let str = dateFormatter.string(from: date as Date)
return str
}
func getNameDay() -> String {
let date = getDate()
let nameDays = loadDictionary()
return nameDays["\(date)"] ?? ""
}
}
Based on the error you're getting, it sounds like some of the lines in your text file don't contain the string " : ". In that case your components array from the line let components = line.components(separatedBy: " : ") would only contain 1 element: The entire line of text.
You need to either make sure your data always contains a " : " in every line or add checking code that makes sure each value of components contains at least 2 elements before attempting to index into components[1].

Why do my simulator crash when I change inside my level.txts?

I'm making a word catch app, simulator works fine until I change
inside a level.txt file.
When I change an answer or just a clue the simulator crashed
immediately.
if let levelFileURL = Bundle.main.url(forResource: "level\(level)", 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]
clueString += "\(index + 1). \(clue)\n"
let solutionWord = answer.replacingOccurrences(of: "|", with: "")
solutionsString += "\(solutionWord.count) letters\n"
solutions.append(solutionWord)
let bits = answer.components(separatedBy: "|")
letterBits += bits
}
}
}
When I changed I get the error: Thread 1: Fatal error: Index out of
range and in chat log ''MGIsDeviceOneOfType is not supported on this
platform.''

Get the string up to a specific character

var hello = "hello, how are you?"
var hello2 = "hello, how are you #tom?"
i want to delete every letter behind the # sign.
result should be
var hello2 = "hello, how are you #tom?"
->
hello2.trimmed()
print(hello2.trimmed())
-> "hello, how are you"
Update
As i want to use it to link multiple users and replace the space behind #sign with the correct name, I always need the reference to the latest occurrence of the #sign to replace it.
text3 = "hey i love you #Tom #Marcus #Peter"
Example what the final version should look like
to start off
var text = "hello #tom #mark #mathias"
i want to always get the index of the latest # sign in the text
Expanding on #appzYourLife answer, the following will also trim off the whitespace characters after removing everything after the # symbol.
import Foundation
var str = "hello, how are you #tom"
if str.contains("#") {
let endIndex = str.range(of: "#")!.lowerBound
str = str.substring(to: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
}
print(str) // Output - "hello, how are you"
UPDATE:
In response to finding the last occurance of the # symbol in the string and removing it, here is how I would approach it:
var str = "hello, how are you #tom #tim?"
if str.contains("#") {
//Reverse the string
var reversedStr = String(str.characters.reversed())
//Find the first (last) occurance of #
let endIndex = reversedStr.range(of: "#")!.upperBound
//Get the string up to and after the # symbol
let newStr = reversedStr.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = String(newStr.characters.reversed())
//str = "hello, how are you #tom"
}
Or looking at #appzYourLife answer use range(of:options:range:locale:) instead of literally reversing the characters
var str = "hello, how are you #tom #tim?"
if str.contains("#") {
//Find the last occurrence of #
let endIndex = str.range(of: "#", options: .backwards, range: nil, locale: nil)!.lowerBound
//Get the string up to and after the # symbol
let newStr = str.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = newStr
//str = "hello, how are you #tom"
}
As an added bonus, here is how I would approach removing every # starting with the last and working forward:
var str = "hello, how are you #tom and #tim?"
if str.contains("#") {
while str.contains("#") {
//Reverse the string
var reversedStr = String(str.characters.reversed())
//Find the first (last) occurance of #
let endIndex = reversedStr.range(of: "#")!.upperBound
//Get the string up to and after the # symbol
let newStr = reversedStr.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = String(newStr.characters.reversed())
}
//after while loop, str = "hello, how are you"
}
let text = "hello, how are you #tom?"
let trimSpot = text.index(of: "#") ?? text.endIndex
let trimmed = text[..<trimSpot]
Since a string is a collection of Character type, it can be accessed as such. The second line finds the index of the # sign and assigns its value to trimSpot, but if it is not there, the endIndex of the string is assigned through the use of the nil coalescing operator
??
The string, or collection of Characters, can be provided a range that will tell it what characters to get. The expression inside of the brackets,
..<trimSpot
is a range from 0 to trimSpot-1. So,
text[..<trimSpot]
returns an instance of type Substring, which points at the original String instance.
You need to find the range of the "#" and then use it to create a substring up to the index before.
import Foundation
let text = "hello, how are you #tom?"
if let range = text.range(of: "#") {
let result = text.substring(to: range.lowerBound)
print(result) // "hello, how are you "
}
Considerations
Please note that, following the logic you described and using the input text you provided, the output string will have a blank space as last character
Also note that if multiple # are presente in the input text, then the first occurrence will be used.
Last index [Update]
I am adding this new section to answer the question you posted in the comments.
If you have a text like this
let text = "hello #tom #mark #mathias"
and you want the index of the last occurrency of "#" you can write
if let index = text.range(of: "#", options: .backwards)?.lowerBound {
print(index)
}
Try regular expressions, they are much safer (if you know what you are doing...)
let hello2 = "hello, how are you #tom, #my #next #victim?"
let deletedStringsAfterAtSign = hello2.replacingOccurrences(of: "#\\w+", with: "", options: .regularExpression, range: nil)
print(deletedStringsAfterAtSign)
//prints "hello, how are you , ?"
And this code removes exactly what you need and leaves the characters after the strings clear, so you can see the , and ? still being there. :)
EDIT: what you asked in comments to this answer:
let hello2 = "hello, how are you #tom, #my #next #victim?"
if let elementIwannaAfterEveryAtSign = hello2.components(separatedBy: " #").last
{
let deletedStringsAfterAtSign = hello2.replacingOccurrences(of: "#\\w+", with: elementIwannaAfterEveryAtSign, options: .regularExpression, range: nil)
print(deletedStringsAfterAtSign)
//prints hello, how are you victim?, victim? victim? victim??
}

Regex find/replace list of keys (Swift 3)

I have a HTML file embedded into my xCode project which has tags within it such as:
{DESCRIPTION}
{LOCATION}
{TIME_SUBMITTED}
I load the contents of the file into a String with:
let url = Bundle.main.url(forResource: "emailTemplate", withExtension: "html")
var messageBody:NSString!
do { messageBody = try String(contentsOf: url!) as NSString! }
catch { messageBody = "" }
Now I have populated "messageBody" I need to find and replace the tags based on my UI, for example:
1) find "{DESCRIPTION}" and replace it with lblDescription.text
2) find "{LOCATION}" and replace it with lblLocation.text
I am trying to use code similar to:
messageBody.enumerateSubstrings(in: NSMakeRange(0, messageBody.length), options: .byWords) { (substring, substringRange, enclosingRange, _) -> () in
print(substring!)
}
However, I am completely useless with regex and could do with some assistance to find and replace if the substring equals a tag. Any ideas please?
You don't need regex for this. Repeated calls to replacingOccurrences will do:
import Foundation
let emailTemplate = "Hello {USER}\n" +
"{DESCRIPTION}\n" +
"\n" +
"Regards."
let email = emailTemplate
.replacingOccurrences(of: "{USER}", with: "John Smith")
.replacingOccurrences(of: "{DESCRIPTION}", with: "Have a nice day")
print(email)

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