3

I am trying to make dynamic filters of Core Data items with SwiftUI. Core Data Entity: Item. Attributes: date (Date), done (Boolean), name (String).

Provided code creates 3 instances of Item entity. Above the list there is a segmented control to change filter value. All turns off filter (doneFilter is set to nil). Not finished turns on filter (sets doneFilter to false).

There is also an init that sets fetchRequest based on segmented control.

PROBLEM

Build fails with error:

Variable 'self.fetchRequest' used before being initialized

What is wrong with my code?

ContentView.swift

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc
    
    var body: some View {
        
        NavigationView {
            VStack {                    
                ListView()
            }
            .navigationBarTitle("Items")
            .navigationBarItems(
                leading:
                Button(action: {
                    for number in 1...3 {
                        let item = Item(context: self.moc)
                        item.date = Date()
                        item.name = "Item \(number)"
                        item.done = false
                        
                        do {
                            try self.moc.save()
                        }catch{
                            print(error)
                        }
                    }
                }) {
                    Text("Add 3 items")
                }
            )
        }
    }
    
}

ListView.swift

import SwiftUI

struct ListView: View {
    
    @Environment(\.managedObjectContext) var moc
    var fetchRequest: FetchRequest<Item>
    var items: FetchedResults<Item> { fetchRequest.wrappedValue }
    
    @State var doneFilter :Bool? = nil
    
    var doneStatus: Binding<Int> { Binding<Int>(
        get: {
            if self.doneFilter == false {
                return 1
            } else {
                return 0
            }
    },
        set: {
            switch $0 {
            case 1:
                self.doneFilter = false
            default:
                self.doneFilter = nil
            }
    })
    }
    
    var body: some View {
        List {
        
        Picker(selection: doneStatus, label: Text("Picker")) {
            Text("All").tag(0)
            Text("Not finished").tag(1)
        }
        .pickerStyle(SegmentedPickerStyle())
        .padding()
            ForEach(items, id: \.self) {item in
                HStack {
                    Text("\(item.name ?? "default item name")")
                    
                    Spacer()
                    
                    Toggle(isOn: Binding<Bool>(
                        get: { item.done },
                        set: {
                            item.done = $0
                            try? self.moc.save()
                    })) {
                        Text("Done")
                    }
                    .labelsHidden()
                }
            }
            .onDelete(perform: removeItem)
        }
    }
    
    func removeItem(at offsets: IndexSet) {
        for offset in offsets {
            let item = items[offset]
            moc.delete(item)
        }
        try? moc.save()
    }
    
    init() {
        if let filter = doneFilter {
        fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \Item.name, ascending: true)
        ], predicate: NSPredicate(format: "done = %d", filter))
        } else {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
                NSSortDescriptor(keyPath: \Item.name, ascending: true)
            ])
        }
    }
}
mallow
  • 2,368
  • 2
  • 22
  • 63

1 Answers1

0

Here is a similar code that works. Main difference is that Segmented Control is moved to ContentView and it passes doneFilter to ListView through parameters in ListView(filter: doneFilter). There are some changes to init code in ListView as well.

ContentView.swift

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc
    
    @State var doneFilter :Bool? = nil
    
    var doneStatus: Binding<Int> { Binding<Int>(
        get: {
            if self.doneFilter == false {
                return 1
            } else {
                return 0
            }
    },
        set: {
            switch $0 {
            case 1:
                self.doneFilter = false
            default:
                self.doneFilter = nil
            }
    })
    }
    
    var body: some View {
        
        NavigationView {
            List {
                
                Picker(selection: doneStatus, label: Text("Picker")) {
                    Text("All").tag(0)
                    Text("Not finished").tag(1)
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding()
                
                ListView(filter: doneFilter)
            }
            .navigationBarTitle("Items")
            .navigationBarItems(
                leading:
                Button(action: {
                    for number in 1...3 {
                        let item = Item(context: self.moc)
                        item.date = Date()
                        item.name = "Item \(number)"
                        item.done = false
                        
                        do {
                            try self.moc.save()
                        }catch{
                            print(error)
                        }
                    }
                }) {
                    Text("Add 3 items")
                }
            )
        }
    }

}

ListView.swift

import SwiftUI

struct ListView: View {
    
    @Environment(\.managedObjectContext) var moc
    var fetchRequest: FetchRequest<Item>
    var items: FetchedResults<Item> { fetchRequest.wrappedValue }
    
    var body: some View {
        ForEach(items, id: \.self) {item in
            HStack {
                Text("\(item.name ?? "default item name")")
                
                Spacer()
                
                Toggle(isOn: Binding<Bool>(
                    get: { item.done },
                    set: {
                        item.done = $0
                        try? self.moc.save()
                })) {
                    Text("Done")
                }
                .labelsHidden()
            }
        }
        .onDelete(perform: removeItem)
    }
    
    func removeItem(at offsets: IndexSet) {
        for offset in offsets {
            let item = items[offset]
            moc.delete(item)
        }
        try? moc.save()
    }
    
    init(filter: Bool?) {
        if let filter = filter {
        fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \Item.name, ascending: true)
        ], predicate: NSPredicate(format: "done = %d", filter))
        } else {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
                NSSortDescriptor(keyPath: \Item.name, ascending: true)
            ])
        }
    }
}

But I still don't know how to make first version work. The one Segmented Control and init both on ListView.

mallow
  • 2,368
  • 2
  • 22
  • 63