如果数组是 ObservableObject 的成员,如何绑定数组和列表?

How to bind an array and List if the array is a member of ObservableObject?

我想创建 MyViewModel 从网络获取数据然后更新结果数组。 MyView 应该订阅 $model.results 并显示 List 填充的结果。

不幸的是,我收到有关 "Type of expression is ambiguous without more context" 的错误消息。

在这种情况下如何正确使用 ForEach

import SwiftUI
import Combine

class MyViewModel: ObservableObject {
    @Published var results: [String] = []

    init() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.results = ["Hello", "World", "!!!"]
        }
    }
}

struct MyView: View {
    @ObservedObject var model: MyViewModel

    var body: some View {
        VStack {
            List {
                ForEach($model.results) { text in
                    Text(text)
                 // ^--- Type of expression is ambiguous without more context
                }
            }
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView(model: MyViewModel())
    }
}

P.S。如果我用 @State var results: [String] 替换模型一切正常,但我需要单独的 class MyViewModel: ObservableObject 用于我的目的

修复

将您的 ForEach 块更改为

ForEach(model.results, id: \.self) { text in
    Text(text)
}

说明

SwiftUI 的错误消息在这里对您没有任何帮助。真正的错误消息(如果将 Text(text) 更改为 Text(text as String) 并删除 model.results 之前的 $,您将看到)是 "Generic parameter 'ID' could not be inferred".

换句话说,要使用 ForEach,您正在迭代的元素需要以两种方式之一进行唯一标识。

  1. 如果元素是结构体或class,您可以通过添加属性 var id: Hashable使其符合可识别协议。在这种情况下,您不需要 id 参数。
  2. 另一种选择是使用 id 参数明确告诉 ForEach 使用什么作为唯一标识符。 更新: 由您来保证您的集合没有重复的元素。如果两个元素具有相同的 ID,则对一个视图所做的任何更改(如偏移量)都会对两个视图发生影响。

在本例中,我们选择了选项 2 并告诉 ForEach 使用 String 元素本身作为标识符 (\.self)。我们可以这样做,因为 String 符合 Hashable 协议。

$呢?

SwiftUI 中的大多数视图仅采用您应用的状态并根据它布置它们的外观。在此示例中,Text 视图仅获取存储在模型中的信息并显示它。但是有些视图需要能够返回并修改应用程序的状态以响应用户:

  • Toggle 需要更新 Bool 值以响应开关
  • Slider 需要更新 Double 值以响应幻灯片
  • TextField 需要更新 String 值以响应键入

我们确定应用程序状态和视图之间应该存在这种双向通信的方法是使用 Binding<SomeType>。因此,Toggle 需要您向其传递 Binding<Bool>,Slider 需要 Binding<Double>,而 TextField 需要 Binding<String>.

这是 @State 属性 包装器(或 @ObservedObject 内部的 @Published)的用武之地。那个 属性 包装器 "wraps" 它包含在 Binding 中的值(连同其他一些东西以保证 SwiftUI 知道在值更改时更新视图)。如果我们需要获取值,我们可以简单地引用myVariable,但是如果我们需要绑定,我们可以使用shorthand $myVariable.

因此,在这种情况下,您的原始代码包含 ForEach($model.results)。换句话说,您告诉编译器 "Iterate over this Binding<[String]>",但 Binding 不是您可以迭代的集合。删除 $ 表示,"Iterate over this [String]," 和数组 一个可以迭代的集合。