我发现 SwiftUI 的 ForEach (和 List )存在一个奇怪的问题,如果您使用子类类型的数组,父类实现 BindableObject , ForEach 循环则坚持每一项都是基类类型而不是您正在使用的Subclass,请参见下面的示例代码。进行了一些实验,发现子类是否实现了 BindableObject ,问题就消失了,在我所显示的示例中,这是可以的,但通常并不合适。
任何人都知道您应该如何处理这个问题,或者这可能是个错误,我应该向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
}
}
}
}
答案 0 :(得分:1)
在Xcode Beta 2上对此进行了尝试
我认为这不是bug,而是Swift类型系统和SwiftUI API的“功能”。
如果您查看ForEach
的签名(只需Cmd +单击ForEach
)
public init(_ data: Data, content: @escaping (Data.Element.IdentifiedValue) -> Content)
您会注意到它接受Data.Element.IdentifiedValue
类型
因此,根据您的示例
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
本地值的类型为Foo.IdentifiedValue
让我们问一下Swift对这种类型的看法:
Foo.IdentifiedValue.self == Bar.IdentifiedValue.self // true
Foo.IdentifiedValue.self == Foo.self // false
Foo.IdentifiedValue.self == Bar.self // true
如您所见,Foo.IdentifiedValue
实际上是Bar
。
要绕过这一点,我们可以使用Swift 5.1的新功能-“关键路径成员查找”来创建包装器! :D
我更新了您的示例。向其添加了AnyBindable
类和arrayOfFoos
的映射元素。
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
}
}
}
}