Alright, it wasn't easy to abstract the code below but I have tried my best. You can copy paste this into Xcode commenting out the text and it should fail at: layerCollection.select()
at the end.
First, lets define a couple of protocols:
import Foundation
import UIKit
protocol LayerLike {
var identifier: String { get }
}
protocol FeatureLike {}
Here I define a class LayerBinding
which takes a generic type Layer
and Feature
; essentially wraps a layer.
class LayerBinding<Layer: LayerLike, Feature: FeatureLike> {
var layer: Layer!
init(layer: Layer) {
self.layer = layer
}
}
Here I create a protocol LayerCollection
which describes two associated types Layer
and Feature
. A LayerCollection
is basically a collection of layer bindings (that each wrap a layer).
struct CustomView {
// implementation for customFeatures...
}
protocol LayerCollection {
associatedtype Layer: LayerLike
associatedtype Feature: FeatureLike
var identifiers: [String] { get }
var layerBindings: [LayerBinding<Layer, Feature>] { get }
func configure() //works
func features(atPoint point: CGPoint, view: CustomView) -> (Feature, Layer)? //works
// concerning code
func select(layer: Layer, feature: Feature)
}
I provide the default implementation for features
API:
extension LayerCollection {
var identifiers: [String] {
get {
self.layerBindings.map { $0.layer.identifier }
}
}
func features(atPoint point: CGPoint, view: CustomView) -> (Feature, Layer)? {
for layerBinding in layerBindings {
let features = visibleFeatures(atPoint: point, layerBinding: layerBinding, view: view)
if let features = features {
for feature in features {
return (feature, layerBinding.layer)
}
}
}
return nil
}
private func visibleFeatures(
atPoint point: CGPoint,
layerBinding: LayerBinding<Layer, Feature>,
view: CustomView)
-> [Feature]? {
// here I would call customFeatures on CustomView and do some typecasting like below
// let featureCast: (SomeTypeX) -> Feature? = { $0 as? Feature }
// var features = view.customFeatures(at: point, ids: layerIds)
// let genericFeatures = features.compactMap(featureCast)
return nil
}
}
Now lets create a concrete type: ConcreteLayerACollection
:
class ConcreteFeature: FeatureLike {}
class ConcreteLayer: LayerLike {
var identifier: String = "bla"
init(v: Int) {}
}
final class ConcreteLayerACollection: LayerCollection {
typealias Feature = ConcreteFeature
typealias Layer = ConcreteLayer
var layerBindings: [LayerBinding<Layer, Feature>] = []
func configure() {
self.layerBindings = [
LayerBinding(layer: Layer(v: 5))
]
}
func select(layer: ConcreteLayer, feature: ConcreteFeature) {
print(layer, feature)
}
}
Now, there could be other concrete collections: ConcreteLayerXCollection
, ConcreteLayerYCollection
. For the sake of argument:
typealias ConcreteLayerXCollection = ConcreteLayerACollection
typealias ConcreteLayerYCollection = ConcreteLayerXCollection
Finally, there is a LayerController that's handling all these collections:
final class LayerController {
private var layerCollections: [any LayerCollection] = []
init() {
layerCollections = [
ConcreteLayerACollection(),
ConcreteLayerXCollection(),
ConcreteLayerYCollection(),
]
}
func someDelegateCallback1() {
layerCollections.forEach { layerCollection in
layerCollection.configure()
}
}
func someDelegateCallback2BasedOnSomeTouchEvent(point: CGPoint, view: CustomView) {
for layerCollection in layerCollections {
if let (feature, layer) = layerCollection.features(atPoint: point, view: view) {
// basically found a hit of sorts; here I basically say layerCollection.select()
// However ERROR: Member 'select' cannot be used on value of type 'any LayerCollection'; consider using a generic constraint instead
layerCollection.select(layer: layer, feature: feature)
}
}
}
}
Thanks for your responses and your help.