Swift 3/Xcode 8/ios 10
I have a method that fetches a bunch of data from a few api endpoints. I pick out the bits I want to keep and then format and print that data to a text file.
When the app starts up, it checks to see whether or not the text file is empty. If it is empty, then the API requests are run and the text file is populated accordingly. So far so good. This all works as expected.
I now want to show the user the progress of the data being retrieved via a progress bar. This is where I am getting stuck. I cannot seem to get the progress bar to display before and during the data retrieval. It only displays after data retrieval is complete - and indicates as much too. Via some print statements in the protocol method, I can see that the progress bar is seemingly being updated - it's just that the segue to the progress bar does not happen when I want it to.
There are 3 classes involved in the above description:
In viewDidAppear of MasterVC, the code below shows the first block of code:
if fileSize == 0{
//SHOW PROGRESS OVERLAY AND RETRIEVE API DATA
performSegue(withIdentifier: "toProgressBarOverlay", sender: self)
GetData.start() //IF THIS IS ACTIVE, DATA IS RETRIEVED BUT PROGRESS BAR APPEARS AT THE END.
}
Any code after this runs only once the text file has been populated via the API calls. I have done this via the use of semaphores contained within the API calling method. If instead, I place GetData.start() in viewDidAppear of ProgressBar, I get errors in this section relating to variables/arrays etc being empty.
From googling and searching SO, I get the impression that the late showing of the progress bar has to do with the updating of views only being done at the end of each cycle and that although I call the performSegue first, it only happens last, after the rest of the code in the class has been iterated through.
How can I make this progress bar appear as intended?
EDIT 1:
OK, so I have managed to get the progress bar to now appear at the start of the API calls. Sadly however, the progress bar is not updating properly. The updates are, however, still being reported correctly in the console. The progress bar appears with the default start value, then once the API calling is done, the progress bar suddenly shoots to 100% with no intermediate values. Using dispatchQueue.main.async/sync still does not work. Below are the changes I have made:
The code section above (which is in MasterVC) now looks like the following:
//FETCH MP INFO AND POPULATE MPDATA.TXT.
if fileSize == 0{
//SHOW PROGRESS OVERLAY AND RETRIEVE API DATA
performSegue(withIdentifier: "toProgressBarOverlay", sender: self)
}
All the code after this (which dealt with the populated text file) has been moved to its own method (called processTextFile).
So when the app starts, it checks for an empty file, and if it finds as much, segues to ProgressBar. ProgressBar also now contains a new reference to MasterVC (called masterVcReference), which is set during prepareForSegue.
ProgressBar now initiates the API calls with:
GetData.start(masterVc: masterVcReference!)
Once all the data has been retrieved and written to file, GetData uses masterVcReference to call processTextFile.
The actual protocol method is as follows:
func updateProgressBar(message: String, percentComplete: Float) {
print("Progress: \(percentComplete)%") << this is being reported correctly.
ProgressBar?.setProgress(percentComplete, animated: true)
ProgressBarMessageField.text = message
if percentComplete == 100{
//dismiss(animated: true, completion: nil)
}
}
If I place "ProgressBar?.setProgress(percentComplete, animated: true)" and "ProgressBarMessageField.text = message" in a dispatchQueue.main.async/sync block, no difference is made.
So currently, the app works as it should except for the progress bar not updating as it should.
EDIT 2:
OK, so using DispatchQueue.global(qos: .userInteractive).async, instead of dispatchQueue.main.async to update the progress bar has made a small difference. The progress bar updates, sort of. Of the 649 updates (from the 649 items of data that are processed) that the progress bar should go through, it updates only 1, 2 or maybe 3 times if I'm lucky. Also, the update frequency is not the same between each run of the app.
EDIT 3:
OK - turns out dispatchQueue.main.async has been working, but it seems to be queueing up all of the updates and executing everything right at the end instead of executing with each protocol method call. Confusion reigns.
Solved at Last!
I was using semaphores to ensure that data from the API calls were processed in the correct order. The solution was to place the "semaphore.signal()" statement BEFORE the call to the delegate method. Originally, the delegate call lay between "let semaphore = DispatchSemaphore(value: 0)" and "semaphore.signal()". Somehow (I don't understand it yet), this was causing the UI updates to queue up and not execute until all of the API data had finished processing.
Voila!
I was using semaphores to ensure that data from the API calls were processed in the correct order. The solution was to place the "semaphore.signal()" statement BEFORE the call to the delegate method. Originally, the delegate call lay between "let semaphore = DispatchSemaphore(value: 0)" and "semaphore.signal()". Somehow (I don't understand it yet), this was causing the UI updates to queue up and not execute until all of the API data had finished processing.