There is a FetchedResultsController for SwiftData in the SwiftDataX open source package. Also a @DynamicQuery where you can dynamically change the fetch params.
Post
Replies
Boosts
Views
Activity
This works for me on iOS 18.1
NSNotification.Name(rawValue: "_SwiftDataModelsChangedInContextNotificationPrivate"), object: modelContext)
I had the same error and fixed it by adding an extra @preconcurrency to the protocol conformance, e.g.
... : @preconcurrency DynamicProperty {
Mine actually looks like this though:
@MainActor @propertyWrapper @preconcurrency public struct DynamicQuery<ResultType>: @preconcurrency DynamicProperty where ResultType: PersistentModel {
I noticed that @FetchRequest has did it on a protocol extension, maybe that was their trick:
extension FetchRequest : DynamicProperty {
@MainActor @preconcurrency public mutating func update()
}
I wonder why their update func is mutating, that must be a mistake surely. The behaviour of @FetchRequest is rather strange, e.g. when re-init it loses any dynamically configured properties. I have had to implement my own to fix it, it's here: https://github.com/malhal/SwiftUICoreDataFetchRequestRedesign
Binding is for structs since you have an object it is just:
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
Note there are 2 design flaws with your code:
You shouldn't name your views with model types.
Only pass the data the view needs not the whole object, e.g. it could be something like this:
var body: some View {
List(library.books) { book in
BigView(topText: book.title, bottomText: "\(book.releaseDate, format: .date)")
}
}
I'm beginning to think this is a bug because when adding ids the unexpected behaviour causes a crash:
Group {
Text("1")
Text("2")
Text("3")
}
.toolbar(id: "myToolbar") {
ToolbarItem(id: "myItem", placement: .primaryAction) {
Button("Hi") {
}
}
}
Crash log
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'index out of bounds for arranged subview: index = 3 expected to be less than or equal to 2'
*** First throw call stack:
(
0 CoreFoundation 0x00000001804b70ec __exceptionPreprocess + 172
1 libobjc.A.dylib 0x000000018008ede8 objc_exception_throw + 72
2 CoreFoundation 0x00000001804b6ffc -[NSException initWithCoder:] + 0
3 UIKitCore 0x0000000185f1da54 -[UIStackView insertArrangedSubview:atIndex:] + 144
4 UIKitCore 0x0000000184fa94c4 -[_UIButtonBar _layoutBar] + 2444
5 UIKitCore 0x0000000184fabb14 __42-[_UIButtonBarStackView updateConstraints]_block_invoke + 40
6 UIKitCore 0x000000018600b380 +[UIView(Animation) performWithoutAnimation:] + 68
7 UIKitCore 0x0000000184fabac0 -[_UIButtonBarStackView updateConstraints] + 84
8 UIKitCore 0x0000000185f3508c -[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 316
9 UIKitCore 0x0000000185f354e0 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 760
10 UIKitCore 0x0000000185f353fc -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 532
11 CoreAutoLayout 0x00000001daa63668 -[NSISEngine withBehaviors:performModifications:] + 76
12 UIKitCore 0x0000000185f35aec __100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 92
13 UIKitCore 0x0000000185f34728 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 104
14 UIKitCore 0x0000000185f35778 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 144
15 UIKitCore 0x000000018501b2a4 -[_UINavigationBarContentViewLayout layoutSubviews] + 156
16 UIKitCore 0x000000018500fb48 -[_UINavigationBarContentView layoutSubviews] + 148
17 UIKitCore 0x0000000186016438 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2404
18 QuartzCore 0x000000018b059eb0 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 432
19 UIKitCore 0x000000018600583c -[UIView(Hierarchy) layoutBelowIfNeeded] + 308
20 UIKitCore 0x0000000185284068 -[UINavigationController _positionNavigationBarHidden:edge:initialOffset:] + 584
21 UIKitCore 0x000000018528423c -[UINavigationController _positionNavigationBarHidden:edge:] + 292
22 UIKitCore 0x0000000185293d7c -[UINavigationController __viewWillLayoutSubviews] + 128
23 UIKitCore 0x000000018527bb00 -[UILayoutContainerView layoutSubviews] + 168
24 UIKitCore 0x0000000186016438 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2404
25 QuartzCore 0x000000018b059eb0 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 432
26 QuartzCore 0x000000018b064c34 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 124
27 QuartzCore 0x000000018af99c58 _ZN2CA7Context18commit_transactionEPNS_11TransactionEdPd + 464
28 QuartzCore 0x000000018afc8468 _ZN2CA11Transaction6commitEv + 652
29 UIKitCore 0x0000000185ab8260 __34-[UIApplication _firstCommitBlock]_block_invoke_2 + 32
30 CoreFoundation 0x000000018041b0ec __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 20
31 CoreFoundation 0x000000018041a824 __CFRunLoopDoBlocks + 352
32 CoreFoundation 0x00000001804150c8 __CFRunLoopRun + 812
33 CoreFoundation 0x0000000180414960 CFRunLoopRunSpecific + 536
34 GraphicsServices 0x000000019016fb10 GSEventRunModal + 160
35 UIKitCore 0x0000000185a9f650 -[UIApplication _run] + 796
36 UIKitCore 0x0000000185aa3848 UIApplicationMain + 124
37 SwiftUI 0x00000001d1cedae4 $s7SwiftUI17KitRendererCommon33_ACC2C5639A7D76F611E170E831FCA491LLys5NeverOyXlXpFAESpySpys4Int8VGSgGXEfU_ + 164
38 SwiftUI 0x00000001d1ced80c $s7SwiftUI6runAppys5NeverOxAA0D0RzlF + 84
39 SwiftUI 0x00000001d1a2fe1c $s7SwiftUI3AppPAAE4mainyyFZ + 148
40 Test.debug.dylib 0x0000000102f20fac $s4Test0A3AppV5$mainyyFZ + 40
41 Test.debug.dylib 0x0000000102f21294 __debug_main_executable_dylib_entry_point + 12
42 dyld 0x0000000100259410 start_sim + 20
43 ??? 0x0000000100426154 0x0 + 4299317588
44 ??? 0xe735800000000000 0x0 + 16660363134015373312
)
libc++abi: terminating due to uncaught exception of type NSException
Submitted feedback on this crash and linked to this forum.
FB15011577
Additional info (unable to edit my initial post anymore):
I know Group is designed to apply the modifier to each item in the group however that isn't the case for .onAppear or .onChange and it seems a bit odd for me for this to happen with .toolbar because it seems like it should only be applicable to style modifiers, e.g. .font etc.
Just a comment: You can remove .onAppear { because @StateObject inits the object at on appear anyway and you have the same actions inside your init.
You actually shouldn't be passing whole objects into child Views and instead only pass the params of the object that the View needs to do its job. If it needs write access to a property then you can convert the object to @Bindable and then pass in a binding to the property. This is one of the basic design principles of SwiftUI, "only pass in the data the View needs to do its job" [Data Essentials in SwiftUI WWDC 2020]
By the way, unfortunately you can't currently update @Query with a parameter passed in from a parent View because it is missing the ability to change the filter, e.g. it's missing something like:
let somethingPassedIn: String
let query = Query<Item, [Item]>
var items: [Item] {
query.filter = // change filter based on somethingPassedIn but impossible because query is missing the filter property
return query.wrappedValue
}
I know some developers try to re-init the @Query with a param from a parent but that is not the correct approach because these property wrappers are designed to be sources of truth and there is no guarantee they will update correctly if the init params change.
As a work around you could fallback to Core Data's @FetchRequest which does have the ability to dynamically change the query, e.g.
let fetchRequest = FetchRequest<Item>(sortDescriptors[])
var items: FetchedResults<Item> {
fetchRequest.predicate = // dynamically update it is possible
return fetchRequest.wrappedValue
}
We currently can't change the filter/predicate of the Query but hopefully in the future we would be able to do something like:
@State private var selectedMonth: String = ""
@State private var selectedYear: String = ""
let query = Query<Transaction, [Transaction]>()
var transactions: [Transaction] {
query.descriptor.predicate = #Predicate<Transaction> { transaction in
transaction.date.monthString == selectedMonth && transaction.date.yearString == selectedYear
}
query.sort = \Transaction.date
return query.wrappedValue
}
This pattern, of changing the predicate lazily when the results are requested makes more sense to me than trying to re-init the property wrapper with different params.
Currently descriptor is a private property of Query for some reason.
Feedbacks FB12367164, FB12292784
Hopefully we will be able to replace the predicate in a future version of SwiftData, here is how I would like it to work:
struct RemindersList: View {
let todoList: TodoList
let query = Query<Reminder, [Reminder]>
var reminders: [Reminder] {
let todoListID = todoList.persistentModelID
let predicate = #Predicate<Reminder> { reminder in
reminder.todoList?.persistentModelID == todoListID
}
query.predicate = predicate
return query.wrappedValue
}
var body: some View {
List(reminders) {
...
}
}
}
Then I would simply be able to do RemindersList(todoList: todoList)
I submitted this as feedback FB12367164
you need to learn @Binding for that. You would create the data in the model that you want to show in the text fields and then bind the fields to that data. There is no @State in this case. Sometimes the UI controls have slightly different types to your model property types in which case you can use computed bindings to do the transformation. The same way you would use computed vars to transform readonly model or state data when passing it down to child Views.
Can you show how you are setting the container on the root view and how you are initializing your container. Maybe you are accidentally initing it twice.
onAppear is too late to set state. You should have the state configured first, then body is designed to create all the Views depending on the state. This is a crucial part of SwiftUI's design where "views are a function of state".
onAppear is designed for external actions unrelated to state.
Since you are using SwiftData it's not @State it is @Query to fetch the models and then bind the Views directly to them. If the types are different you can use computed bindings to convert.
To use async/await from SwiftUI it is .task not Task and to get a background thread for a func declared inside the View struct (which is annotated MainActor) use nonisolated func, e.g.
Button(isRunning ? "Stop" : "Run Concurrent Tasks") {
results.removeAll()
isRunning.toggle()
}
.task(id: isRunning) {
// main thread
if isRunning {
async let task1 = countingTask(name: "Task 1", target: 1000)
}
}
...
}
// without nonisolated it would be main actor and thus always on main thread
func nonisolated countingTask(name: String, target: Int) async -> String {
// background thread
If you moved the func to a custom struct then it wouldn't need nonisolated. If that controller struct was an EnvironmentKey then it could be mocked for Previews.
Same problem here. In .fileImporter I even call startAccessingSecurityScopedResource() and am able to read the file in. But afterwards NSMetadataQueryAccessibleUbiquitousExternalDocumentsScope doesn't find the file when searching for it's name with NSMetadataItemFSNameKey.
iOS 17.5.1 (21F90)