So I finally just decided to create my own "Grouped" var called groupedCustomers which groups the customers (all or just filtered results) by the first letter of their name, sorts the customers alphabetically in each group and then finally sorts the entire group alphabetically:
private var groupedCustomers: [GroupedCustomer] {
var groupedCusts : [String: [SD_Customer]]
var sortedCusts: [GroupedCustomer] = []
var retVal: [GroupedCustomer] = []
groupedCusts = Dictionary(grouping: (searchText.isEmpty ? customers : customers.filter {$0.custName.lowercased().contains(searchText.lowercased())}), by: \.firstLetterOfCustName)
sortedCusts = groupedCusts.map { (arg) -> GroupedCustomer in
let (key, values) = arg
return GroupedCustomer(id: key, customers: values.sorted {$0.custName < $1.custName})
}
retVal = sortedCusts.sorted { $0.id < $1.id }
return retVal
}
Full View:
import SwiftUI
import SwiftData
struct CustomerMasterView: View {
//data
@Environment(\.modelContext) private var modelContext
@Query(sort: \.custName, order: .forward, animation: .spring) private var customers: [SD_Customer]
@State private var selectedCustomer: SD_Customer?
//state
@State private var searchText: String = ""
//calculated values
private var groupedCustomers: [GroupedCustomer] {
var groupedCusts : [String: [SD_Customer]]
var sortedCusts: [GroupedCustomer] = []
var retVal: [GroupedCustomer] = []
groupedCusts = Dictionary(grouping: (searchText.isEmpty ? customers : customers.filter {$0.custName.lowercased().contains(searchText.lowercased())}), by: \.firstLetterOfCustName)
sortedCusts = groupedCusts.map { (arg) -> GroupedCustomer in
let (key, values) = arg
return GroupedCustomer(id: key, customers: values.sorted {$0.custName < $1.custName})
}
retVal = sortedCusts.sorted { $0.id < $1.id }
return retVal
}
//body
var body: some View {
NavigationSplitView {
List(groupedCustomers, id: \.id, selection: $selectedCustomer) { header in
Section {
ForEach(header, id: \.self) {customer in
CustomerListCellView(customer: customer)
}
} header: {
Text("\(header.id) - \(header.customers.count)")
}
}
.listStyle(PlainListStyle())
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.disableAutocorrection(true)
.navigationTitle(Text("CUSTOMERS"))
//.navigationBarItems(trailing: menuButton)
}
detail: {
if let cust = self.selectedCustomer {
CustomerDetailView(customer: cust)
} else {
ContentUnavailableView("Nothing To See Here", systemImage: "eyes", description: Text("Select a customer from the list to view thier details."))
}
}
}
}
struct GroupedCustomer: Identifiable, RandomAccessCollection {
typealias Element = SD_Customer
typealias Index = Int
var id: String
var customers: [SD_Customer]
init(id: String, customers: [SD_Customer]) {
self.id = id
self.customers = customers
}
var startIndex: Int {
return customers.startIndex
}
var endIndex: Int {
return customers.endIndex
}
subscript(position: Int) -> SD_Customer {
return customers[position]
}
func index(after i: Int) -> Int {
return customers.index(after: i)
}
}
Hope this helps. And if you find any errors or improvements to my code, please let me know.