It's expected. body needs to call the myVar getter if you want body to be called when the myVar setter is used. And you need this to happen so that the .sheet computes a new version of the closure. You can fix it by putting the bool and the myVar in the same State, e.g.
struct Content {
var myVar: Int? = nil
var presentSheet: Bool = false
}
@State var content = Content()
.sheet(isPresented: $content.presentSheet)
Post
Replies
Boosts
Views
Activity
I noticed if the Button's title is dynamic it breaks .disabled() from working, e.g.
Button("Save \(text)") { // dynamic title
items.append(ListItem(text: text))
}
.disabled(text.isEmpty) // no worky anymore
Xcode 16.2, iOS simulator 18.2
Submitted FB16539507 to request the .alert() docs be updated to state if using .disabled is supported because currently it is not mentioned. Also included this bug I noticed which is probably a waste of time since I reported the bug to the documentation team.
I'm having my own FocusState issues with TextField however I tested the OP's code in Xcode 16.2 and iPhone 16 Pro Simulator iOS 18.2 and it seems to work ok
FYI hardware keyboard is disabled from IO menu so keyboard appears during focus.
The main misatke is in:
func textField(
forColorWithID colorID: UUID
) -> some View {
ensureThatThereIsEditableText(
The ensureThatThereIsEditableText func is changing the model which is not allowed from inside body.
A few other mistakes in the SwiftUI code are:
id: \.self is not a valid id keypath it needs to be a path to a unique identifeir property or the data should implement Identifiable.
@ViewBuilder func colorTextField is not valid SwiftUI you need to make a struct ColorTextField: View {
Don't have if inside body with no else clause. Try and avoid the if completely by defining the view and doing the if inside the param, e.g. MyView(text: a ? "a" : "b")
If I was you I would redesign the data to use Identifiable and an array instead of trying to use a dictionary.
There is a FetchedResultsController for SwiftData in the SwiftDataX open source package. Also a @DynamicQuery where you can dynamically change the fetch params.
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.