0

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:

DesiredView

Unfortunately I have not been able to successfully create this view due to errors in attempts to incorporated functions within the viewModel's array.

Errors

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"

viewModelCodeUpdate1

//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.

viewModelError2

Removing the @Published causes this complier error in the WorkerView:

"Cannot assign to property: 'userDataList' is a get-only property"

ViewError1

  • 1
    It's clear that `$Worker` doesn't exist, but it's not clear to me from reading your code what you are hoping it will be. What are you expecting `$Worker` to represent? – jnpdx Oct 28 '22 at 21:00
  • @jnpdx I updated my original post changing '$Worker' to '$WorkerInfo'... My desire is that the '$WorkerInfo' variables (birthdate, hiredate, department) which are altered by the user in the 'ProfileFormView' will be updated/represented in the viewModel 'userDataArray' functions. When the user inputs a new birthdate in the 'ProfileFormView', the 'userDataArray' functions in the viewModel will show an updated 'Age' ... same with updating the yearGroup based on user-input 'hireDate' – weekendwarrior Oct 28 '22 at 21:12
  • 1
    You seem to have a misunderstanding between variables and the types that they represent. `WorkerInfo` is a *type*. Also, a misunderstanding about when the `$` is used -- I'm not clear what you're trying to get by using that here. – jnpdx Oct 28 '22 at 21:24
  • 1
    Also, your idea about somehow automatically updating `userDataArray` based on new values from the form isn't going to work like this. When you set the *initial value*, it is *static*. Maybe you're looking to have something like a "computed property" – jnpdx Oct 28 '22 at 21:26
  • @jnpdx you’re right, the $ is useless since these should be read-only, not read-and-write. However removing the $ does not solve the error. I’ll do some more reading in computed properties… thanks for the tip. – weekendwarrior Oct 28 '22 at 22:05
  • 1
    Yes, it’s not the only issue. The main issue is the type vs variable issue. – jnpdx Oct 29 '22 at 04:40
  • @jnpdx Thank you for the helpful hints! I have replaced the functions with computed properties. The only remaining compiler error is within the userDataArray which requires initialization of each of the three computed properties. (see the update to the original post above) I understand that computed properties do not store values and therefore should not need to be initialized, but I'm sure you can set me straight on that point. In any case, I'm not sure how to correct the compiler error. – weekendwarrior Oct 31 '22 at 14:45
  • To me, it looks like `userDataArray` should be a computed property, but I'm still pretty unclear on what you're trying to accomplish – jnpdx Oct 31 '22 at 15:00
  • @jnpdx Thanks! I converted the userDataArray into a computed property but I need it to be visible to the WorkerView wherein the List should iterate over the userDataArray. (See UPDATE 2 above) – weekendwarrior Oct 31 '22 at 16:43
  • It shouldn’t be Published – jnpdx Oct 31 '22 at 17:21

0 Answers0