I'm trying to create a view using the SwiftData framework with a search bar where the user can type a registration of an airplane and dynamically update the list. But first I'm not sure if I'm doing it correctly (I can't find too many examples online) and I'm getting the error:
Generic struct 'ForEach' requires that 'Query<Aircraft, [Aircraft]>' conform to 'RandomAccessCollection
struct AirplanePick: View {
@State var addNewPlane = false
@Environment (\.modelContext) var mc
@Binding var planePick: Aircraft?
let dm: DataManager
@Environment(\.presentationMode) var presentationMode
@State var searchTerm: String = ""
var filterPlanes : Query<Aircraft, [Aircraft]>{
var predicate: Predicate<Aircraft>?
if !searchTerm.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
predicate = .init(#Predicate { $0.registration.contains(searchTerm) })
}
return Query(filter: predicate, sort: \.registration)
}
var body: some View {
List {
Text("planes are \(planes.count)")
ForEach(filterPlanes, id: \.self){ plane in
Button {
planePick = plane
self.presentationMode.wrappedValue.dismiss()
} label: {
HStack{
VStack(alignment: .leading, content: {
Text("Registration").foregroundStyle(.secondary).font(.subheadline)
Text(plane.registration).bold()
})
Spacer()
VStack(alignment: .trailing, content: {
Text("Type").foregroundStyle(.secondary).font(.subheadline)
Text(plane.type ?? "")
})
}
}
}
.onDelete { IndexSet in
IndexSet.forEach { index in
let airplane = planes[index]
mc.delete(airplane)
}
}
}.toolbar {
NavigationLink("Add New") {
AddNewAirplane()
}
}
.searchable(text: $searchTerm)
}
}
aircraft model is the following:
@Model
class Aircraft{
@Attribute(.unique) var idAircraft: UUID = UUID()
var flight: Flight?
var registration: String
var category: String
var type: String?
var maxWeigth: String?
var maker: String?
var model: String?
var classOF: String?
var oper: String?
var owner: String?
var msn: String?
var note: String?
var aerobatic:Int?
var engineType:String?
var complex: Int?
var efis: Int?
var hightPerf: Int?
var military: Int?
var pressurize:Int?
var radialEngine:Int?
var tailwheel: Int?
var turbocharger: Int?
var ambphibian: Int?
var retractGear: Int?
var year: String?
init(idAircraft: UUID, registration: String, category: String, type: String? = nil, maxWeigth: String? = nil, maker: String? = nil, model: String? = nil, classOF: String? = nil, oper: String? = nil, owner: String? = nil, msn: String? = nil, note: String? = nil, aerobatic: Int? = nil, engineType: String? = nil, complex: Int? = nil, efis: Int? = nil, hightPerf: Int? = nil, military: Int? = nil, pressurize: Int? = nil, radialEngine: Int? = nil, tailwheel: Int? = nil, turbocharger: Int? = nil, ambphibian: Int? = nil, retractGear: Int? = nil, year: String? = nil) {
self.idAircraft = idAircraft
self.registration = registration
self.category = category
self.type = type
self.maxWeigth = maxWeigth
self.maker = maker
self.model = model
self.classOF = classOF
self.oper = oper
self.owner = owner
self.msn = msn
self.note = note
self.aerobatic = aerobatic
self.engineType = engineType
self.complex = complex
self.efis = efis
self.hightPerf = hightPerf
self.military = military
self.pressurize = pressurize
self.radialEngine = radialEngine
self.tailwheel = tailwheel
self.turbocharger = turbocharger
self.ambphibian = ambphibian
self.retractGear = retractGear
self.year = year
}
}
My first suggestion is not to use Query
like this since it will mean a lot of database access so unless you have a very large number of persisted objects it is probably better to filter in memory
@Query(sort: \.registration) var objects: [AirCraft]
//...
var filtered: [Aircraft] {
guard searchText.isEmpty == false else { return objects }
return objects.filter { $0.registration.contains(searchTerm)}
}
If you do want to filter using a predicate you should create a new view and pass the predicate to that view every time it changes.
@State private var predicate: Predicate<AirCraft> = .true
var body: some View {
//...
AirCraftListView(predicate: predicate)
}
.searchable(text: $searchTerm)
.onChange(of: searchTerm) { old, new in
guard new.isEmpty == false, old != new else { return }
predicate = #Predicate<AirCraft> {
$0.registration.contains(new)
}
}
And then in the list view
struct AirCraftListView: View {
@Query(sort: \.registration) var objects: [AirCraft]
init(predicate: Predicate< AirCraft >) {
_objects = Query(filter: predicate)
}