Reputation: 8276
My first attempt was to set the property wrapper's nsPredicate
dynamic property in .onAppear
, but if the view gets reinitialized for any reason, the predicate set by .onAppear
is lost. So I went back to using the init pattern.
Here is what I thought should work (but doesn't) and something that does work (however mysteriously):
struct ItemEditView : View {
var item: Item
@FetchRequest(fetchRequest: Attribute.fetchRequestAllInOrder(), animation: .default)
var attributes: FetchedResults<Attribute>
init(item: Item) {
self.item = item
// This is how I would have expected to set the dynamic property at View initialization, however
// it crashes on this statement
attributes.nsPredicate = NSPredicate(format: "item == %@", item)
// Not sure why the below works and the above does not.
// It seems to work as desired, however it receives this runtime warning:
// "Context in environment is not connected to a persistent store coordinator"
$attributes.projectedValue.wrappedValue.nsPredicate = NSPredicate(format: "item == %@", item)
}
var body: some View {
List {
ForEach(attributes) { attribute in
Text("Name:\(attribute.name) Order:\(attribute.order)")
}
}
}
}
So, why does the first assignment to nsPredicate crash? And after commenting out that first one, why does the second one work? Is the warning message a real issue? Is there a better way to do this? It seems like there should be a simple way to do this using the new dynamic properties.
Upvotes: 3
Views: 387
Reputation: 30811
FetchRequest
is a DynamicProperty
that is only ready just before body
is called which is before it is about to appear. If you implement a DynamicProperty
yourself you'll see the func update
which is called before the View's body is called and where it can access its own wrapped properties like @State
and in the case of FetchRequest
how it gets the managedObjectContext
from the @Environment
.
So to translate your 2 errors. The first one probably was "attempting to access property without being installed in a View" that is because it was used before body
is ready to be called, i.e. it is not close to appearing yet. And the other one about not finding the context, is because it was accessed before its func update
was called.
Another way to set the predicate is:
struct ItemEditView : View {
let item: Item
var request = FetchRequest<Attribute>(predicate: NSPredicate(value: false))
var results: FetchedResults<Attribute> {
request.wrappedValue.nsPredicate = NSPredicate(format: "item == %@", item)
request.wrappedValue.sortDescriptors = ...
return request.wrappedValue
}
An advantage to doing it this way is the predicate or sort could also use an @State
like a search box or sort button.
Upvotes: 0
Reputation: 8276
It turns out that (re)setting the nsPredicate
property of the @FetchRequest
in onAppear
is really the way to go. However, to make this work, you must make sure that your View's init()
method does not get called again after onAppear
is called. There are several valuable hints on how to accomplish this in the Demystify SwiftUI session from this year's WWDC (WWDC21-10022).
Upvotes: 1