Search code examples
iosswiftuitableviewuisearchbaruisearchcontroller

Swift generic base search functionality through section in UITableVIew


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?


Solution

  • 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.