Sunkas Sunkas -4 years ago 96
Swift Question

Call method on subclass to UITableViewCell with generic parameter value based on protocol or base class

I want to simplify the calling of a "setup"-method in UITableViewCell subclasses. However, not all setup methods are the same, but their parameters inherit from the same type. Is it possible with generics or protocol to not have to cast the parameter every time?

First I a cellForRow-method like this:

class DataSource<V : UIViewController, T: TableViewCellData, VM: ViewModel> : NSObject, UITableViewDataSource, UITableViewDelegate {

var dataCollection: TableViewDataCollection<T>!
var viewModel: VM!

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = dataCollection.object(for: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: cellData.identifier(), for: indexPath)
if let setupableCell = cell as? CellDataSetupable {
setupableCell.setup(with: cellData, viewModel: viewModel)
}
return cell
}
}

protocol CellDataSetupable : class {
func setup(with cellData: TableViewCellData, viewModel: ViewModel)
}


where I setup the cell with cellData and viewModel.

In my (many) custom UITableViewCell subclasses:

extension BlurbTableViewCell : CellDataSetupable {
func setup(with cellData: TableViewCellData, viewModel: ViewModel) {
guard let cellData = cellData as? HomeViewTableViewCellData else { return }
guard let viewModel = viewModel as? HomeViewModel else { return }

// Use cellData and viewModel to setup cell appearance
}
}


where
HomeViewTableViewCellData
is subclass of
TableViewCellData
and
HomeViewModel
is subclass of
ViewModel


Instead I want to remove the guards and directly write something like this:

extension BlurbTableViewCell : CellDataSetupable< {
func setup(with cellData: HomeViewTableViewCellData, viewModel: HomeViewModel) {
// Use cellData and viewModel to setup cell appearance
}
}


Attempted solutions (that does not work):



Any ideas or do I have to live with my castings?

Edit 1:
After suggestions from Nate Mann below I tried this code (note that I have renamed some generic types):

// This works fine
extension TransactionTableViewCell : CellDataSetupable {
typealias CellData = HomeViewTableViewCellData
typealias VM = HomeViewModel
func setup(with cellData: CellData, viewModel: VM) {
//Setup cell appearance ...
}
}


This row also works fine: (note the extra where clause)

class DataSource<VC : UIViewController, TVCD: TableViewCellData, VM: ViewModel, CDS: CellDataSetupable> : NSObject, UITableViewDataSource, UITableViewDelegate where CDS.TVCD == TVCD, CDS.VM == VM {
// ...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = dataCollection.object(for: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: cellData.identifier(), for: indexPath)
if let setupableCell = cell as? CDS {
setupableCell.setup(with: cellData, viewModel: viewModel)
}
return cell
}


But changing from

class HomeTableViewDataSource: DataSource<HomeViewController, HomeViewTableViewCellData, HomeViewModel> {


to

class HomeTableViewDataSource: DataSource<HomeViewController, HomeViewTableViewCellData, HomeViewModel, CellDataSetupable> {


give this error:

Using 'CellDataSetupable' as a concrete type confirming to protocol 'CellDataSetupable' is not supported


Edit 2: Using a concrete version of a generic baseclass for a UITableViewCell class is not either possible. See Why can't interface builder use a concrete generic subclass of of UIView?

Answer Source

My own idea of the answer is that it's not possible at the moment.

Nate Mann's answer requires me to specify a concrete implementation of CellDataSetupable which I do not want.

timaktimak's answers either needs a concrete implementation as well or requires subclassing of a UIView.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download