Reputation: 181
I have to populate a TableView
with some data fetched with an URLSession task
. The source is an XML file, so i parse it into the task. The result of parsing, is an array that i pass to another function that populate another array used by TableView Delegates
.
My problem is that TableView Delegates
are called before task ends, so tha table is empty when i start the app, unless a data reloading (so i know that parsing and task work fine).
Here is viewDidLoad
function. listOfApps
is my TableView
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
checkInstalledApps(apps: <ARRAY POPULATED>)
listOfApps.delegate = self
listOfApps.dataSource = self
}
}
fetchData
is the function where i fetch the XML file and parse it
func fetchData() {
let myUrl = URL(string: "<POST REQUEST>");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "firstName=James&lastName=Bond";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
self.parser = XMLParser(data: data!)
self.parser.delegate = self
}
task.resume()
}
while checkInstalledApps
is the function where i compose the array used by TableView Delegates
.
func checkInstalledApps(apps: NSMutableArray){
....
installedApps.add(...)
installedApps.add(...)
....
}
So, for example, to set the number of rows i count installedApps
elements
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (installedApps.count == 0) {
noApp = true
return 1
}
return installedApps.count
}
that are 0. Obviously, if i reload data, it's all ok.
My problem is the async call: first of that i used an XML accessible via GET request, so i can use XMLParser(contentsOf: myUrl)
and the time is not a problem. Maybe if the XML will grow up, also in this way i will have some trouble, but now i've to use a POST request
I've tried with DispatchGroup
, with a
group.enter()
before super.viewDidLoad
group.leave()
after task.resume()
group.wait()
after checkInstalledApps()
where group is let group = DispatchGroup()
, but nothing.
So, how can i tell to the tableview delegate to wait the task response and the next function?
thanks in advance
Upvotes: 0
Views: 560
Reputation: 32786
Your problem is caused by the fact that you currently have no way in knowing when the URLSession
task ended. The reloadData()
call occurs almost instantly after submitting the request, thus you see the empty table, and a later table reload is needed, though the new reload should be no sooner that the task ending.
Here's a simplified diagram of what happens:
Completion blocks provide here an easy-to-implement solution. Below you can find a very simplistic (and likely incomplete as I don't have all the details regarding the actual xml parsing) solution:
func fetchData(completion: @escaping () -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
self.parser = XMLParser(data: data!)
self.parser.delegate = self
completion()
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData() { [weak self] in self?.tableView.reloadData() }
}
Basically the completion block will complete the xml data return chain.
Upvotes: 0
Reputation: 298
The cleaner way is to use an empty state view / activityIndicator / loader / progress hud (whatever you want), informing the user that the app is fetching/loading datas,
After the fetch is done, just reload your tableview and remove the empty state view / loader
Upvotes: 0
Reputation: 19737
I would forget about DispatchGroup
, and change a way of thinking here (you don't want to freeze the UI until the response is here).
I believe you can leave the fetchData
implementation as it is.
In XMLParserDelegate.parserDidEndDocument(_:)
you will be notified that the XML has been parsed. In that method call checkInstalledApps
to populate the model data. After that simply call listOfApps.reloadData()
to tell the tableView to reload with the new data.
You want to call both checkInstalledApps
and listOfApps.reloadData()
on the main thread (using DispatchQueue.main.async {}
).
Also keep listOfApps.delegate = self
and listOfApps.dataSource = self
in viewDidLoad
as it is now.
Upvotes: 1