Please can you help me with this problem 😟
I'm pressing a button that segue into a second view controller ( modal sheet) that trigger an intensive calcucaltion process using viewDidLoad.
The first problem was that once I press the button in the first view controller it freezes ( or get stuck) for 3 to 4 seconds ( going through the clauclation loops) before it segue into the second view controller and show the results of the calculation in a tableView.
So I had to put an activity indicator and place the caclucaltion process in the background thread using Dispatchqueue, then put UI activity in the main thread to avoid the button freezing.
The outcome if this was pressing the button in the first view controller, the second view controller ( modal sheet) pops up instantly with an activity indicator spinning and then calculation results gets viewed in the tableview!!
Everything seems to be perfect, so I tried to test it again by dismissing the modal sheet by pulling it down to get back to the first view controller and press the button once more, at this moment it crashes showing : "fatal error: index out of range" and it keeps doing it each time I run it again!
if I remove the dispatch queue and the activity indicator ( spinner), everything works but with a freezed button for 3-4 seconds and then get the results. I can do this multiple times with no crash or index out or range. Only when I put the disptachQueue, it works one time then it crashes the second time.
Sometime it works twice or three times then it crashes again, I realy can't get my head around it !!!
It feels to me that somehow the backgorund thread is keeping some memory and not clearing the table after dismissing the modal sheet but I can't find any clue how to fix this or to prove it.
below is sample of the code :
// THE SECOND VIEW CONTROLLER
class PressurePipeResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var spinner: UIActivityIndicatorView!
func startSpinner () {
spinner.startAnimating()
}
func stopSpinner () {
spinner.stopAnimating()
}
func fillVC () {
let PW = round((PumpPowerkW(flow: finalFlow, p: density, dp: PdTotal, ef: finalEff))*100)/100
PumpPower.text = String ("Pump Power = \(PW) kw @ \(finalEff) % eff ")
PressurePipeHead.text = ("\(PdTotal) bar")
PressurePipeFlow.text = String("\(finalFlow) litre/min")
}
//below function is filling the result table
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return PdArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ResultCell", for: indexPath)
// row filler
let Pd = PdArray[indexPath.row]
let Fname = FittingName [indexPath.row]
let Fnumber = FittingNo [indexPath.row]
// to translate the value of friction to material name through a loop in dictionary
cell.textLabel?.text = "\(Fname) " + "\(Fnumber) " + "ΔP: \(Pd) bar"
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
startSpinner()
DispatchQueue.global(qos: .background).async {
// list of the matrix claculations to be triggered in sequence
self.matEquation1 ()
self.matEquation2 ()
self.matEquation3 ()
self.efmatEquation1()
self.efmatEquation2()
self.efmatEquation3()
self.pipelyKitAlgorithmCHECK ()
self.effeciencyValue ()
DispatchQueue.main.async {
// presenting the results of the calculation into the UI
self.fillVC()
self.tableview.reloadData()
self.hideKeyboardWhenTappedAround()
self.stopSpinner()
}
}
}
}
- the calucaltion should be over when I dismiss the modal sheet
I cannot find any code to prevent dismissing while calculating.
once the calculation is over the results are used by the table view
I cannot find any code to prevent the table view to access the Arrays while calculating
This last PdArray version gets used in the tableview.
I cannot find any code that forces the tableview to use only the last version.
Once you invoke a background task, your app gets in a multi-threaded environment and Swift Arrays are not thread-safe.
iOS would call numberOfRowsInSection while old values filled in PdArray, and later call cellForRowAt just after `PdArray.removeAll()`.
I recommend you not to access the same Array from both the main thread and the background thread.
Something like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mtPdArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ResultCell", for: indexPath)
// row filler
let Pd = mtPdArray[indexPath.row]
let Fname = mtFittingName[indexPath.row]
let Fnumber = mtFittingNo[indexPath.row]
// to translate the value of friction to material name through a loop in dictionary
cell.textLabel?.text = "\(Fname) " + "\(Fnumber) " + "ΔP: \(Pd) bar"
return cell
}
//...
override func viewDidLoad() {
super.viewDidLoad()
startSpinner()
DispatchQueue.global(qos: .background).async {
// list of the matrix claculations to be triggered in sequence
self.matEquation1()
self.matEquation2()
self.matEquation3()
self.efmatEquation1()
self.efmatEquation2()
self.efmatEquation3()
self.pipelyKitAlgorithmCHECK()
self.effeciencyValue()
DispatchQueue.main.async {
// presenting the results of the calculation into the UI
self.fillVC()
//
self.mtPdArray = self.PdArray
self.mtFittingName = self.FittingName
self.mtFittingNo = self.FittingNo
//
self.tableview.reloadData()
self.hideKeyboardWhenTappedAround()
self.stopSpinner()
}
}
}
Prepare main-thread-only Arrays and update them only in the main thread. Please try and tell me what you get.