0

I can't seem to get my "Employee View" to update based on user input in the Form inside the "ProfileFormView." Currently any input in the Form fields does not affect the "EmployeeView". I need the data that is typed into the Form to update the "Employee View." Where did I go wrong?

Here is my viewModel:

@MainActor class EmployeeViewModel: ObservableObject {
@Published var name = ""
@Published var empNum = ""
@Published var birthdate = Date(timeIntervalSince1970: 0)
@Published var dept = ""
@Published var userData: [Employee] = []

init(name: String = "", empNum: String = "", birthdate: Date = Date(timeIntervalSince1970: 0), dept: String = "", userData: [Employee] = []) {
    self.name = name
    self.empNum = empNum
    self.birthdate = birthdate
    self.dept = dept
    self.userData = [
        Employee(birthdate: birthdate, name: name, empNum: empNum, department: dept)]
}
}

Here is my EmployeeView:

struct EmployeeView: View {
let viewModel: EmployeeViewModel

var body: some View {
    ZStack {
        VStack {
            List(viewModel.userData, id: \.id) { line in
                EmployeeCardView(employee: line)
            }
        }
    }
}
}

Here is my ProfileFormView:

struct ProfileFormView: View {
@EnvironmentObject var viewModel: EmployeeViewModel
@State var depts = ["HR","Management","Marketing","Development"]


var body: some View {
    NavigationView {
        Form {
            Section(header: Text("Personal Information")) {
                TextField("Name", text: $viewModel.name)
                DatePicker("Birthdate", selection: $viewModel.birthdate, displayedComponents: .date)
                TextField("Employee #", text: $viewModel.empNum)
                Picker("Department", selection: $viewModel.dept) {
                    ForEach(depts, id: \.self) {
                        Text ($0)
                    }
                }

            }
        }
    }
}
}

Here is my ContentView (note that the two views are tabs under ContentView):

struct ContentView: View {
@StateObject var vm = EmployeeViewModel()

var body: some View {
    NavigationView {
        ZStack {
            Color.gray.ignoresSafeArea()
                .navigationBarHidden(true)
            TabView {
                ProfileFormView()
                    .tabItem {
                        Image(systemName: "square.and.pencil")
                        Text("Profile")
                    }
                EmployeeView(viewModel: EmployeeViewModel())
                    .tabItem {
                        Image(systemName: "house")
                        Text("Home")
                    }
                    .padding()
            }
            .environmentObject(vm)
        }
    }
}
}

Here is my card view:

struct EmployeeCardView: View {
let employee: Employee

var body: some View {
    ZStack{
        VStack {
            HStack() {
                Spacer()
                Text(employee.name)
                    .foregroundStyle(.blue)
                    .font(.headline)
                Spacer()
                Label("\(employee.birthdate.formatted(date: .abbreviated, time: .omitted))", systemImage: "calendar")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()
            }
            HStack() {
                Spacer()
                Label("\(employee.empNum)", systemImage: "person")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()
                Label("\(employee.department)", systemImage: "building")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()

            }
        }
    }
}
}

Here is my data model:

struct Employee: Codable, Identifiable {
var id = UUID()
var birthdate = Date(timeIntervalSince1970: 0)
var name = ""
var empNum = "8675309"
var department = ""

init(id: UUID = UUID(), birthdate: Date = Date(timeIntervalSince1970: 0), name: String = "", empNum: String = "8675309", department: String = "") {
    self.id = id
    self.birthdate = birthdate
    self.name = name
    self.empNum = empNum
    self.department = department
}
}
struct MockData {

static let mockData = [sampleUser,sampleUser, sampleUser]

static let sampleUser = Employee(birthdate: Date(timeIntervalSince1970: 0), name: "Bob", empNum: "8675309", department: "HR")
}

Here is a screenshot of the card view with mock data: cardview

Here is a screenshot of the ContentView not taking input: ContentView

Here is the error I get in the EmployeeCardView_Preview: Preview error

  • You aren't showing the code for `EmployeeViewModel` which is really the most important part of this. Anywhere that you have a reference to it should be annotated with `@StateObject` (if it's the view that owns it) or `@ObservableObject`. Your `EmployeeView`, for example, does not have one of those annotations and thus will never update based on changed content. – jnpdx Oct 26 '22 at 02:04
  • You're also creating multiple instances of it -- you should likely have *one* instance (ie where you call `EmployeeViewModel()`) – jnpdx Oct 26 '22 at 02:05
  • You also may be getting confused trying to pass it both explicitly as a parameter *and* via `environmentObject` -- maybe choose one until you're comfortable with the difference. – jnpdx Oct 26 '22 at 02:06
  • Looking at your other questions, maybe you do need multiple EmployeeViewModels -- but, you should reconsider using a view model class like this. Instead, use simple `@State` and `@Binding` -- it'll make your life much easier – jnpdx Oct 26 '22 at 02:25
  • @jnpdx Thank you for the feedback! I've updated my post by adding my viewModel and also some errors associated with adding ObservableObject wrappers. – weekendwarrior Oct 26 '22 at 02:40
  • Sorry — should’ve written ObservedObject – jnpdx Oct 26 '22 at 03:02

1 Answers1

1

Try this approach using @EnvironmentObject to pass the view model around, and @Binding in EmployeeCardView. Works for me:

struct EmployeeCardView: View {
    @Binding var employee: Employee
    
    var body: some View {
        VStack {
            HStack() {
                Spacer()
                Text(employee.name)
                    .foregroundStyle(.blue)
                    .font(.headline)
                Spacer()
                Label("\(employee.birthdate.formatted(date: .abbreviated, time: .omitted))", systemImage: "calendar")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()
            }
            HStack() {
                Spacer()
                Label("\(employee.empNum)", systemImage: "person")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()
                Label("\(employee.department)", systemImage: "building")
                    .foregroundStyle(.blue)
                    .font(.subheadline)
                Spacer()
            }
        }
    }
}

struct EmployeeView: View {
    @EnvironmentObject var vm: EmployeeViewModel
    
    var body: some View {
        List($vm.userData) { $employee in
            EmployeeCardView(employee: $employee)
        }
    }
}

struct ProfileFormView: View {
    @EnvironmentObject var vm: EmployeeViewModel
    @State var depts = ["HR","Management","Marketing","Development"]
    
    var body: some View {
        Form {
            ForEach($vm.userData) { $employee in
                Section(header: Text("Personal Information")) {
                    TextField("Name", text: $employee.name)
                    DatePicker("Birthdate", selection: $employee.birthdate, displayedComponents: .date)
                    TextField("Employee #", text: $employee.empNum)
                    Picker("Department", selection: $employee.department) {
                        ForEach(depts, id: \.self) {
                            Text($0)
                        }
                    }
                }
            }
        }
    }
}

struct ContentView: View {
     @StateObject var vm = EmployeeViewModel()

    var body: some View {
            ZStack {
                Color.gray.ignoresSafeArea()
                TabView {
                    ProfileFormView()
                        .tabItem {
                            Image(systemName: "square.and.pencil")
                            Text("Profile")
                        }
                    EmployeeView()
                        .tabItem {
                            Image(systemName: "house")
                            Text("Home")
                        }
                        .padding()
                }.environmentObject(vm)
            }
    }
}

struct Employee: Codable, Identifiable {
    var id = UUID()
    var birthdate: Date
    var name: String
    var empNum: String
    var department: String
}

class EmployeeViewModel: ObservableObject {
    @Published var userData: [Employee] = [
        Employee(birthdate: Date(timeIntervalSince1970: 0), name: "Bob", empNum: "8675309", department: "HR"),
        Employee(birthdate: Date(timeIntervalSince1970: 10000), name: "Jack", empNum: "123456", department: "Marketing"),
        Employee(birthdate: Date(timeIntervalSince1970: 60000), name: "Albert", empNum: "87425", department: "HR")
    ]
}

EDIT-1: with a Previews for EmployeeCardView

 struct EmployeeCardView_Previews: PreviewProvider {
     static var previews: some View {
         EmployeeCardView(employee: .constant(MockData.sampleUser))
     }
 }
  • Thank you! I have only one remaining error which is within the EmployeeCardView_Preview. static var previews: some View { EmployeeCardView(employee: $employee) ... Error: "Cannot find '$employee' in scope". I've added a picture to my original post for clarity. How do I get my preview to be error free? – weekendwarrior Oct 27 '22 at 01:00
  • updated my answer with a possible approach for the `Previews`. If my answer helped, consider accepting it, using the tick mark, it turns green. – workingdog support Ukraine Oct 27 '22 at 01:47