Here is the desired result (a List from a viewModel derived using functions to generate yearly pay raises and show user's age from year to year). When the user alters their birthdate and hiredate in the ProfileFormView, this List should update the Age and Year group accordingly:
Unfortunately I have not been able to successfully create this view due to errors in attempts to incorporated functions within the viewModel's array.
Here is my viewModel:
class WorkerViewModel:ObservableObject {
@Published var userData: Worker =
Worker(name: "Robert", birthdate: Date(timeIntervalSince1970: 10000), hiredate: Date(timeIntervalSince1970: 30000), department: "HR")
func calcAge(birthdate: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let birthdayDate = dateFormater.date(from: birthdate)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcAge = calendar.components(.year, from: birthdayDate!, to: now, options: [])
let age = calcAge.year
return age!
}
func calcYearGroup(hiredate: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let newHireDateFormatted = dateFormater.date(from: hiredate)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcYearGroup = calendar.components(.year, from: newHireDateFormatted!, to: now, options: [])
let yearGroup = calcYearGroup.year! + 1
return yearGroup
}
func calcAnnualEarnings(hourlyRate: Double, monthlyHours: Double) -> Double {
return (hourlyRate * monthlyHours) * 12
}
@Published var userDataList: [WorkerInfo] = [
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate), yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate), salary: calcAnnualEarnings(hourlyRate: PayRates.hR[1], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 1, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 1, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[2], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 2, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 2, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[3], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 3, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 3, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[4], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 4, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 4, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[5], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
]
}
Here is my model data:
struct Worker: Codable, Identifiable {
var id = UUID()
var name: String
var birthdate: Date
var hiredate: Date
var department: String
}
struct WorkerInfo: Codable, Identifiable {
var id = UUID()
var age: Int
var yearGroup: Int
var salary: Double
var dept: String
}
struct MockData {
static let sampleUser = WorkerInfo(age: 41, yearGroup: 1, salary: 80000, dept: "HR")
}
struct PayRates {
let hR: [Double] = [0,92,128,150,154,158,162,166,170,172,174,177,180]
let management: [Double] = [0,235,236,238,240,242,244,246,248,250,252,254,256]
}
Here is my Content View:
struct ContentView: View {
@StateObject var vm = WorkerViewModel()
var body: some View {
TabView {
ProfileFormView()
.tabItem {
Image(systemName: "square.and.pencil")
Text("Profile")
}
WorkerView()
.tabItem {
Image(systemName: "house")
Text("Home")
}
.padding()
}.environmentObject(vm)
}
}
Here is my Worker View:
struct WorkerView: View {
@EnvironmentObject var vm: WorkerViewModel
var body: some View {
ZStack {
List($vm.userDataList) { $worker in
WorkerCardView(workerInfo: $worker)
}
}
}
}
Here is my ProfileForm View:
struct ProfileFormView: View {
@EnvironmentObject var vm: WorkerViewModel
@State var depts = ["HR","Management","Marketing","Development"]
var body: some View {
Form {
Section(header: Text("Personal Information")) {
TextField("Name", text: $vm.userData.name)
DatePicker("Birthdate", selection: $vm.userData.birthdate, displayedComponents: .date)
DatePicker("New Hire Date", selection: $vm.userData.hiredate, displayedComponents: .date)
Picker("Department", selection: $vm.userData.department) {
ForEach(depts, id: \.self) {
Text ($0)
}
}
}
}
}
}
And lastly, here is my WorkerCard View:
struct WorkerCardView: View {
@Binding var workerInfo: WorkerInfo
var body: some View {
HStack{
VStack(alignment: .leading) {
Text("Year \(workerInfo.yearGroup)")
.font(.headline)
Label("Age \(workerInfo.age)", systemImage: "person")
.foregroundStyle(.blue)
.font(.subheadline)
}
Spacer()
VStack(alignment: .leading) {
Text("$ \(workerInfo.salary, specifier: "%.0f") salary")
.font(.headline)
Label("\(workerInfo.dept) Dept", systemImage: "building")
.foregroundStyle(.blue)
.font(.subheadline)
}
}
}
}
Thank you in advance for your help!!
//UPDATE 1//
I have converted my functions to computed properties seemingly error free. This also has an added benefit of making the code cleaner which is nice.
I have boiled the problem down now to an initialization issue. Each of my three computed properties trigger compiler errors when placed in the userDataList array:
"Cannot use instance member 'computedProperty' within property initializer; property initializers run before 'self' is available"
//UPDATE 2//
I converted the userDataArray into a computed property, but the resulting compiler error is related to the @Published property wrapper. Naturally, computed properties cannot receive property wrappers, but I need the userDataArray to be visible to the WorkerView in order for the List to iterate over the userDataArray.
Removing the @Published causes this complier error in the WorkerView:
"Cannot assign to property: 'userDataList' is a get-only property"