.onDelete resets NSPredicate (Core Data, SwiftUI)

Hello

I have a list of data in SwiftUI. The data shown in the list can be saved or deleted by using Core Data. In the @FetchRequest property that I am using to display data, I initialized an NSPredicate and in the view, I gave the possibility to the user to change the value of the predicate so that he can filter data, and that is all working, the problem shows up when I delete data from the list when I do so the predicate becomes nil and I don't know why.

Here is the code

struct SectionList: View {
    @FetchRequest(
        entity: LifetimeInputs.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)], predicate: nil
    ) var lifetimeInputsModel: FetchedResults<LifetimeInputs>
   
    
    @FetchRequest(entity: Limit.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Limit.date, ascending: false)]) var limit: FetchedResults<Limit>
    
    @Environment(\.dynamicTypeSize) var dynamicTypeSize
    
    var size: CGFloat{
        if UIDevice.current.userInterfaceIdiom == .phone {
            switch dynamicTypeSize {
            case .xSmall: return 11
            case .small: return 13
            case .medium: return 15
            case .large: return 17
            case .xLarge: return 19
            case .xxLarge: return 21
            case .xxxLarge: return 23
            default: return 23
            }
        } else {
            switch dynamicTypeSize {
            case .xSmall: return 13
            case .small: return 15
            case .medium: return 17
            case .large: return 19 
            case .xLarge: return 21
            case .xxLarge: return 23
            case .xxxLarge: return 25
            case .accessibility1: return 27
            case .accessibility2: return 29
            default: return 29
            }
        }
    }
    
    @StateObject var lifeTimeInputsViewModel = LifeTimeInputsViewModel()
    
    @Environment(\.managedObjectContext) private var viewContext
    
    
    var conversion: Double {
        if !limit.isEmpty{
            switch limit.last?.unita {
            case Unit.ml.rawValue: return 1
            case Unit.oz.rawValue: return 29.574
            default: return 1
            }
        }
        return 1
    }
    
    @State private var wantsToFilter: Bool = false
    @State private var dateSelected = Date()

    var body: some View {
        Section{
            HStack{
                Text("Filter")
                
                Spacer()
                
                Image(systemName: wantsToFilter ? "checkmark.circle" : "xmark")
                    .font(.system(size: size + 6))
                    .foregroundColor(wantsToFilter ? .green : .red)
                    .onTapGesture {
                        wantsToFilter.toggle()
                        if wantsToFilter{
                            lifetimeInputsModel.nsPredicate = NSPredicate(
                                format: "date >= %@ && date <= %@",
                                Calendar.current.dateInterval(of: .day, for: dateSelected)!.start as CVarArg,
                                Calendar.current.dateInterval(of: .day, for: dateSelected)!.end as CVarArg
                            )
                        } else{
                            lifetimeInputsModel.nsPredicate = nil
                        }
                    }
            }
            
            DatePicker("Date", selection: $dateSelected, displayedComponents: .date)
            
        } header: {
            Text("Filter")
                .font(.system(size: size - 4))
        }
        .onChange(of: dateSelected, perform: { _ in
            if wantsToFilter{
                lifetimeInputsModel.nsPredicate = NSPredicate(
                    format: "date >= %@ && date <= %@",
                    Calendar.current.dateInterval(of: .day, for: dateSelected)!.start as CVarArg,
                    Calendar.current.dateInterval(of: .day, for: dateSelected)!.end as CVarArg
                )
            }
        })
        
        Section{
            ForEach(lifetimeInputsModel){ lifetimeInputs in
                HStack{
                    Text("\(lifetimeInputs.valori / conversion, specifier: format(unita: !limit.isEmpty ? limit[limit.count - 1].unita ?? ml : ml)) \(!limit.isEmpty ? limit[limit.count - 1].unita ?? ml: ml)")
                        .font(.system(size: size))
                    
                    Spacer()
                    
                    Text("\(dateFormatter.string(from: lifetimeInputs.date ?? Date()))")
                        .font(.system(size: size))
                }
            }
            .onDelete{lifeTimeInputsViewModel.deleteItems(offsets: $0, lifetimeInputsModel: lifetimeInputsModel); }
        } header: {
            Text("History \(lifetimeInputsModel.count)".localized()).font(.system(size: size - 4))
        }
    }
}

Thank You!

Answered by Jad-T in 700440022

I found a solution.

    @FetchRequest(
        entity: LifetimeInputs.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)], predicate: nil
    ) var lifetimeInputsModel: FetchedResults<LifetimeInputs>

Becomes this (in my case)

@FetchRequest var lifetimeInputs: FetchedResults<LifetimeInputs>

    init(nsPredicate: NSPredicate, sortDescriptors: [NSSortDescriptor]) {
        let entity = LifetimeInputs.entity()
        let fetchRequest = FetchRequest<LifetimeInputs>(entity: entity, sortDescriptors: sortDescriptors, predicate: nsPredicate, animation: .default)
        self._lifetimeInputs = fetchRequest
    }

The nsPredicate and the sortDescriptors must be passed to a parent view and you have to change/update (if you need to) them in that parent view.

Hope it works also for you

same here! Did you find out the answer now?

And I wonder if you also have another view which fetch the same entity( in your case is LifetimeInputs) with the same predicate, and then updated your data in one of the view, then the predicate became nil, but if the predicate(in your case is the date range) is different, everything works fine.

If you find a way to fix this issue, please let me know, thank you :)

Accepted Answer

I found a solution.

    @FetchRequest(
        entity: LifetimeInputs.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)], predicate: nil
    ) var lifetimeInputsModel: FetchedResults<LifetimeInputs>

Becomes this (in my case)

@FetchRequest var lifetimeInputs: FetchedResults<LifetimeInputs>

    init(nsPredicate: NSPredicate, sortDescriptors: [NSSortDescriptor]) {
        let entity = LifetimeInputs.entity()
        let fetchRequest = FetchRequest<LifetimeInputs>(entity: entity, sortDescriptors: sortDescriptors, predicate: nsPredicate, animation: .default)
        self._lifetimeInputs = fetchRequest
    }

The nsPredicate and the sortDescriptors must be passed to a parent view and you have to change/update (if you need to) them in that parent view.

Hope it works also for you

.onDelete resets NSPredicate (Core Data, SwiftUI)
 
 
Q