Clay_F
Clay_F

Reputation: 589

How to Calculate Totals, Subtotals, and Average from Array in SwiftUI

I'm new to SwiftUI and I've been stuck on this for a few days.

I have an unordered array that is identifiable. I have a filter for that array in the parent view and the value of the filter gets passed down the child view.

I'm needing to group elements based on a value of one element in the array, then calculate the total, subtotals, and average based on another value set in each array element.

The calculation needs to update based on the filter being set to true or false in the parent view.

I solved this easily in JavaScript (React) by making the calculations inside the ForEach view but don't know how to solve this in SwitUI.

Any help at all is greatly appreciated!

struct Student: Identifiable {
    let id: String
    let name: String
    let gradeLevel: Int
    let studyHours: Int
}

class GetStudents: ObservableObject {

    @Published var items = [Student]()

    init() {
        self.items = [
            Student(id: "aa1", name: "Bobby", gradeLevel: 9, studyHours: 2),
            Student(id: "aa2", name: "Sally", gradeLevel: 12, studyHours: 4),
            Student(id: "aa3", name: "Susy", gradeLevel: 11, studyHours: 3),
            Student(id: "aa4", name: "Billy", gradeLevel: 12, studyHours: 5),
            Student(id: "aa5", name: "Jimmy", gradeLevel: 11, studyHours: 1),
            Student(id: "aa6", name: "Johnny", gradeLevel: 10, studyHours: 3),
            Student(id: "aa7", name: "Mikey", gradeLevel: 12, studyHours: 2),
        ]
    }
}

struct StudyTotals: View {

    @ObservedObject var students = GetStudents()

    var onlyUpperClass: Bool

    var studentsFiltered: [Student] {
        switch self.onlyUpperClass {
        case true: return students.items.filter {$0.gradeLevel == 11 || $0.gradeLevel == 12 }
        case false: return students.items
        }
    }

    var body: some View {

        var myArray = getCalcTotals(students: studentsFiltered)

        var rows: Array<String> = []

        return VStack {

            Text("Push Calculated Rows Here??? ")

        }.padding()
    }
}

struct ContentView: View {
    @State private var onlyUpperClass: Bool = false
    var body: some View {
        return VStack {
            StudyTotals(onlyUpperClass: self.onlyUpperClass)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


func getCalcTotals(students: Array<Student>) -> Array<String> {
    let studentArray = students.reduce({ /* here */ })

    var grade: String
    var gradeCount: Int
    var gradeHours: Double? = 0
    var gradeAvg: Double? = 0

    let myArray = ["Count", "Total", "Average"]
    return myArray
}

What I'm needing is this:

enter image description here

Upvotes: 2

Views: 1452

Answers (1)

Sorin Lica
Sorin Lica

Reputation: 7656

You can calculate the values inside ForEach as in JavaScript

struct StudyTotals: View {

    @ObservedObject var students = GetStudents()

    var onlyUpperClass: Bool

    var studentsFiltered: [Student] {
        switch self.onlyUpperClass {
        case true: return students.items.filter {$0.gradeLevel == 11 || $0.gradeLevel == 12 }
        case false: return students.items
        }
    }

    var body: some View {

        var countTotal = self.studentsFiltered.count
        var hoursTotal = self.studentsFiltered.reduce(0) { $0 + $1.studyHours }

        return List {
            Group {
                HStack {
                    Text("Count")
                    Text("Hours")
                    Text("Avg")
                }
                ForEach(9...12, id: \.self) { gradeLevel -> AnyView in
                    let count = self.studentsFiltered.filter{ $0.gradeLevel ==  gradeLevel }.count
                    let hours = self.studentsFiltered.filter{ $0.gradeLevel ==  gradeLevel }.reduce(0) { $0 + $1.studyHours }
                    let avg = Float(hours) / Float(count)
                    countTotal += count
                    hoursTotal += hours
                    return AnyView(HStack {
                        Text("Grade\(gradeLevel)")
                        Text("\(count)")
                        Text("\(hours)")
                        Text("\(avg)")
                    })
                }
                HStack {
                    Text("TOTALS")
                    Text("\(countTotal)")
                    Text("\(hoursTotal)")
                    Text("\(Float(hoursTotal) / Float(countTotal))")
                }
            }
        }.padding()
    }
}

Upvotes: 2

Related Questions