【问题标题】:SwiftUI - Dynamically filter and fetch CoreData records via attributesSwiftUI - 通过属性动态过滤和获取 CoreData 记录
【发布时间】:2022-01-09 23:23:28
【问题描述】:

我的数据模型中保存了记录,但我想知道如何根据传入的参数动态过滤和获取数据,例如,我的数据模型是 people 类型,其中包括他们的姓名、年龄和职业。

假设我希望返回 20 条记录,但每条记录可能略有不同。这些是我的参数,它们是静态的:

let ageParamters = [60, 24, 35, 64, 29, 39, 90, 92, 38, 48, 23, 58, 62, 78, 19, 18, 29, 48, 40]

我希望为一个年龄与数组的当前索引匹配的人获取一条记录,因此第一个人的年龄为 60 岁,下一个为 24 岁,之后为 35 岁,依此类推。

但在此基础上,我如何通过观察年龄和职业等两个字段来过滤我的数据:

let ageOccupationParamters = [[20: "Doctor"], [78: "Retired"], [32: "Police"]] 

我会很感激我能得到的所有帮助。提前谢谢你。

【问题讨论】:

  • 有很多 Core Data 教程可以展示你想要的东西。这不是获取教程的地方。

标签: core-data swiftui


【解决方案1】:

这是一个示例,它归结为 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 中的样子

当您运行代码时,它看起来像这样。滑块控制年龄,他选择职业

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    • 2012-03-28
    • 2012-11-02
    相关资源
    最近更新 更多