swiftuiswiftdata

SwiftData - get next record


I have a basic, test database in SwiftData that just consists of a number and a name. The number is an ascending integer and mainly continuous, but that can't be a given.

Displaying the data in a list works fine. E.g.:

@Query(sort: \EntrantsDatabase.number) var entrants: [EntrantsDatabase]
    @State private var entrantToEdit: EntrantsDatabase?
    var body: some View {
        List {
            ForEach(entrants) { entrant in
                EntrantCell(entrant: entrant) 

But next I want to display just one record/row at a time , click a button and then move onto the next.

I have tried to set the fetchLimit to 1:

@Query(sort: \EntrantsDatabase.number) var entrants: [EntrantsDatabase]
 
init() {
        var fetchDescriptor = FetchDescriptor<EntrantsDatabase>(sortBy: [SortDescriptor(\EntrantsDatabase.number, order: .reverse)])
        fetchDescriptor.propertiesToFetch = [\.number, \.name]
        fetchDescriptor.fetchLimit = 1
        
 }

But then in in the view how would I display the result and then go onto the next record?

var body: some View {
        VStack {
            HStack {
                     Text(entrants.name)

This doesn't work.


Solution

  • You could try this approach using an additional var numbers: [Int] that stores the unique numbers of @Query(...) var entrants. Sort those numbers in .onAppear and use an nextIndex into this array of possible numbers in the Button.

    Example code:

    
     struct ContentView: View {
         @Environment(\.modelContext) private var modelContext
         @Query(sort: \EntrantsDatabase.number) var entrants: [EntrantsDatabase]
    
         @State private var nextEntrant: EntrantsDatabase?
         @State private var numbers: [Int] = []
         @State private var nextIndex: Int = -1
         
         var body: some View {
             VStack {
                 List {
                     ForEach(entrants) { entrant in
                         HStack {
                             Text(entrant.name)
                             Text("\(entrant.number)")
                         }
                     }
                     Spacer()
                     // select the next nextEntrant number to be displayed
                     Button("Next") {
                         nextIndex += 1
                         if nextIndex < numbers.count, let next = entrants.first(where: { $0.number == numbers[nextIndex] }) {
                             nextEntrant = next
                         }
                     }.buttonStyle(.borderedProminent)
                     Spacer()
                     // display the nextEntrant
                     if let nextOne = nextEntrant {
                         HStack {
                             Text(nextOne.name)
                             Text("\(nextOne.number)")
                         }
                     }
                 }
                 .padding()
                 .onAppear {
    //                 // for my testing
    //                 if !entrants.isEmpty { return }
    //                 let newItem1 = EntrantsDatabase(name: "New Entrant1", number: 1)
    //                 modelContext.insert(newItem1)
    //                 let newItem2 = EntrantsDatabase(name: "New Entrant2", number: 2)
    //                 modelContext.insert(newItem2)
    //                 let newItem4 = EntrantsDatabase(name: "New Entrant4", number: 4)
    //                 modelContext.insert(newItem4)
    //                 let newItem5 = EntrantsDatabase(name: "New Entrant5", number: 5)
    //                 modelContext.insert(newItem5)
    //                 try? modelContext.save()
                     
                     // make sure all numbers are unique and sorted
                     var numberSet: Set<Int> = []
                     numberSet.formUnion(entrants.map(\.self.number))
                     numbers = Array(numberSet).sorted()
                 }
             }
         }
     }
    
    @Model final class EntrantsDatabase {
        var name: String
        var number: Int
        
        init(name: String, number: Int) {
            self.name = name
            self.number = number
        }
    }
    
    

    EDIT-1:

    @Query(...) gives you all the records as an array [EntrantsDatabase]. You can go through the index of that array and use that if you want to simply display the next record, instead of the next number.

    For example:

    
    struct ContentView: View {
        @Environment(\.modelContext) private var modelContext
        @Query(sort: \EntrantsDatabase.number) var entrants: [EntrantsDatabase]
        
        @State private var nextIndex: Int = -1
        
        var body: some View {
            VStack {
                List {
                    ForEach(entrants) { entrant in
                        HStack {
                            Text(entrant.name)
                            Text("\(entrant.number)")
                        }
                    }
                    Spacer()
                    Button("Next") {
                        nextIndex += 1
                    }.buttonStyle(.borderedProminent)
                    Spacer()
                    if nextIndex < entrants.count && nextIndex != -1 {
                        HStack {
                            Text(entrants[nextIndex].name)
                            Text("\(entrants[nextIndex].number)")
                        }
                    } else {
                        Text("no selection")
                            .onAppear {
                                nextIndex = -1
                            }
                    }
                }
                .padding()
            }
        }
    }