Reputation: 801
I made a TableView with Swift where I show Search results of the Query that's filled in the UiTextField.
let cellReuseIdentifier = "cell"
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:SearchCell = self.tableView.dequeueReusableCell(withIdentifier:cellReuseIdentifier) as! SearchCell
cell.objectName.text = self.search_results_list[indexPath.section]
return cell
}
On the Value Changed-event I trigger the Search action:
@IBAction func searchInputChanged(_ sender: Any) {
self.search_results_list = []
let search = self.searchField.text
let parameters: Parameters = ["api_token": token, "search": search!+" "]
let URL = "https://myweb.app/api/search"
Alamofire.request(URL, parameters: parameters).responseArray { (response: DataResponse<[SearchResponse]>) in
let searchResultsArray = response.result.value
if let searchResultsArray = searchResultsArray {
for searchResult in searchResultsArray {
self.search_results_list.append(searchResult.description!)
}
}
// Then I reloeadData of the TableView
self.tableView.reloadData()
}
}
And the NumberOfSections method:
func numberOfSections(in tableView: UITableView) -> Int {
return self.search_results_list.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
It does work, but when I fill in the form quickly, the Array has the correct order, but the TableView has a different order. When I inspect the Array I see that the order IS correct, but the TableView shows a different one. When I wait for a second and add a space at the end, the TableView does show the correct order. The same when I add letter by letter and wait after each letter that the tableview is reloaded, the output is correct
What am I missing?
Upvotes: 1
Views: 71
Reputation: 11243
Like they said in the comments. It's probably because the asynchronous calls are returning at different times. And most probably the one with fewer results (newer calls) will come back faster than the ones that have more results (older calls). So what you have to do is invalidate every API response which doesn't match your current search text.
@IBAction func searchInputChanged(_ sender: Any) {
self.search_results_list = []
let search = self.searchField.text
let parameters: Parameters = ["api_token": token, "search": search!+" "]
let URL = "https://myweb.app/api/search"
Alamofire.request(URL, parameters: parameters).responseArray { (response: DataResponse<[SearchResponse]>) in
if search == self.searchTextField.text { // Compare search text with current text in searchTextField.
let searchResultsArray = response.result.value
if let searchResultsArray = searchResultsArray {
for searchResult in searchResultsArray {
self.search_results_list.append(searchResult.description!)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
IMPORTANT OPTIMIZATION TIP
You can wrap your API call in a timer so that it is hit only when the user stops entering characters into the text field. This helps avoid unnecessary API calls. You can achieve this by using a Timer
to make the network calls. Start the timer in textDidChange
and invalidate it if the delegate is called again within a short duration (0.5s or whatever you feel is optimal)
Upvotes: 1