SwiftUI:如何迭代可绑定对象数组?

SwiftUI:如何迭代可绑定对象数组?,swift,binding,swiftui,Swift,Binding,Swiftui,我正在努力学习SwiftUI,以及绑定是如何工作的 我有这个代码,它显示了一个项目列表。点击一个项目时,该项目的绑定将传递给子视图: struct ProjectsView: View { @ObjectBinding var state: AppState @State var projectName: String = "" var body: some View { NavigationView { List { ForEach(0..&l

我正在努力学习SwiftUI,以及绑定是如何工作的

我有这个代码,它显示了一个项目列表。点击一个项目时,该项目的绑定将传递给子视图:

struct ProjectsView: View {
  @ObjectBinding var state: AppState
  @State var projectName: String = ""

  var body: some View {
    NavigationView {
      List {
        ForEach(0..<state.projects.count) { index in
          NavigationLink(destination: ProjectView(project: self.$state.projects[index])) {
            Text(self.state.projects[index].title)
          }
        }
      }
      .navigationBarTitle("Projects")
    }
  }
}
然而,我宁愿在不使用索引的情况下迭代projects数组(因为我想学习,而且它更容易阅读),但不确定如何将绑定传递给单个项目。我试过这样做,但是我无法访问
project.title
,因为它是一个绑定,而不是字符串

ForEach($state.projects) { project in
  NavigationLink(destination: ProjectView(project: project)) {
    Text(project.title)
  }
}

我怎样才能做到这一点呢?

我被困在了与你相同的问题上,我找到了部分解决方案。但首先我应该指出,使用
ForEach(0..在索引上进行迭代,不幸的是,此时似乎不可能做到这一点。实现这一点的唯一方法如下所示:

ForEach(state.projects.indices) { index in
    NavigationLink(destination: ProjectView(project: state.projects[index])) {
        Text(state.projects[index].title)
    }
} 

注意:我没有编译这段代码。这只是给你一个如何操作的手势。例如,使用和索引类型index而不是Int。

注意:我使用的是Xcode 11.2,@ObjectBinding在其中被淘汰了(因此你需要更新以验证下面的代码)

我问了一下模型,因为它可能对方法很重要。对于类似的功能,我更喜欢Combine的ObserveObject,所以模型是引用而不是值类型

下面是我为您的场景调整的方法。它与您要求的不完全一样,因为ForEach需要一些序列,但您尝试使用不支持的类型为其提供数据

无论如何,你可以考虑下面的替换(它是W/O索引)。它是完整的模块,所以你可以将它粘贴到XCODE 11.2中,并在预览中测试。希望它会有所帮助。

预览:

解决方案:

import SwiftUI
import Combine

class Project: ObservableObject, Identifiable {
    var id: String = UUID().uuidString
    @Published var title: String = ""

    init (title: String) {
        self.title = title
    }
}

class AppState: ObservableObject {
    @Published var projects: [Project] = []
    init(_ projects: [Project]) {
        self.projects = projects
    }
}

struct ProjectView: View {
  @ObservedObject var project: Project
  @State var projectName: String = ""

  var body: some View {
    VStack {
      Text(project.title)
      TextField("Change project name",
        text: $projectName,
        onCommit: {
          self.project.title = self.projectName
          self.projectName = ""
      })
      .padding()
    }
  }
}

struct ContentView: View {
    @ObservedObject var state: AppState = AppState([Project(title: "1"), Project(title: "2")])
    @State private var refreshed = false

    var body: some View {
        NavigationView {
            List {
                ForEach(state.projects) { project in
                  NavigationLink(destination: ProjectView(project: project)) {
                    // !!!  existance of .refreshed state property somewhere in ViewBuilder
                    //      is important to inavidate view, so below is just a demo
                    Text("Named: \(self.refreshed ? project.title : project.title)")
                  }
                  .onReceive(project.$title) { _ in
                        self.refreshed.toggle()
                    }
                }
            }
            .navigationBarTitle("Projects")
            .navigationBarItems(trailing: Button(action: {
                self.state.projects.append(Project(title: "Unknown"))
            }) {
                Text("New")
            })
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
我发现这很有效: 在AppState中,添加项目时,请观察其更改:

import Combine

class AppState: ObservableObject {
  @Published var projects: [Project]
  var futures = Set<AnyCancellable>()

  func addProject(project: Project) {
    project.objectWillChange
      .sink {_ in
        self.objectWillChange.send()
      }
     .store(in: &futures)
  }

  ...
}
导入联合收割机
类AppState:ObservableObject{
@已发布的var项目:[项目]
var期货=集合()
func addProject(项目:项目){
project.objectWillChange
.sink{uuuu in
self.objectWillChange.send()
}
.store(位于:&期货)
}
...
}
如果需要在外部视图中为项目变量创建绑定,请执行以下操作:

func titleBinding(forProject project: Project) -> Binding<String> {
    Binding {
        project.title
    } set: { newValue in
        project.title = newValue
    }
}
func标题绑定(用于项目:项目)->绑定{
装订{
项目名称
}集合:{newValue in
project.title=newValue
}
}

但是,如果要将项目传递到另一个视图中,则不需要它。

我使用传递给子视图的
CurrentValueSubject
而不是
Binding
解决了这个问题,但可能它太复杂了……项目是否可识别?能否提供AppState声明,或者至少提供项目属性?
project
可识别
,是。
import Combine

class AppState: ObservableObject {
  @Published var projects: [Project]
  var futures = Set<AnyCancellable>()

  func addProject(project: Project) {
    project.objectWillChange
      .sink {_ in
        self.objectWillChange.send()
      }
     .store(in: &futures)
  }

  ...
}
func titleBinding(forProject project: Project) -> Binding<String> {
    Binding {
        project.title
    } set: { newValue in
        project.title = newValue
    }
}