CSVImporter starts importing after viewdidload [Swift] - swift

I want to import data from a .csv file, so I have used the CSVImporter https://github.com/Flinesoft/CSVImporter. It works well, but it starts the importing before the other part of the function viewDidLoad is executed.
The following code is only a test but I need either a solution that ensures that the CSVImporter completes importing before the other viewDidLoad code executes or a function which starts automatically after viewDidLoad.
Here is my code:
var Vokabeln: [[String]]?
var i = 0
override func viewDidLoad() {
super.viewDidLoad()
let path = "/Users/---CENSORED---/Documents/TestLoĢˆschen/TestLoĢˆschen/Vokabeln.csv"
let importer = CSVImporter<[String]>(path: path, delimiter: ";")
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
self.Vokabeln?[self.i][0] = record[0]
self.Vokabeln?[self.i][1] = record[1]
self.Vokabeln?[self.i][2] = record[2]
print("Begin1")
print(record[0])
print(record[1])
print(record[2])
print("End1")
self.i += 1
}
}
print("Begin2")
print(Vokabeln?[0][0])
print(Vokabeln?[0][1])
print(Vokabeln?[0][2])
print(Vokabeln?[1][0])
print(Vokabeln?[1][1])
print(Vokabeln?[1][2])
print("End2")
}
So first it prints "Begin2" and 6 times prints nil. Then, when the function viewDidLoad is finished, it prints "Begin1", then the correct variables and "End1"
Can anybody help me?
Thanks.

startImportingRecords works asynchronously, just put the code to print the Vokabeln in the completion handler after the repeat loop.
First of all you need to initialize the array otherwise nothing will be appended. And do not declare the array as optional and variable names are supposed to start with a lowercase letter.
var vokabeln = [[String]]()
In case to update the UI wrap the code in a dispatch block for example
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
self.vokabeln[self.i][0] = record[0]
self.vokabeln[self.i][1] = record[1]
self.vokabeln[self.i][2] = record[2]
print("Begin1")
print(record[0])
print(record[1])
print(record[2])
print("End1")
self.i += 1
}
DispatchQueue.main.async {
print("Begin2")
print(self.vokabeln[0][0])
print(self.vokabeln[0][1])
print(self.vokabeln[0][2])
print(self.vokabeln[1][0])
print(self.vokabeln[1][1])
print(self.vokabeln[1][2])
print("End2")
}
}
But there is still another issue. If you declare the array as [[String]] both outer and inner arrays are empty and you cannot assign values with index subscripting. I recommend this syntax
for record in importedRecords {
self.vokabeln.append(record) // this appends the whole record array
print("Begin1")
print(record)
print("End1")
}
PS: Consider to use a more suitable text format like JSON or property list.

Related

Swift Realm Results to Editable object or array

OK, I understand that I can not modify the results of a Realm Object.
So what is best way to change the data.
First I get all the Realm data as Results< Month >
let m = Month.getAllEntriesByDateAsc()
Now I need to loop through all the data to modify it. (This is a function to recalculate the entire table data.)
So I want to loop through the data and do something like:
for i in m {
var d = i
// perform calculations like
d.value = 9999
}
I want to do all the modifying on d.
Is these some sort of mapping I can use to create the new edible object from the Realm data?
Previously I did something like this:
for i in m {
let d = Month()
d.value = i.value
d.status = i.status
}
But there are now to many variables.
I guest what I need to so change the Realm Object to the Model object?
And the .toArray() stuff will not work inside the loop? Not sure why.
Thanks.
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for i in 0 ..< count {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
From here

How to avoid unnecessary initialization in helper function returning an object?

I have a helper function called fileByIndex. This function takes an index parameter, and then uses this parameter to find an existing object, and returns this existing object. In this process, the code initializes an object with var file = File() line, then assigns an existing object to this reference. Here the initialization is actually unnecessary. I wonder if there is any way to avoid this unnecessary initialization & deinitialization.
I thought of (1) Creating a dummy File object for this purpose in global context (2) Giving this dummy object to this helper function as an inout parameter and returning it, but I feel this is not a good approach too.
Is there a proper way to avoid this?
class File {
}
let newFile1 = File()
let newFile2 = File()
let oldFile1 = File()
let oldFile2 = File()
var newFiles = [newFile1, newFile2]
var oldFiles = [oldFile1, oldFile2]
func fileByIndex(index: Int) -> File {
var file = File()
if index > 0 {
file = newFiles[index]
} else {
file = oldFiles[index]
}
return file
}
You can just declare the variable and not give it value:
func fileByIndex(index: Int) -> File {
var file: File
if index > 0 {
file = newFiles[index]
} else {
file = oldFiles[index]
}
return file
}
In fact, file could be declared as a let in this case:
let file: File
You can use ternary operator like this.
func fileByIndex(index: Int) -> File {
return index > 0 ? newFiles[index] : oldFiles[index]
}

How to change empty input value to empty array?

var input = [readLine() ?? ""]
If I just entered, input has [""]
If I do not input anything, I want to make the input an empty list.
How can I do it?
This is because I want the count of the input to be zero when the input is empty.
You can use an IF statement to check if the input was an empty string and if so, then set the input to an empty array. There may be a better way to do this but I think this will work.
if input == [""] {
input = []
}
I hope this helped.
Another way to do this is to define your own function that reads the line or returns an empty array:
private func myReadLine() -> [String] {
let line = readLine()
if line == [""] {
return []
} else {
return line
}
}
And then at the call site you can write:
var input = myReadLine()
Which keeps the logic separated from the calling code and is easier to read. It also has the added advantage of being a lot easier to change if you want to amend your input handling conditions later on.
Simply filter out empty values:
input = input.filter { !$0.isEmpty }
or even:
let input = [readLine()]
.compactMap { $0 } // remove nil
.filter { !$0.isEmpty } // remove empty strings

Swift: Remove only the first object from the NSUserDefaults

I create the Bus Ticket Booking App. so I have Dictionary with the Array in it. In which the recent Route search result is save into the NSUserDefaults. Now I want to print that result into the one TableView.
Now in that TableView I only want to show the last 10 That's all I can do perfectly but the problem is every time the result is save in to the UserDefaults so the size of the UserDefaults is increase so I just want to remove the Defaults So every time my array element remove Repeat While loop is call the number of the element in the UserDefaults (If I i have 30 element into it it run 20 times so like). This is what i'm doing for Dictionary.
var arrSearch = [[String:Any]]()
var searchBusVC:SearchBusViewController?
override func viewDidLoad() {
super.viewDidLoad()
if let arr = UserDefault["Search"] {
arrSearch.append(contentsOf: (arr as! [[String:Any]]))
repeat{
arrSearch.removeFirst()
}while (arrSearch.count > 10)
arrSearch.reverse()
}
self.tblSearch.reloadData()
}
So I want to remove the same from the UserDefaults Is it possible? This is my Table View Image.
If you are saving [[String: Any]] in UserDefaults means its like a normal way to remove objects from array. Once you done removing objects from array then save it to UserDefaults.
var recentRecords:[[String: Any]]? {
get {
return UserDefaults.standard.array(forKey:"recentRecord")
}
set {
var trimmed: [[String: Any]] = newValue
// Trim last 10 objects from array
if newValue.count >= 10 {
trimmed = newValue.suffix(10)
}
UserDefaults.standard.set(trimmed, forKey: "recentRecord")
}
}

Modifying struct instance using call to name in function parameter

I am attempting to use Parse to call up some variables and put them into a struct that is already initialized. The calling of the variables is happening smoothly and the data is available, but the inputing of the class into the function is not happening.
'unit' is a struct that has the name, hp, attack, etc. variables contained within it.
Is it not possible to pass along an instance of a struct and modify it's values like this? It would save me a lot of copy-pasting code to do it this way.
Thanks for your help!
func fetchStats(name: String, inout nameOfClass: unit) {
var unitStatArray = []
let query = PFQuery(className: "UnitStats")
query.whereKey("name", equalTo: name)
query.findObjectsInBackgroundWithBlock{(objects:[PFObject]?, error: NSError?)->Void in
if (error == nil && objects != nil){ unitStatArray = objects! }
nameOfClass.name = "\(unitStatArray[0].objectForKey("name")!)"
print("class name is \(nameOfClass.name)")
print("cannon name is \(cannon.name)")
nameOfClass.hitPoints = unitStatArray[0].objectForKey("hitPoints") as! Double
nameOfClass.hitPointsMax = unitStatArray[0].objectForKey("hitPointsMax") as! Double
nameOfClass.attack = unitStatArray[0].objectForKey("attack") as! Double
nameOfClass.defense = unitStatArray[0].objectForKey("defense") as! Double
nameOfClass.rangedAttack = unitStatArray[0].objectForKey("rangedAttack") as! Double
nameOfClass.rangedDefense = unitStatArray[0].objectForKey("rangedDefense") as! Double
nameOfClass.cost = unitStatArray[0].objectForKey("cost") as! Int
}
}
fetchStats("3-inch Ordnance Rifle", nameOfClass: &cannon)
This is an attempt to explain what I had in mind when writing my comment above.
Because there's an asynchronous call to findObjectsInBackgroundWithBlock, the inout won't help you here. The idea is to add a callback fetched like this:
func fetchStats(name: String, var nameOfClass: unit, fetched: unit -> ()) {
// your code as above
query.findObjectsInBackgroundWithBlock {
// your code as above plus the following statement:
fetched(nameOfClass)
}
}
This can be called with
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
nameOfClass = newNameOfClass
}
(all of this code has not been tested)
The point is that you understand that your code is asynchronous (I know, I'm repeating myself). After you have called fetchStats you don't know when the callback (here: the assignment nameOfClass = newNameOfClass) will be executed. You cannot assume the assignment has been done after fetchStats has returned.
So whatever you need to do with the changed nameOfClass: the corresponding statements must go into the callback:
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
// do whatever you want with the received newNameOfClass
}
Hope this helps.