I have two protocols, one for a ViewModel and one for a ConfigurableView that takes the ViewModel type as an associated type.:
public protocol ViewModel {}
public protocol ConfigurableView {
associatedtype ViewModelType: ViewModel
func configure(with viewModel: ViewModelType)
}
In my method that configures an abstract view with an abstract model:
let viewModel = getMyViewModel() // returns ViewModel
if let configurableView = cell as? ConfigurableView {
configurableView.configure(with: viewModel)
}
I get "Protocol 'ConfigurableView' can only be used as a generic constraint because it has Self or associated type requirements".
How do I tell the compiler that I want to configure the view with whatever associated type this instance has, if it's a ConfigurableView instance?
I actually found what I think is a decent solution that didn't require too much mangling of my architecture. Thanks to @lib for putting me on the right path. The trick was to have a protocol above that doesn't have an associatedType requirement with an extension that casts the generic ViewModel to the associatedType of the specific one. I believe this is type erasure? But it doesn't look like any of the examples I read.
public protocol ViewModel {}
/*
This parent protocol exists so callers can call configure on
a ConfigurableView they don't know the specific type of.
*/
public protocol AnyConfigurableView {
func configure(with anyViewModel: ViewModel)
}
public protocol ConfigurableView: AnyConfigurableView {
associatedtype ViewModelType: ViewModel
func configure(with viewModel: ViewModelType)
}
/*
This extension does the trick of converting from the generic
form of ConfigurableView to the specific form.
*/
public extension ConfigurableView {
func configure(with anyViewModel: ViewModel) {
guard let viewModel = anyViewModel as? ViewModelType else {
return
}
configure(with: viewModel)
}
}
Usage:
let viewModel = getViewModel()
(someView as? AnyConfigurableView)?.configure(with: viewModel)
You cannot use Generic Protocols other way than type constraints. Without generic type defined, compiler cannot compare type conformity. If I understood you correctly, then you need to define generic CellConfigurator
class. One of possible solutions below:
protocol ConfigurableCell {
associatedtype DataType
func configure(viewModel: DataType?)
}
protocol CollectionViewCellConfigurator {
static var reuseId: String { get }
func configure(cell: UICollectionViewCell)
var item: UniqueIdentifiable? { get }
}
final class CellConfigurator<CellType: ConfigurableCell, DataType>: CollectionViewCellConfigurator where CellType.DataType == DataType, CellType: UICollectionViewCell {
/// Cell Reuse identifier
static var reuseId: String { return CellType.reuseId }
/// Configures cell and populates it with `viewModel`
///
/// - Parameter cell: Cell to configure
func configure(cell: UICollectionViewCell) {
(cell as! CellType).configure(viewModel: item as? DataType)
}
/// Initializer
///
/// - Parameter item: Data item (usually ViewModel of the cell)
init(item: DataType?) {
self.item = item
}
}
Your data source will now operate with CellConfigurators
looking like CellConfigurator<CellType /*UI(CollectionView/TableView)Cell subclass*/, CellData /*Data you need to populate to the cell*/>(item: cellData)
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let configItem = yourDataSource.rows[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: configItem).reuseId, for: indexPath)
configItem.configure(cell: cell)
return cell
}
Hope it helps. Good luck
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.