使用 SwiftUI ForEach 迭代[任何协议],其中所述协议是可识别的

问题描述 投票:0回答:3

在 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 这样的东西吗? 预先感谢。

swift swiftui
3个回答
9
投票

以游乐场的形式考虑这个示例:

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


1
投票

您可以向 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")
    }

1
投票

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
,否则代码将无法编译。

© www.soinside.com 2019 - 2024. All rights reserved.