Reputation: 1449
I have an app which reloads data (by running two queries, appending the queried info to arrays, and the reloading tableview data). Both queries are set up as function which run on viewDidLoad()
but are also linked to a refreshing function (which the user can manually activate with a pull to refresh). The queries work fine, however, an issue occasionally arises while the query functions are being run (but not yet completed) by the viewDidLoad()
and the user tries to pull to refresh before it is complete... duplicates are added to the array and I would like to avoid that. The idea I had was to set a checking variable initially to false, change it to true after the viewDidLoad() had completed, and only allow the pull to refresh function to work after the variable had been changed to true. Here is the set up of the function:
func myQueryandAppend(completion: (() -> Void)?){
myCreatorArray.removeAll(keepingCapacity: true)
//repeated for all arrays
let myQuery = PFQuery(className: "Posts")
myQuery.whereKey("User", equalTo: currentUserId)
myQuery.findObjectsInBackground { (objects, error) in
if let objectss = objects{
for object in objectss {
//append the arrays
self.tableView.reloadData()
}
}
})
}
The other function is essentially identical to this one but pulls slightly different info and appends different arrays. (Side note... not entirely sure if the self.tableView.reloadData()
is in the correct spot...)
This is the viewDidLoad():
var checkerVar = false
override func viewDidLoad() {
super.viewDidLoad()
print("It loaded")
myQueryandAppend(completion: {
self.tableView.reloadData()
print("myQuery was called in viewDidLoad()")//never called
checkerVar = true
})
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(MainPageVC.refresh(_:)), for: UIControlEvents.valueChanged)
tableView.addSubview(refreshControl)
self.navigationController?.navigationBar.isHidden = true
checkerVar = true //isn't changed on *completion* of the function
}
Then the refresh handler:
func refresh(_ sender: AnyObject){
if checkerVar == true{
myQueryandAppend(completion: {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
})
self.refreshControl.endRefreshing()
}else{
self.refreshControl.endRefreshing()
}
}
The problem is, with the checkerVar, the instance within the completion in the viewDidLoad() closure is never called (the print statement above it is never logged), and I believe the second instance, at the bottom of viewDidLoad() is done immediately, without actually waiting for the function to complete. Also, no print statements within any of the (completion:{})
are ever logged.
So: 1. Why are the print statements within the completions not being called and (more importantly) 2. how do I get the checkerVar to only be changed to true after viewDidLoad()
is complete?
Upvotes: 1
Views: 597
Reputation: 535944
(Side note... not entirely sure if the
self.tableView.reloadData()
is in the correct spot...)
It's not. You are calling reloadData
multiple times, once for every object in objects
. That's wasteful. You should write this:
if let objectss = objects{
for object in objectss {
//append the arrays
}
self.tableView.reloadData()
}
Also, I wouldn't count on this code running on the main thread — but you must make sure reloadData
is called on the main thread. So now we have this:
if let objectss = objects{
for object in objectss {
//append the arrays
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Finally, as you've already been told, you are failing to call your own completion
handler. But wait. You are also calling reloadData
in your completion
handler. So now we can delete all of that and just call completion
:
if let objectss = objects{
for object in objectss {
//append the arrays
}
DispatchQueue.main.async {
completion()
}
}
Now, however, we run into a slight difficulty, as there are two cases to consider. What if the if let
fails? We still need to call the completion handler, so we need to move the call to completion
so that we are sure it takes place no matter what:
if let objectss = objects{
for object in objectss {
//append the arrays
}
}
DispatchQueue.main.async {
completion()
}
And with that final change, I think the code will do what you wanted it to do.
Upvotes: 1
Reputation: 78
Your method "myQueryandAppend"'s completion closure was never called, so the completion closure never fired.
So after your for statement:
for object in objectss {
//append the arrays
self.tableView.reloadData()
}
add:
completion()
to trigger the closure.
I believe once you fix this issue, everything would fall in place easily as your check var would be updated as you expect (maybe you should name it to something like isQuerying though.
Upvotes: 1