Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a more reusable UITableView subclass with default behaviour

Many of my tableViews need a method which loads in more data when the last cell is displayed on the screen. To avoid implementing the same method 100s of times, I decided to make my own base subclass of UITableView, which will, upon displaying the last cell, call tableView delegate (the view controller) for a method, asking it to load more data, and if data loading was successful, will reload a table view. I though this is quite reasonable, especially when nearly every table view in my app needs this feature.

Unfortunately, tableView:willDisplayCell:forRowAtIndexPath: is a UITableViewDelegate method. And I am pretty sure, that making a UITableView subclass be a UITableViewDelegate is wrong. So, that would be I need to make a base UITableViewController class. But if I implement a base UITableViewController, I will probably want to override tableView:willDisplayCell:forRowAtIndexPath: in the subclass.

Also, some of my tableViews are embedded in a ViewController, not TableViewController (as there is other stuff going, other than the table View), so that would mean I need a separate UITableViewController and UIViewController subclasses. So for this is just making things more complicated.

So how do I make this "load more data upon displaying the last cell" feature reusable (following MVC guidelines)?

EDIT: So I think I have figured out how to start. I want to have a base TableViewDelegateClass, e.g. BaseTableViewDelegate. Then, I want to subclass it, override the tableView:willDisplayCell:forRowAtIndexPath:, but call super.tableView:willDisplayCell:forRowAtIndexPath. Now, I still have an issue. I want to make it reload data. But I feel like having a reference to tableView in tableView delegate is a bad idea (could be a reference loop). Is there a nice solution with protocols?

EDIT3: I am also thinking of another approach:

protocol MyTableViewDelegate: UITableViewDelegate {
    
    func default_tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
    func loadMoreData()
}

extension MyTableViewDelegate {
    
    func default_tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
    {
        if(indexPath.section == tableView.numberOfSections - 1 && indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 )
        {
            self.loadMoreData()
        }
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
    {
        self.default_tableView(tableView, willDisplay: cell, forRowAt: indexPath)
        //do something else here that is not done in default_tableView
    }
    
}

class MyView: UIView, MyTableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
    {
        self.default_tableView(tableView, willDisplay: cell, forRowAt: indexPath)
    }

    func loadMoreData()
    {
        loadMoreDataFromServer(){
            tableView.reloadData()
        }   
    }
}

But this looks a bit ugly.

like image 362
Ilya Lapan Avatar asked May 20 '26 23:05

Ilya Lapan


1 Answers

You can create an extension for UITableViewDelegate and write a default implementation for tableView:willDisplayCell:forRowAtIndexPath: in it. Then each instance of your subclass will call this implementation.

extension UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // do what you need here, each ViewController that conforms to UITableViewDelegate will execute this implementation unless told otherwise
    }
}

EDIT: Another option is creating a base ViewController for all the ViewControllers that need that behaviour. In this case you don't need to subclass UITableView.

You create a class like this:

class BaseViewControllerForReloadingTables: UIViewController, UITableViewDelegate {
    private var needToLoadMoreData: Bool = false
    @IBOutlet var reloadingTableView: UITableView!

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if needToLoadMoreData {
            loadMoreData()
        }
    }


    private func loadMoreData() {
        //load data
        //if loading was successful 
        if reloadingTableView != nil {
            reloadingTableView.reloadData()
        }
    }
}

Then each ViewController that needs to load more data to the table just inherits from this class. For example:

class ReloadingTableViewController1: BaseViewControllerForReloadingTables {

}

and:

class ReloadingTableViewController2: BaseViewControllerForReloadingTables {

}

In these subclasses you have the UITableView which is reloadingTableView. When its delegate function is called, the implementation in the superclass is executed. This way the data loading and table reloading are handled in one place, but in appropriate ways for each of your ViewControllers.

Of course, you can add more common functions to the BaseViewControllerForReloadingTables class - depends on how much similar behaviour your screens have.

Hope it helps.

like image 99
Max Pevsner Avatar answered May 22 '26 14:05

Max Pevsner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!