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.
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()
}
}
}