I have successfully parsed a JSON file with the following data model into my project and my tableview.
import Foundation
struct ActionResult: Codable {
let data: [Datum]
}
struct Datum: Codable {
let goalTitle, goalDescription, goalImage: String
let action: [Action]
}
struct Action: Codable {
let actionID: Int
let actionTit: String
}
Now I am trying to create a searchbar to search on the "actionTitle". My tableview has section headers and rows.
Relevant code:
var filteredData: [Action]?
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
filteredData = ????
navigationItem.searchController = searchController
parseJSON()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == ""{
filteredData = ????
}
else {
for actions in ???? {
if actions.lowercased().contains(searchText.lowercased()) {
filteredData.append(actions)
}
self.tableView.reloadData()
}
I do not know what code to use where I have ????.
Thanks.
You want to keep an instance of all of the available data (allActions
), and always show your filter data (filteredData
) in the tableView. So when there is nothing to filter, filteredData
is equal to allActions
(unless you intend to hide all data when the search is empty).
When searchBar(_:,textDidChange:)
is called, you can use filter(_:) to evaluate if the item should be included. Apple's description on the filter closure:
A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
I don't know if there is a specific reason for declaring filteredData: [Action]?
, is it because the data is not populated until parseJSON()
is called? If so--I suggest initializing an empty arrays and populating them when the data is available.
Also, does parseData()
produce an instance of Datum
? I believe this piece of your code is not included, so I am adding datum: Datum?
.
If I am wrong, please provide more info what parseJSON()
populates and I will update my answer.
var result: ActionResult? {
didSet {
guard let result = result else { return }
allSectionDataActionMap = Dictionary(uniqueKeysWithValues: result.data.enumerated().map { ($0.0, ($0.1, $0.1.actions)) })
updateFilteredData()
}
}
var allSectionDataActionMap = [Int: (datum: Datum, actions: [Action])]()
// Maps the section index to the Datum & filtered [Action]
var filteredSectionDataActions = [Int: (datum: Datum, actions: [Action])]()
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
// ...
parseJSON()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredSectionDataActions.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSectionDataActions[section]?.actions.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
if let action = filteredSectionDataActions[indexPath.section]?.actions[indexPath.row] {
// setup cell for action
cell.textLabel?.text = action.actionTitle
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateFilteredData(for: searchText.lowercased())
tableView.reloadData()
}
func updateFilteredData(for searchText: String = String()) {
if searchText.isEmpty {
filteredSectionDataActions = allSectionDataActionMap
} else {
for (index, (datum, actions)) in allSectionDataActionMap {
let filteredActions = actions.filter { $0.actionTitle.lowercased().contains(searchText) }
if filteredActions.isEmpty {
filteredSectionDataActions[index] = (datum, actions)
} else {
filteredSectionDataActions[index] = (datum, filteredActions)
}
}
}
}