0

I have two classes with basically identical tableViewDelegate and tableViewDataSource implementation code. In the interest of leaving the code better than I found it, I figure I should try to reduce duplication. This is proving to be quite difficult. See the example code structure below:

class A : UICollectionViewCell {
//a bunch of code

}

extension A : UITableViewDataSource, UITableViewDelegate {
//A bunch of code that is nearly identical to the other class
}

class B : UIViewController {
//a bunch of code
}

extension B : UITableViewDataSource, UITableViewDelegate {
//A bunch of code that is nearly identical to the other class 
}

Both extensions use the same global variables from class A and B respectively. My initial idea was to create a superclass for class A and B that already has these delegates implemented. However, I don't think this will work because classes A and B are not extending the same class. I think I would have to go too far up the class hierarchy to find a superclass that they share.

Is there a good way to reduce this repeated code?

Thanks

  • Swift is more about protocol+extension instead of superclass. Depending on your concrete code this may be better. – burnsi Jul 04 '22 at 21:16
  • @burnsi Thanks for the idea, are you referring to something like this: https://stackoverflow.com/questions/38464134/how-to-make-extension-for-multiple-classes-swift So for me, I'd have a protocol that implements data source and table view delegate. The extension will have my one concrete implementation, and then both classes B and A will use this extension? – CichlidGold Jul 04 '22 at 21:46
  • The accepted answer of that question looks like the proper way to do this. – burnsi Jul 04 '22 at 22:03
  • @burnsi Thanks, I've been trying to run it in my head how this would work, but I run into the issue in that my tableView is defined in both classes by an IBoutlet, but I need access to the tableview to define my functions in the extension. Since I have my classes extending(?) the extension as class A : ExtensionClass, the ExtensionClass would not have access to the IBoutlets. Is there a way around this? – CichlidGold Jul 04 '22 at 22:47
  • Why can’t you go with the inheritance? – Jayachandra A Jul 05 '22 at 07:22

1 Answers1

0

You could try to create a shared UITableViewDataSource and UITableViewDelegate class that manages the data for both table views. In the example below a ViewModel that is generic over Item, can manage lists of Book or User instances. They share a CellModel interface that is used to configure the cell for the table view.

import UIKit

class ViewController: UITableViewController {
    struct User: CellModel {
        let displayName: String

        var title: String { displayName }
    }

    let viewModel = ViewModel<User>(
        items: [
            .init(displayName: "Hanna"),
            .init(displayName: "Jo")
        ]
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = viewModel
        tableView.delegate = viewModel
    }
}

// MARK: -

class AnotherViewController: UITableViewController {
    struct Book: CellModel {
        let title: String
    }

    let viewModel = ViewModel<Book>(
        items: [
            .init(title: "Stories from A"),
        ]
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = viewModel
        tableView.delegate = viewModel
    }
}

// MARK: -

protocol CellModel: Hashable {
    var title: String { get }
}

final class ViewModel<Item: CellModel>: NSObject, UITableViewDataSource, UITableViewDelegate {

    let items: [Item]

    init(items: [Item]) {
        self.items = items
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "CellID") else {
            return UITableViewCell(style: .default, reuseIdentifier: "CellID")
        }

        let item = items[indexPath.row]
        var content = cell.defaultContentConfiguration()
        content.text = item.title
        cell.contentConfiguration = content
        return cell
    }
}
Andreas
  • 61
  • 3