I'm trying to display some SwiftUI Views for an object that is a member of a polymorphic hierarchy. For example, "markers" is a drawing/painting application. There are different kinds of markers with different properties - sizes, colors, etc.
I'd like the UI to conditionally display views, depending on the protocols supported by the model object. I run into problems with type casting...
protocol Marker : ObservableObject {
var name: String { get set }
}
protocol MarkerWithSize: Marker {
var size: Float { get set }
}
struct ContentView<MT: Marker>: View {
@ObservedObject var marker: MT
var body: some View {
VStack {
Text("name: \(marker.name)")
if marker is MarkerWithSize {
// This cast fails - XXX
MarkerWithSizeSection(marker: marker as! MarkerWithSize)
}
}
}
}
struct MarkerWithSizeSection<MT: MarkerWithSize>: View {
@ObservedObject var marker: MT
var body: some View {
Slider(value: $marker.size, in: 1...50)
}
}
That cast fails with the notorious error:
Protocol type 'MarkerWithSize' cannot conform to 'MarkerWithSize' because only concrete types can conform to protocols
I need some way to get a binding to the properties in the sub-protocol:
$marker.size
To use SwiftUI things like
Slider(value: $marker.size, in: 1...5)
I can refer to $marker.name in the ContentView, but not $marker.size. The $marker there is (according to Xcode) of type ObservableObject<MT>.Wrapper.
Okay, I can make it work with classes, but that's disappointing. I read all this stuff about protocols and protocol oriented programming, but then can't use it with models in SwiftUI?
Sometimes I create protocols that are only meant for classes. You can mark a protocol as such. protocol P : class { ... }
Your example doesn't quite fit my question, because you didn't put size in the subclass. I was surprised that when I did that, the example didn't work. The Slider binding won't update the size. Apparently @Published doesn't work in subclasses. But a simple workaround is to manually send the Combine event. In other words, this failed:
class MarkerA : BaseMarker {
@Published var size: Float
But this worked:
class MarkerA : BaseMarker {
var size : Float {
willSet {
objectWillChange.send()
}
}