I have been unable to solve the problem for several days. There is a data type that includes an array with an enum. I want to зфыы the data to another view for editing. But can't understand how to bind it. Is this even possible.
P.S. Use MVVM architecture.
Here is my code
Data models:
struct SportProgramModel: Codable, Identifiable { //Ьodel for training program
var id = UUID().uuidString
let title: String
let description: String
let author: String
let shared: Bool
let trainingDays: [TrainingDayListValues]
}
struct TrainingDayListValues: Codable, Identifiable{ //Model for workout days: contain workouts or rest. Select by enum
var id = UUID().uuidString
let trainingDayType: trainingDaysTypes
}
enum trainingDaysTypes: Codable{
case workout (TrainingDayModel)
case rest (String)
}
struct TrainingDayModel: Codable, Identifiable {
var id = UUID().uuidString
let title: String
let setOfExercises: [SetOfExercisesModel]
View #1:
View model
@MainActor
class SportProgramViewModel: ObservableObject{
@Published var sportProgram: SportProgramModel?
@Published var trainingDaysList: [TrainingDayListValues] = []
func getSportProgramDetails(sportProgramID: String) async {
do{
let sportProgram = try await SportManager.shared.getSportProgram(id: sportProgramID) //get data from firebase
self.trainingDaysList = sportProgram.trainingDays
} catch {
print("Error when getting sport program")
}
}
}
View
struct SportProgramView: View {
@ObservedObject var sportProgramViewModel = SportProgramViewModel()
@Environment(\.dismiss) var dismiss
@State private var title = ""
@State private var description = ""
@State private var author = ""
@State private var shared: Bool = false
@State private var restDays = ""
@State private var showRestDayView: Bool = false
var sportPorgramID: String?
var body: some View {
VStack{
VStack {
TextField("Name your program", text: $title)
Divider()
TextField("Enter descripton", text: $description)
Text("author id" + author)
}
List{
ForEach(sportProgramViewModel.trainingDaysList){day in
switch day.trainingDayType {
case .workout(let workout):
NavigationLink {
TrainingDayView(trainingDay: workout) //Here pass data to another view with binding, but how??
} label: {
Text(workout.title)
}
case .rest(let rest):
Text("Rest for " + rest + " days")
}
}
}
// Create new empty
HStack {
NavigationLink{
// TODO: TrainingDayView()
} label: {
HStack {
Image(systemName: "plus.app.fill")
Text("Add day")
}
}
}
}
.onAppear{
//if we have id it means its not a new sport program
if let id = sportPorgramID{
Task{
await sportProgramViewModel.getSportProgramDetails(sportProgramID: id)
//update data
title = sportProgramViewModel.sportProgram?.title ?? ""
description = sportProgramViewModel.sportProgram?.description ?? ""
author = sportProgramViewModel.sportProgram?.author ?? ""
shared = sportProgramViewModel.sportProgram?.shared ?? false
}
}
}
.toolbar{
Button {
Task{
guard let user = UserManager.shared.firebaseAuth.currentUser.userId else { return }
//TODO: await sportProgramViewModel.saveSportProgram()
dismiss()
}
} label: {
Image(systemName: "checkmark.circle.fill")
}
}
.navigationTitle("New program")
}
}
#Preview {
SportProgramView()
}
View #2
struct TrainingDayView: View {
@Environment(\.dismiss) var dismiss
let trainingDay: TrainingDayModel? //get data from previous view
@State var title: String = ""
var body: some View {
NavigationStack{
VStack{
TextField("Name yor day", text: $title)
}
.padding()
}
}
}
#Preview {
TrainingDayView(trainingDay: TrainingDayModel(title: "", setOfExercises: []))
}
Tried make like in this thread, but have a problem with enum.
I would add an optional workout
property to TrainingDayTypes
so you can easily form a binding to workout
.
enum TrainingDaysTypes: Codable{
case workout(TrainingDayModel)
case rest(String)
var workout: TrainingDayModel? {
get {
switch self {
case .workout(let trainingDayModel):
trainingDayModel
case .rest(let string):
nil
}
}
set {
if let newValue {
self = .workout(newValue)
}
}
}
}
You should also change TrainingDayListValues.trainingDayType
to a var
, since TrainingDayView
is going to modify its value.
struct TrainingDayListValues: Codable, Identifiable{ //Model for workout days: contain workouts or rest. Select by enum
var id = UUID().uuidString
var trainingDayType: TrainingDaysTypes
// ^^^
// here!
}
Now you can make TrainingDayView
take a @Binding
,
struct TrainingDayView: View {
@Environment(\.dismiss) var dismiss
@Binding var trainingDay: TrainingDayModel?
In SportProgramView
, you should pass a binding to the ForEach
initialiser, so you also get a binding in the body of the ForEach
.
ForEach($sportProgramViewModel.trainingDaysList){ $day in
switch day.trainingDayType {
case .workout(let workout):
NavigationLink {
TrainingDayView(trainingDay: $day.trainingDayType.workout)
} label: {
Text(workout.title)
}
case .rest(let rest):
Text("Rest for " + rest + " days")
}
}