SwiftUI 中传递数据的多维列表

Multidimensional lists in SwiftUI that pass data

我有这个型号:

struct Training: Identifiable {
    let id = UUID()
    let name: String
    let workout: [Workout]?
}

和:

struct Workout: Identifiable {
    let id = UUID()
    let name: String
    let exercices: [Exercice]?
}

和:

struct Exercice: Identifiable {
    let id = UUID()
    let name: String
}

模型的数据来自环境对象。

该应用程序将以空的培训列表启动,您可以在 UI 中添加培训。每个训练都有一个导航链接到一个视图,以便为每个训练添加锻炼,在下一步中,您可以为每个锻炼添加锻炼。

在我的逻辑中,我使用上面显示的结构创建多维数组。

培训视图很简单:

struct TrainingsView: View {

    @EnvironmentObject var appState: AppState

    @State var showingDetail = false

    var body: some View {
        NavigationView {

            VStack {
                List {
                    ForEach (appState.trainings) { training in
                        NavigationLink(destination: WorkoutsView(training: training).environmentObject(self.appState)) {
                            Text(training.name)
                        }
                    }
                    .onDelete(perform: appState.removeTraining)
                }
                // Button to add trainings....
                .navigationBarTitle(Text("Trainings").foregroundColor(Color.white))
            }
        }
    }
}

WorkoutsView 看起来一样,但我在列出家长培训项目时遇到问题:

struct WorkoutsView: View {
    // ...
    var training: Training

    var body: some View {
        VStack {
            List {
                ForEach (appState.trainings(training).workouts) { workout in // I know the appState call is incorrect, but I don't know how to access is correctly.
                    NavigationLink(destination: ExercicesView(workout)) {
                        Text(workout.name)
                    }
                }
            }
            // ...
        }
    }
}

我已经试过了:

List {
    ForEach (0 ..< appState.trainings.count) {
        NavigationLink(destination: WorkoutsView(training: [=15=]).environmentObject(self.appState)) {
            Text(appState.trainings[[=15=]].name)
        }
    }
}

我可以在 WorkoutsView 中使用 appState.trainings[training].workouts,但我在 NavigationLink 行收到错误 Contextual closure type '() -> Text' expects 0 arguments, but 1 was used in closure body,不知道该怎么做。

附加问题:如果这接近于解决方案,我不需要结构符合 Identifiable?

这里有 2 种广泛的方法,具体取决于您希望如何设计系统。

1. 您的 child 视图知道应用程序状态并可以直接修改它。所以,parent需要通过indices/keys让child定位修改哪些数据:

struct TrainingsView: View {
   @EnvironmentObject var appState: AppState

   var body: some View {
      NavigationView {
         List(0..<appState.trainings.count) { i in
            NavigationLink(destination: WorkoutsView(trainingIndex: i)) {
               Text(self.appState.trainings[i].name)
            }
         } 
      }
   }
}
struct WorkoutsView: View {
   var trainingIdx: Int
   @EnvironmentObject var appState: AppState

   var body: some View {
      VStack() {

         TextField("training name: ", text: $appState.trainings[trainingIdx].name)

         Button(action: {self.appState.trainings[trainingIdx].workouts.append(Workout(...))}) {
            Text("Add workout")
         }
      }
   }
}

2. 或者,您可能会说您不希望 child 视图了解应用程序的状态 - 您只希望它们修改一些静态的struct 他们不拥有(而是属于他们的 parent),那么你应该使用 @Binding.

下面的例子是为了说明一个观点的概念:

struct TrainingsView: View {
   @State var trainingA = Training(...)
   @State var trainingB = Training(...)

   var body: some Body {
      NavigationView {
         List {
            WorkoutsView(training: $trainingA)
            WorkoutsView(training: $trainingB)
         }
      }
   }
}
struct WorkoutsView: View {
   @Binding var training: Training

   var body: some View {
      VStack() {

         TextField("training name: ", text: $training.name)

         Button(action: { self.training.workouts.append(Workout(...)) }) {
            Text("Add workout")
         }
      }
   }
}