I have designed my search component based on this tutorial.
It works good in case I have one section in table view, so I don't need to worry about nested things.
Here is my code:
import UIKit
protocol Searchable {
var query: String { get }
var isSelected: Bool { get set }
}
class BaseSearchDataSource<V, T: Searchable>: NSObject, UITableViewDataSource where V: BaseTableViewCell<T> {
private var models: [T]
private let configureCell: CellConfiguration
typealias CellConfiguration = (V, T) -> V
private var searchResults: [T] = []
private var isSearchActive: Bool = false
init(models: [T], configureCell: @escaping CellConfiguration) {
self.models = models
self.configureCell = configureCell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isSearchActive ? searchResults.count : models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: V = tableView.dequeueReusableCell(forIndexPath: indexPath)
let model = getModelAt(indexPath)
return configureCell(cell, model)
}
func getModelAt(_ indexPath: IndexPath) -> T {
return isSearchActive ? searchResults[indexPath.item] : models[indexPath.item]
}
func search(query: String) {
isSearchActive = !query.isEmpty
searchResults = models.filter {
let queryToFind = $0.query.range(of: query, options: NSString.CompareOptions.caseInsensitive)
return (queryToFind != nil)
}
}
}
I have this class implementation which conforms to a protocol specified:
class MuscleSelectableItem: Searchable {
var query: String {
return name
}
var isSelected: Bool
let name: String
let muscle: MusclEntity
init (isSelected: Bool, name: String, muscle: MusclEntity) {
self.isSelected = isSelected
self.name = name
self.muscle = muscle
}
}
So now when I use my subclass of parent BaseSearchDataSource I can simple specify a class I want to load to my table view and make this class searchable. So now my var models: [T]
represents models as [MuscleSelectableItem]
I understand that I need to use some section object with nested items kind of:
class TableViewSection {
var items: [MuscleSelectableItem]
}
But the problem with code above that I specified concrete type [MuscleSelectableItem]
for items
.
How to declare [T]
which will be a TableViewSection
with items
which are undefined until we tell compiler in subclasses what type we want to use for items
, like search for cities, muscles or books or any other entities, so items
could be any of them
Also what I don't like as well that my protocol contains search and select functionality, how to separate it correctly?
You can use generics in your wrapper as well. In the above case it would look like this:
class TableViewSection<T> {
var items = [T]()
}
You could also build a TableDataSource class that has an array of table view sections, which would open up opportunities such as subscripting based on indexPath and then conforming it to the collection protocol in order to get functionality such as filter, count, etc.
Also what I don't like as well that my protocol contains search and select functionality, how to separate it correctly?
Swift allows for composing protocols of smaller protocols using typealiasing. The above could be divided like so:
protocol Searchable {
var query: String { get }
}
protocol Selectable {
var isSelected: Bool { get set }
}
typealias SearchAndSelectable = Searchable & Selectable
A variable of type SearchAndSelectable will have both the 'query' and 'isSelected' values on it, which is convenient for generic constraints or functions that can take advantage of both protocol types.