SwiftUI - переменная self.fetchRequest, используемая перед инициализацией

Я пытаюсь создать динамические фильтры элементов Core Data с помощью SwiftUI. Основной объект данных: Item. Атрибуты: дата (Date), done (Boolean), name (String).

Предоставленный код создает 3 экземпляра объекта Item. Над списком есть сегментированный элемент управления для изменения значения фильтра. Все отключает фильтр (для doneFilter установлено значение nil). Не завершено включает фильтр (устанавливает для doneFilter значение false).

Также существует инициализация, которая устанавливает fetchRequest на основе сегментированного управления.

ПРОБЛЕМА

Сбой сборки с ошибкой:

Переменная self.fetchRequest использовалась перед инициализацией

Что не так с моим кодом?

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)
            ])
        }
    }
}

1 ответ

Вот аналогичный код, который работает. Основное отличие состоит в том, что сегментированный элемент управления перемещается в ContentView и передает doneFilter в ListView через параметры в ListView(filter: doneFilter). Также есть некоторые изменения в коде инициализации в ListView.

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)
            ])
        }
    }
}

Но я до сих пор не знаю, как заставить работать первую версию. Один сегментированный элемент управления и инициализация обоих в ListView.

Другие вопросы по тегам