2

Question: How to write a default implementation of UITableViewDataSource by extending it?

Swift supports default implementations in protocol extensions, and UITableViewDataSource is a protocol. So why doesn't the example below work?

I tried the example below, but the table stays blank. To be sure, I added breakpoints to the default implementations, and they aren't reached. I put print methods inside but they print nothing.

This extension would make usage of basic table views almost code-less, as they'd only need a collection of entities that conform to TableItem.

This question with similar title is unrelated.

Full example:

import UIKit

/// Conform to this protocol to be immediatelly usable in table views.
protocol TableItem {
    var textLabel: String? { get }
    var detailTextLabel: String? { get }
}

protocol BasicTableDataSource {

    associatedtype TableItemType: TableItem

    var tableItems: [TableItemType]? { get set }

    /// The table view will dequeue a cell with this identifier.
    /// Leave empty to use `cellStyle`.
    var cellIdentifier: String? { get set }

    /// If `cellIdentifier` is empty, the table view will use this cell style.
    /// Leave empty to use `UITableViewCellStyle.default`.
    var cellStyle: UITableViewCellStyle? { get set }

}

extension UITableViewDataSource where Self: BasicTableDataSource {

    func tableView(
        _ tableView: UITableView,
        numberOfRowsInSection section: Int) -> Int {

        return tableItems?.count ?? 0
    }

    func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = cellIdentifier == nil
            ? UITableViewCell(
                style: cellStyle ?? .default,
                reuseIdentifier: nil)
            : tableView.dequeueReusableCell(
                withIdentifier: cellIdentifier!,
                for: indexPath)
        let tableItem = tableItems?[indexPath.row]
        cell.textLabel?.text = tableItem?.textLabel
        cell.detailTextLabel?.text = tableItem?.detailTextLabel
        return cell
    }

}

class ProductsTableViewController: UITableViewController, BasicTableDataSource {

    var cellIdentifier: String?

    var cellStyle: UITableViewCellStyle? = .subtitle

    /// Product conforms to TableItem
    var tableItems: [Product]? = Sample.someProducts()

}
João Souza
  • 4,032
  • 3
  • 25
  • 38

2 Answers2

0

Replace

extension UITableViewDataSource where Self: BasicTableDataSource

With

extension UITableViewDataSource

Since UITableViewDataSource is not confirming to BasicTableDataSource protocol. So it will not extend UITableViewDataSource

More about Protocol Constraints Read about Adding constraints to protocol extension.

Martin
  • 846
  • 1
  • 9
  • 23
0

Answer: It's not possible to write a default implementation of UITableViewDataSource methods in an extension. This is a no-go.

Xcode now shows the errors:

Non-'@objc' method 'tableView(_:numberOfRowsInSection:)' does not satisfy requirement of '@objc' protocol 'UITableViewDataSource'

Non-'@objc' method 'tableView(_:cellForRowAt:)' does not satisfy requirement of '@objc' protocol 'UITableViewDataSource'

In February 2017, this issue was officially closed as "Won't Do" by one of the Swift Core Team members with the message below. More history on this Stack Overflow topic.

This is intentional: protocol extensions cannot introduce @objc entry points due to limitations of the Objective-C runtime. If you want to add @objc entry points to NSObject, extend NSObject.

Curious thing is that Xcode only started complaining when I replaced the inheritance from UITableViewController to UIViewController. This complains nothing:

class ProductsTableViewController: UITableViewController, BasicTableDataSource {

    var cellIdentifier: String?

    var cellStyle: UITableViewCellStyle? = .subtitle

    var tableItems: [Product]? = Sample.someProducts()

    // Table already exists in UITableViewController
}

...however this makes the extension start complaining:

class ProductsTableViewController: UIViewController, BasicTableDataSource, UITableViewDataSource {
    
    var cellIdentifier: String?
    
    var cellStyle: UITableViewCellStyle? = .subtitle
    
    var tableItems: [Product]? = Sample.someProducts()

    // Adding table manually...
    
    var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView(frame: view.bounds)
        view.addSubview(tableView)
    }

}
Community
  • 1
  • 1
João Souza
  • 4,032
  • 3
  • 25
  • 38