SwiftData Equivalent of @SectionedFetchRequest

Hi all, Has anyone stumbled upon the SwiftData equivalent of @SectionedFetchRequest? Is there a way to do it with @Query? I'll keep going through the documentation but if anyone has an answer, it would be much appreciated!!

Thank you.

Answered by Chuck H in 758236022

I fully expect Apple to provide this capability eventually, but after studying the headers for the @SectionedFetchRequest property wrapper, I began to wonder how hard it would be to create a package to do the same thing for Swift Data. The package README also has a link to a fully functional sample program that demonstrates the package.

This is what I came up with: https://github.com/Whiffer/swiftdata-sectionedquery

Did you figure out if this is possible? Looking at the documentation, I think we’re out of luck, but I’d love to be wrong.

Nope. no luck yet...

At the live SwiftData Q&A session last week, this same question was asked several times. Each time the developer/moderator said this was not yet implemented and a Feedback should be submitted to request it. Mine is FB12292624.

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.

Accepted Answer

I fully expect Apple to provide this capability eventually, but after studying the headers for the @SectionedFetchRequest property wrapper, I began to wonder how hard it would be to create a package to do the same thing for Swift Data. The package README also has a link to a fully functional sample program that demonstrates the package.

This is what I came up with: https://github.com/Whiffer/swiftdata-sectionedquery

SwiftData Equivalent of @SectionedFetchRequest
 
 
Q