Last active
March 12, 2024 21:24
-
-
Save bigmountainstudio/cc6beba0ef18284696e97e59d8eb8cc0 to your computer and use it in GitHub Desktop.
SwiftData Aggregate Functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright © 2024 Big Mountain Studio. All rights reserved. Twitter: @BigMtnStudio | |
import SwiftData | |
import SwiftUI | |
@Model | |
class EmployeeModel { | |
var employeeId: UUID | |
var birthDate: Date | |
var firstName: String | |
var lastName: String | |
var gender: String | |
var hireDate: Date | |
var salary: Decimal | |
init(employeeId: UUID, birthDate: Date, firstName: String, lastName: String, gender: String, hireDate: Date, salary: Decimal) { | |
self.employeeId = employeeId | |
self.birthDate = birthDate | |
self.firstName = firstName | |
self.lastName = lastName | |
self.gender = gender | |
self.hireDate = hireDate | |
self.salary = salary | |
} | |
var viewAge: Int { | |
let calendar = Calendar.current | |
let now = Date() | |
let ageComponents = calendar.dateComponents([.year], from: birthDate, to: now) | |
return ageComponents.year ?? 0 | |
} | |
} | |
// Aggregate function examples | |
extension EmployeeModel { | |
static func getSalarySum(employees: [EmployeeModel]) -> Decimal { | |
// Sum of salaries (reduce combines values of current + next items in a sequence) | |
let totalSalary = employees.reduce(Decimal(0)) { $0 + $1.salary } | |
return totalSalary | |
} | |
static func getAverageAge(employees: [EmployeeModel]) -> Int { | |
// Calculate the average age of employees using their birthDate | |
let totalAge = employees.reduce(0) { $0 + Calendar.current.dateComponents([.year], from: $1.birthDate, to: Date()).year! } | |
return totalAge / employees.count | |
} | |
static func getOldestEmployee(modelContext: ModelContext) -> String { | |
var fetch = FetchDescriptor<EmployeeModel>() | |
fetch.sortBy = [SortDescriptor(\EmployeeModel.birthDate, order: .forward)] | |
fetch.fetchLimit = 1 | |
return try! modelContext.fetch(fetch).first!.firstName | |
} | |
static func getYoungestEmployees(modelContext: ModelContext) -> [EmployeeModel] { | |
// Youngest Male | |
let maleFilter = #Predicate<EmployeeModel> { $0.gender == "M" } | |
var fetchDescriptorMale = FetchDescriptor<EmployeeModel>() | |
fetchDescriptorMale.predicate = maleFilter | |
fetchDescriptorMale.sortBy = [SortDescriptor(\EmployeeModel.birthDate, order: .reverse)] | |
fetchDescriptorMale.fetchLimit = 1 | |
let youngestMale = try! modelContext.fetch(fetchDescriptorMale).first! | |
// Youngest Female | |
let femaleFilter = #Predicate<EmployeeModel> { $0.gender == "F" } | |
var fetchDescriptorFemale = FetchDescriptor<EmployeeModel>() | |
fetchDescriptorFemale.predicate = femaleFilter | |
fetchDescriptorFemale.sortBy = [SortDescriptor(\EmployeeModel.birthDate, order: .reverse)] | |
fetchDescriptorFemale.fetchLimit = 1 | |
let youngestFemale = try! modelContext.fetch(fetchDescriptorFemale).first! | |
return [youngestMale, youngestFemale] | |
} | |
} | |
extension EmployeeModel { | |
@MainActor | |
static var preview: ModelContainer { | |
let container = try! ModelContainer(for: EmployeeModel.self, | |
configurations: ModelConfiguration(isStoredInMemoryOnly: true)) | |
let employees = [ | |
EmployeeModel(employeeId: UUID(), birthDate: Date(timeIntervalSince1970: -568025136), firstName: "John", lastName: "Doe", gender: "M", hireDate: Date(timeIntervalSince1970: 915148800), salary: 50000), | |
EmployeeModel(employeeId: UUID(), birthDate: Date(timeIntervalSince1970: -315360000), firstName: "Jane", lastName: "Smith", gender: "F", hireDate: Date(timeIntervalSince1970: 1009843200), salary: 55000), | |
EmployeeModel(employeeId: UUID(), birthDate: Date(timeIntervalSince1970: 0), firstName: "Emily", lastName: "Davis", gender: "F", hireDate: Date(timeIntervalSince1970: 1199145600), salary: 65000), | |
EmployeeModel(employeeId: UUID(), birthDate: Date(timeIntervalSince1970: 157680000), firstName: "Michael", lastName: "Miller", gender: "M", hireDate: Date(timeIntervalSince1970: 1293840000), salary: 70000), | |
] | |
for employee in employees { | |
container.mainContext.insert(employee) | |
} | |
return container | |
} | |
} | |
struct AggregateFunctions: View { | |
@Environment(\.modelContext) private var modelContext | |
@Query private var employees: [EmployeeModel] | |
var body: some View { | |
Form { | |
Section("Employees") { | |
ForEach(employees) { employee in | |
VStack { | |
LabeledContent(employee.firstName, value: employee.salary, format: .currency(code: "USD")) | |
LabeledContent("Age", value: employee.viewAge, format: .number) | |
.font(.footnote) | |
} | |
} | |
} | |
Section("Summary") { | |
LabeledContent("Total Budget", value: EmployeeModel.getSalarySum(employees: employees), format: .currency(code: "USD")) | |
LabeledContent("Average Age", value: EmployeeModel.getAverageAge(employees: employees), format: .number) | |
LabeledContent("Oldest Employee", value: EmployeeModel.getOldestEmployee(modelContext: modelContext)) | |
} | |
Section("Youngest") { | |
ForEach(EmployeeModel.getYoungestEmployees(modelContext: modelContext)) { employee in | |
LabeledContent(employee.firstName, value: employee.viewAge, format: .number) | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
AggregateFunctions() | |
.modelContainer(EmployeeModel.preview) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment