SwiftUI: ObservableObject не работает с инициализацией fetchRequest
У меня есть ObservableObject, объявленный в моем DateView следующим образом:
import SwiftUI
class SelectedDate: ObservableObject {
@Published var selectedMonth: Date = Date()
var startDateOfMonth: Date {
let components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
let startOfMonth = Calendar.current.date(from: components)!
return startOfMonth
}
var endDateOfMonth: Date {
var components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
components.month = (components.month ?? 0) + 1
let endOfMonth = Calendar.current.date(from: components)!
return endOfMonth
}
}
struct DateView: View {
// other code
}
Я могу получить доступ к startDateOfMonth и endDateOfMonth в других представлениях, например:
Text("\(self.selectedDate.startDateOfMonth)")
Но у меня возникает проблема, когда я пытаюсь использовать эти переменные из ObservableObject внутри инициализатора fetchRequest в моем TransactionsListView:
import SwiftUI
struct TransactionsListView: View {
@Environment(\.managedObjectContext) var managedObjectContext
var fetchRequest: FetchRequest<NPTransaction>
var transactions: FetchedResults<NPTransaction> { fetchRequest.wrappedValue }
@EnvironmentObject var selectedDate: SelectedDate
// other code
init() {
fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)
], predicate: NSPredicate(format: "date >= %@ AND date < %@", self.selectedDate.startDateOfMonth as NSDate, self.selectedDate.endDateOfMonth as NSDate))
}
var body: some View {
// other code
}
}
Я получаю сообщение об ошибке:
Переменная self.fetchRequest использовалась перед инициализацией
Что я делаю не так? Я пытался использовать эти переменные внутри init без self. Та же ошибка. Единственный способ, которым это работало, заключался в том, что эти startDateOfMonth и endDateOfMonth были не в ObservableObject, а как переменные в представлении предков, которые я передавал в качестве параметров своему представлению с помощью fetchRequest init. Но у меня мало таких представлений, и я хотел использовать ObservableObject, чтобы не передавать одни и те же параметры в подвиды все время.
3 ответа
Проблема в том, что нельзя использовать @EnvironmentObject
в init()
. Возможный вариант - передатьEnvironmentObject
в качестве параметра View. Затем используйте этот параметр внутри файла init. Вот конструктор для представления, тогда вы можете получить доступ к start и endTime с локальной переменнойselectedDate
.
init(selectedDate : SelectedDate)
{
//now you can access selectedDate here and use it in FetchRequest
fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)], predicate: NSPredicate(format: "date >= %@ AND date < %@", selectedDate.startDateOfMonth as NSDate, selectedDate.endDateOfMonth as NSDate))
}
Где бы вы ни называли это представление, пройдите мимо EnvironmentObject
как параметр.
//Declared as Environment Object
TransactionsListView(selectedDate : self.selectedDate)
У тебя не должно быть init()
в View
. Создаются представления и вызывается метод body (который должен быть максимально быстрым), затем визуализируется фактическое представление, а затем все структуры представления отбрасываются, отображая все, что вы делаете внутриinit
бессмысленно и просто замедлит работу SwiftUI. Вам необходимо использовать оболочки свойств, которые позволяют данным сохраняться между многократным созданием представления. Однако в вашем случае вы не можете использовать@FetchRequest
потому что он статичен, и вам нужно динамически изменять его в зависимости от выбранных дат.
Вы можете исправить это, сделав выбранную дату структурой, которая использует @Binding
если он передан или @State
если твой View
несет ответственность за это. Затем выполните загрузку с помощьюNSFetchedResultsController
в TransactionsListViewModel : ObservableObject
с помощью @Published
для Array<NPTransaction>
транзакций и создайте его в своем View
с помощью @StateObject
что позволяет ему сохраняться между несколькими запусками View. Вызвать метод выборки модели из.onAppear()
который будет выполнять выборку с использованием выбранных дат, обновит @Published
транзакции, о которой будет уведомлен SwiftUI, и вызывает View
тело, чтобы называться снова, и ваш List
можно обновлять транзакциями в пределах вашего диапазона дат.
Непроверенный образец кода:
NPTransaction.swift
import Foundation
import CoreData
@objc(NPTransaction)
public class NPTransaction: NSManagedObject {
@nonobjc public class func sortedFetchRequest() -> NSFetchRequest<NPTransaction> {
let fr : NSFetchRequest<NPTransaction> = NPTransaction.fetchRequest()
fr.sortDescriptors = [NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)]
return fr
}
}
Транзакции ListViewModel.swift
class TransactionsListViewModel : NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
lazy var fetchedResultsController : NSFetchedResultsController<NPTransaction> = {
let frc = NSFetchedResultsController<NPTransaction>(fetchRequest: NPTransaction.sortedFetchRequest(), managedObjectContext: PersistenceController.shared.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
@Published
var transactions : Array<NPTransaction> = []
func fetch(selectedDate:SelectedDate){
fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "date >= %@ AND date < %@", selectedDate.startDateOfMonth as NSDate, selectedDate.endDateOfMonth as NSDate)
try! fetchedResultsController.performFetch()
transactions = fetchedResultsController.fetchedObjects ?? []
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
transactions = fetchedResultsController.fetchedObjects ?? []
}
}
TransactionsListView.swift
import SwiftUI
struct TransactionsListView: View {
@StateObject var model = TransactionsListViewModel()
@Binding var selectedDate: SelectedDate
var body: some View {
List {
ForEach(model.transactions) { item in
...
}
}
.onAppear() {
model.fetch(selectedDate)
}
}
Swift гарантирует, что все свойства инициализируются после инициализации.
Когда вы обращаетесь к свойству объекта, предполагается, что объект уже инициализирован, что не так, когда вы обращаетесь к нему в середине инициализации.
Что происходит, когда вы инициализируете fetchRequest
, вы получаете доступ self.selectedDate
(то же самое для компилятора, независимо от того, добавляете вы себя или нет), что предполагает, что self инициализирован, что означает, что, fetchRequest
, как одно из свойств self, готов к использованию. Отсюда ошибка компилятора:
Переменная self.fetchRequest использовалась перед инициализацией
Сделать fetchRequest
вычисляемое или ленивое свойство должно работать. (не слишком знаком с@FetchRequest
)
Надеюсь это поможет.