这是一个示例,它归结为 2 种基本方式。
iOS 15+ 方式和 iOS 15 之前的方式。
它们都使用NSPredicate(单个条件)或NSCompoundPredicate(多个条件)。
import SwiftUI
import CoreData
@available(iOS 15.0, *)
struct FilteredListView: View {
@StateObject var vm: FilteredListViewModel = .init()
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Person.age, ascending: true)],
animation: .default)
private var people: FetchedResults<Person>
var body: some View {
NavigationView{
VStack{
FilterOptionsView(fromAge: $vm.fromAge, toAge: $vm.toAge, selectedOccupation: $vm.occupation, occupations: vm.uniqueOccupations)
HStack{
//This is the iOS 15+ way
SimpleListView(label: "@FetchRequest", people: Array(people))
.onChange(of: vm.toAge, perform: { val in
///This can only be used on iOS15+
setThePredicate()
})
.onChange(of: vm.fromAge, perform: { val in
///This can only be used on iOS15+
setThePredicate()
})
.onChange(of: vm.occupation, perform: { val in
///This can only be used on iOS15+
setThePredicate()
})
//This is the other os compatible version but it does not observe the store
SimpleListView(label: "NSFetchRequest", people: Array(vm.people))
}
}.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Button("add samples", action: {
vm.addSamples()
})
})
})
}
}
///This can only be used on iOS15+
func setThePredicate(){
//You can use any of the predicates I created in the extension here
people.nsPredicate = Person.ageAndOccupationPredicate(ageFrom: Int16(vm.fromAge), ageTo: Int16(vm.toAge), occupation: vm.occupation)
}
}
class FilteredListViewModel: ObservableObject{
@Published var fromAge: Int = 14{
didSet{
//Update the list when the variable changes
getNewList()
}
}
@Published var toAge: Int = 100{
didSet{
//Update the list when the variable changes
getNewList()
}
}
@Published var occupation: String = ""{
didSet{
//Update the list when the variable changes
getNewList()
}
}
@Published var people: [Person] = []
var uniqueOccupations: [String]{
///Get all the items, get the unique values of the occupations with the set, and then get the array for the View
Array(Set(getObjects(nsPredicate: nil).map{$0.occupation ?? "unknown"}))
}
func addSamples(){
for _ in 0...20{
let context = PersistenceController.previewAware.container.viewContext
_ = Person(context: context)
}
}
//Other os version way of using the predicate
func getNewList(){
//This is retrieved using NSFetchRequest it does not observe the store
//Any tutorial will show you how to make this request
people = getObjects(nsPredicate: Person.ageAndOccupationPredicate(ageFrom: Int16(fromAge), ageTo: Int16(toAge), occupation: occupation))
}
func getObjects(nsPredicate: NSPredicate? = nil) -> [Person]{
let request = NSFetchRequest<Person>(entityName: "Person")
request.predicate = nsPredicate
request.sortDescriptors = [NSSortDescriptor(keyPath: \Person.age, ascending: true)]
do{
let context = PersistenceController.previewAware.container.viewContext
return try context.fetch(request)
}catch{
print(error)
return []
}
}
}
struct SimpleListView: View{
let label: String
var people: [Person]
var body: some View{
VStack{
Text(label)
List(people, id: \.objectID) { person in
HStack{
Text(person.age.description)
Text(person.occupation ?? "no occupation")
}
}
}
}
}
struct FilterOptionsView: View{
@Binding var fromAge:Int
@Binding var toAge:Int
@Binding var selectedOccupation: String
var occupations: [String]
var body: some View{
Slider(value: Binding(get: {
//Return the fromAge
Double(fromAge)
}, set: { val in
//If the ages will overlap don't update the variables
if val < Double(toAge){
fromAge = Int(val)
}
}), in: 14...100, label: {
Text("from")
})
Slider(value: Binding(get: {
//Return the toAge
Double(toAge)
}, set: {val in
//If the ages will overlap don't update the variables
if val > Double(fromAge){
toAge = Int(val)
}
}), in: 14...100, label: {
Text("to")
})
Picker("occupations", selection: $selectedOccupation, content: {
ForEach(occupations, id: \.self){ occ in
Text(occ).tag(occ)
}
Text("all occupations").tag("")
})
}
}
@available(iOS 15.0, *)
struct FilteredListView_Previews: PreviewProvider {
static var previews: some View {
FilteredListView().environment(\.managedObjectContext, PersistenceController.previewAware.container.viewContext)
}
}
extension Person{
public override func awakeFromInsert() {
self.age = Int16.random(in: 16...100)
self.occupation = ["Doctor", "Retired", "Police"].randomElement()!
_ = PersistenceManager.init(entityType: Person.self).updateObject(object: self)
}
///Predicate to filter out by age range
static func ageRangePredicate(from: Int16, to: Int16) -> NSPredicate{
NSPredicate(format: "age >= %d AND age <= %d", from, to)
}
///Predicate to filter out by age
static func agePredicate(age: Int16) -> NSPredicate{
NSPredicate(format: "age == %d", age)
}
///Predicate to filter out by occupation
static func occupationPredicate(occupation: String, ascending: Bool = false) -> NSPredicate?{
if occupation.isEmpty{
return nil
}else{
return NSPredicate(format: "occupation CONTAINS %@", occupation)
}
}
///Compound predicate to filter by age range and occupation
static func ageAndOccupationPredicate (ageFrom: Int16, ageTo: Int16, occupation: String) -> NSCompoundPredicate{
let occPred = occupationPredicate(occupation: occupation)
if occPred != nil{
let pred = NSCompoundPredicate(andPredicateWithSubpredicates: [occPred!, ageRangePredicate(from: ageFrom, to: ageTo)])
return pred
}else{
let pred = NSCompoundPredicate(andPredicateWithSubpredicates: [ageRangePredicate(from: ageFrom, to: ageTo)])
return pred
}
}
}
这就是 Person 在 CoreData 中的样子
当您运行代码时,它看起来像这样。滑块控制年龄,他选择职业