I have found a weird issue with SwiftUI 's ForEach (and List ) where if you use an Array of subclass types where the parent class implements BindableObject , the ForEach loop insists each item is of the base class type not the Subclass you are using, see my example code below. A little experimenting has found if the subclass implements BindableObject then the issue goes away, which in the example I have shown is OK, but often is not really suitable.
Anybody seen this know how you are suppose to deal with this or perhaps this is a bug and I should raise it with Apple?
class Bar: BindableObject {
let didChange = PassthroughSubject<Bar, Never>()
let name: String
init(name aName: String) {
name = aName
}
}
class Foo: Bar {
let value: Int
init(name aName: String, value aValue: Int) {
value = aValue
super.init(name:aName)
}
}
let arrayOfFoos: Array<Foo> = [ Foo(name:"Alpha",value:12), Foo(name:"Beta",value:13)]
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // error aFoo is a Bar not a Foo
}
}
}
}
Tried this on Xcode Beta 2
I think this is not a bug but rather a "feature" of Swift type system and SwiftUI API.
If you look at the signature of ForEach
(just Cmd + Click on ForEach
)
public init(_ data: Data, content: @escaping (Data.Element.IdentifiedValue) -> Content)
you can notice that it accepts Data.Element.IdentifiedValue
type
So, from your example
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // error aFoo is a Bar not a Foo
}
}
}
}
aFoo
local value has type Foo.IdentifiedValue
Lets ask Swift what it thinks about this type:
Foo.IdentifiedValue.self == Bar.IdentifiedValue.self // true
Foo.IdentifiedValue.self == Foo.self // false
Foo.IdentifiedValue.self == Bar.self // true
As you can see, Foo.IdentifiedValue
is actually Bar
.
To bypass this we can create a wrapper using a new feature of Swift 5.1 - 'Key Path Member Lookup'! :D
I updated your example. Added AnyBindable
class and mapped elements of arrayOfFoos
to it.
class Bar: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
let name: String
init(name aName: String) {
name = aName
}
}
class Foo: Bar {
let value: Int
init(name aName: String, value aValue: Int) {
value = aValue
super.init(name:aName)
}
}
@dynamicMemberLookup
class AnyBindable<T: BindableObject>: BindableObject {
let didChange: T.PublisherType
let wrapped: T
init(wrapped: T) {
self.wrapped = wrapped
self.didChange = wrapped.didChange
}
subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
return wrapped[keyPath: keyPath]
}
}
let arrayOfFoos = [ Foo(name:"Alpha",value:12), Foo(name:"Beta",value:13)]
.map(AnyBindable.init)
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // it compiles now
}
}
}
}
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.