Advance Generics to create reusable UI

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code, it allows us to write clean and reusable code. Understanding its logic its not as straight forward than understanding its benefits and this post is not intended to explain them on detail, I think that is better to see how you can use it in a real component in your app, for this I am going to share with you a base implementation that you can reuse for any type.

We are going to create a base search controller, using generics and protocols polymorphism you will see how easy is to avoid code duplication in your code base. Lets start with a protocol that will help us to avoid dealing with strings in the view controller for reuse identifiers, create a new Project and create a new file and call it Reusable, copy and paste.

protocol Reusable {}

/// MARK: - UITableView

extension Reusable where Self: UITableViewCell  {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable {}

extension UITableView {
    func register<T: UITableViewCell>(_ :T.Type) {
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    }
    func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
            fatalError("Could not deque cell with identifier")
        }
        return cell
    }
}

This piece of code will help you avoiding use a string for cell id’s, ok first protocol done, lets now start with the UI. First we will create a cell with a generic type property, create a new file and call it BaseTableViewCell, copy and paste…

class BaseTableViewCell<V>: UITableViewCell {
    var item: V!
}

If you are not familiar with this type of syntax I suggest going here before continue and come back after :).

Ready? ok, you can see that this is a subclass of UItableViewCell with a generic property type.

Now we are going to create a generic data source object, create a new file and call it GenericTableViewDataSource, copy and paste…

final class GenericTableViewDataSource<V, T: Searchable>: NSObject, UITableViewDataSource where V: BaseTableViewCell<T> {
    /// 1
    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
    }
    
/// 2
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)
    }
    private func getModelAt(_ indexPath: IndexPath) -> T {
        return isSearchActive ? searchResults[indexPath.item] :  models[indexPath.item]
    }
    /// 3 external function for searching
    func search(query: String) {
        isSearchActive = !query.isEmpty
        searchResults = models.filter {
            let queryToFind = $0.query.range(of: query, options: NSString.CompareOptions.caseInsensitive)
            return (queryToFind != nil)
        }
    }
}

Ok, lets see this in detail, here you have a generic UITableViewDataSource sub class, it has two generic types constraints you can see the V and T here? well this just means that V can be any cell as long is a subclass of a BaseTableViewCell and T is any object that conforms to a protocol called “Searchable” (no worries we are going to create Searchable in one sec) but what I want to highlight is that by using this protocol we will be able to perform a search based on a query for any type of model displayed in a BaseTableViewSearchController that we will create after, lets see this in detail…

/// 1 Here we have the properties that will hold our objects and display them in the tableview.

/// 2 Standard UITableviewDataSource methods and a helper function that returns an object at indexPath.

/// 3 This function is the one that allow us the search, let’s say that our object is a Person and we want to search it using her/his name.

Lets create now Searchable to satisfy the compiler, create a new file and call it Searchable, copy and paste…

protocol Searchable {
    var query: String { get }
}

This protocol has only a getter property, when any model adopts this protocol it will must have this query property, then the search function in the dataSource will do the job filtering results based on the query.

Finally, lets create the BaseTableViewSearchController, create a new file and call it BaseTableViewSearchController, copy and paste…

class BaseTableViewSearchController<T: BaseTableViewCell<V>, V>: UITableViewController, UISearchBarDelegate where V: Searchable {
    
/// 1
private var strongDataSource: GenericTableViewDataSource<T, V>?
    private let searchController = UISearchController(searchResultsController: nil)
    
/// 2
var models: [V] = [] {
        didSet {
            DispatchQueue.main.async {
                self.strongDataSource = GenericTableViewDataSource(models: self.models, configureCell: { cell, model in
                    cell.item = model
                    return cell
                })
                self.tableView.dataSource = self.strongDataSource
                self.tableView.reloadData()
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(T.self)
        setUpSearchBar()
    }
    
/// 3
private func setUpSearchBar() {
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = false
        searchController.dimsBackgroundDuringPresentation = false
        searchController.searchBar.delegate = self
    }
    
/// 4
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        strongDataSource?.search(query: searchText)
        self.tableView.reloadData()
    }
}

Lets go piece by piece here, first as you can see this controller also uses generics with type constraints, so whats V and T here? well again is just a BaseTableViewCell and a model that conforms to Searchable, lets see this in detail…

/// 1 datasource objects needs to be declared as a property.

/// 2 public property models that when is set it initialize the generic datasource object and assigns it to the tableview data source.

/// 3 UI configuration for a standard search bar.

/// 4 UISearchBarDelegate method that calls the search function of the generic data source and reloads the table view with the results.

Thats it, I know that all this code can be difficult to understand but it will become more natural if you review it a few times and start practicing creating your own generic models, ok lets put this in practice, now that the world cup is all over lets create a SoccerPlayer model, create a new file and call it SoccerPlayer, copy and paste…

struct SoccerPlayer: Searchable {
    var query: String {
        return name
    }
    let name: String
}

This object has a name property and it also conforms to Searchable, and because the query property is a getter we can return here the name as a value, of course this object can have a last name and address, you get the picture? you just need to return the value of the property in the query getter (as long is a string) that suits the search functionality on your app.

Now lets subclass the BaseTableViewCell, create a new file and call it SoccerCell, copy and paste…

class SoccerCell: BaseTableViewCell<SoccerPlayer> {
    override var item: SoccerPlayer? {
        didSet {
            textLabel?.text = item?.name
        }
    }
}

This generic cell has SoccerPlayer as the generic object type, you can see that by overriding the item property it infers it as SoccerPlayer type for this instance object, now lets present this in the generic search controller you will be amazed of the amount of work that is already done, create a new file for one last time and call it ListTableViewController, copy and paste…

/// 1
class ListTableViewController: BaseTableViewSearchController<SoccerCell, SoccerPlayer> {
    
override func viewDidLoad() {
        super.viewDidLoad()
        
/// 2
let soccerPlayers = [SoccerPlayer(name: "Messi"), SoccerPlayer(name: "Ronaldo"), SoccerPlayer(name: "Modric"), SoccerPlayer(name: "Guerrero"), SoccerPlayer(name: "Rodriguez"), SoccerPlayer(name: "Kane"), SoccerPlayer(name: "Ramos"), SoccerPlayer(name: "Pique"), SoccerPlayer(name: "Mbape"), SoccerPlayer(name: "Pogba"), SoccerPlayer(name: "Zidane"), SoccerPlayer(name: "Kross"), SoccerPlayer(name: "Puyol"), SoccerPlayer(name: "Beckham")]
        self.models = soccerPlayers
    }
}

This is it, this is pretty much you need to create a generic controller with a search functionality, let me explain what is going on here…

/// 1 The only thing that you need when subclassing a generic object is that you declare the type of models that will be infer by it, you see here that SoccerCell replaces “T” and SoccerPlayer replace “V”.

/// 2 dummy data to be used for the data source, setting the models array will do the rest, you can also use an array that comes from a server request of course.

Now run the app and search for the best player in the world, you are right type “Messi” and see how the UI displays the result.

I hope this can help you understand the power of generics and how to take advantage of protocols to write reusable and clean code.

Here is the complete sample project and here a great explanation of generics that I used as inspiration for this post.

Let me know how do you use generics in your projects.


More where this came from

This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more product & design stories featured by the Journal team.