在 ViewModel 中我有:
public var models: [any Tabbable]
Tabbable 开头为
public protocol Tabbable: Identifiable {
associatedtype Id
var id: Id { get }
/// ...
}
在我的 ViewModel 中的 Swift 中,我可以使用 Array.forEach 来:
models.forEach { _ in
print("Test")
}
但是在 SwiftUI 中我不能使用 ForEach 来:
ForEach(viewModel.models) { _ in
Text("Test")
}
由于:
输入“任何 Tabbable”不能符合“可识别”
有什么建议吗? 这是 Swift 5.7 的情况。我需要回去制作像 AnyTabbable 这样的东西吗? 预先感谢。
以游乐场的形式考虑这个示例:
import UIKit
import SwiftUI
public protocol Tabbable: Identifiable {
associatedtype Id
var id: Id { get }
var name : String { get }
}
struct TabbableA : Tabbable{
typealias Id = Int
var id : Id = 3
var name = "TabbableA"
}
struct TabbableB : Tabbable {
typealias Id = UUID
var id : Id = UUID()
var name = "TabbableB"
}
struct ViewModel {
public var models: [any Tabbable]
}
let tabbableA = TabbableA()
let tabbableB = TabbableB()
let models: [any Tabbable] = [tabbableA, tabbableB]
struct ContentView {
@State var viewModel : ViewModel = ViewModel(models: models)
var body : some View {
ForEach(viewModel.models) { model in
Text(model.name)
}
}
}
在这种情况下,我们有类型
TabbableA
,其中每个实例都有一个 id
属性,它是一个整数(为了示例,他们只使用“3”,但它是重要的类型)。 在 TabbableB
中,每个实例的 id
都是一个 UUID。 然后我创建一个数组,其中一项是 TabableA
的实例,另一项是 TabbableB
的实例。 该数组的类型为 [any Tabbable]
。
然后我尝试在数组上使用
ForEach
。 但数组中的某些元素使用 Int
id,有些使用 UUID
id。系统不知道使用什么类型来唯一标识视图。 Ints
和 UUIDs
无法直接相互比较以确定是否相等。虽然进入数组的每个项目都是 Tabbable
,因此符合 Identifiable
,但从数组中出来的元素,每个元素都是 any Tabbable
类型,不 符合 Identifiable
。所以系统拒绝了我的代码。
另一种思考方式:类型为
any SomeProtocol
的值是一个盒子,可以包含任何符合 SomeProtocol
的内容,但盒子本身 不 符合 SomeProtocol
您可以向 id(或任何唯一变量)提供一个 KeyPath:参数,该参数指定如何检索
ID
中的 ForEach
。这是因为 ForEach
中的所有项目都必须是唯一的。您可以创建一个符合您的协议的结构。在那一刻它应该起作用。第二个选项是从协议中删除Identifiable
,然后您可以直接使用它。
public protocol Tabbable {
var id: String { get }
/// ...
}
public var models: [Tabbable]
ForEach(viewModel.models, id: \.id) { _ in
Text("Test")
}
或
public protocol TabbableType: Identifiable {
associatedtype Id
var id: Id { get }
/// ...
}
struct Tabbable: TabbableType {
var id: String { get }
/// ...
}
public var models: [Tabbable]
ForEach(viewModel.models) { _ in
Text("Test")
}
Scott的回答很好地解释了编译器抛出该错误的原因。我正在尝试一种方法,为编译器提供必要的信息来编译此代码,并找到了解决方案,但它需要向您的协议添加约束并扩展
ForEach
。
您需要将协议的
ID
类型限制为具体的 Hashable
类型,例如 UUID
。它有非常具体的语法。如果您的用例取决于不同类型的 ID
,这可能是一个缺点。
protocol Tabbable: Identifiable where ID == UUID {
var id: Self.ID { get }
}
然后您需要扩展 ForEach 并给它一个初始化程序,其中包含为您推断 id 参数所需的所有信息,同样,它具有非常具体的语法:
extension ForEach where ID == UUID, Content: View, Data.Element == any Tabbable {
init(_ data: Data, @ViewBuilder content: @escaping (any Tabbable) -> Content) {
self.init(data, id: \.id, content: content)
}
}
然后您将能够按预期将
[any Tabbable]
类型的值传递给 ForEach
。我不完全理解为什么需要扩展,但是如果没有它,除非您在初始化 id
时显式传递 ForEach
,否则代码将无法编译。