Post

Replies

Boosts

Views

Activity

Reply to How do you create an inverse relationship to the same model in SwiftData
You need to add another property that can be used for the inverse relationship. You don't have to use this property, it's just to satisfy SwiftData's relationship requirements. For example: @Model final class Person { var name = "" @Relationship(inverse: \Person.parent) var children: [Person]? @Relationship var parent: Person? // can make private if not using elsewhere }
Aug ’23
Reply to How to use the .onMove SwiftUI modifier to reorder SwiftData elements?
One way to do this is to have a property in the model that stores the relative order index. Start by adding this property to the model class: var orderIndex: Int This will need to be added to the initialiser as well. When querying for a list of items, you can sort by this property: @Query(sort: \ChecklistItem.orderIndex) private var items: [ChecklistItem] For the view's move action you can do this: .onMove { source, destination in // Make a copy of the current list of items var updatedItems = items // Apply the move operation to the items updatedItems.move(fromOffsets: source, toOffset: destination) // Update each item's relative index order based on the new items // Can extract and reuse this part if the order of the items is changed elsewhere (like when deleting items) // The iteration could be done in reverse to reduce changes to the indices but isn't necessary for (index, item) in updatedItems.enumerated() { item.orderIndex = index } } That last part I would put in an extension like so: extension [ChecklistItem] { func updateOrderIndices() { for (index, item) in enumerated() { item.orderIndex = index } } } and you can instead write this for better ease of use and reusability: items.updateOrderIndices() // run after changes to the order of `items` When you add a new item, you will need to provide a value for its orderIndex property. This would be resolved as the number of current items (as indexing starts at 0). So if there are 5 items (with indices 0, 1, 2, 3, 4), the new index would be 5. Here's an example of how to do that: // Fetch the number of all items that contribute to the relative index ordering let descriptor = FetchDescriptor<ChecklistItem>(/* add filtering or sorting if needed */) let count = (try? modelContext.fetchCount(descriptor)) ?? 0 // Pass the next index to the new item let newItem = ChecklistItem(..., orderIndex: count) modelContext.insert(newItem) This is only a basic solution and probably isn't the most optimised as any change, even to a single item, results in all the items being updated. Hope it helps anyway!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to Is it possible to color a LineMark based on if it is above or below a certain value?
What do you want colouring, the whole line the same color or segments of the line different colours? You can colour individual points on the line differently, but separate segments I'm not sure about. I think the line can only be one colour. Anyway, if you wanted different segments it might look a bit odd as they're supposed to be transitions to other points. Saying that, you could use a gradient but I don't know if that's what you're looking for.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to View won't update in ForEach loop
The problem you have is you're updating the local thing variable when the increment button is tapped. The thing variable is a copy of product.price and so changes to thing will not be reflected elsewhere. This is what the @State property wrapper is used for: to update the view's body when it's underlying value changes. As you already have a state property, base, you can extract read/write values that can be updated from within the view body and will have the behaviour you are expecting. You can use the ForEach initialiser that takes a binding to a collection and gives you a binding to each of its elements to work with. Here is an example that works: ForEach($base) { $product in // `product` is a variable HStack { Text(product.name) Spacer() Button { product.price += 1 } label: { Image(systemName: "plus") } Text("\(product.price, specifier: "%.2f")") } } Just a note on this code: $product is of type Binding<Product>. It provides a way of reading and writing to another value, in this case an element of the base collection. It can be useful if, for example, you have a Stepper control which requires a binding to update its value to and from. product is of type Product. This is a variable and can be read and written to. It also updates the view body when it changes which is why it is useful here.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to how to assign textField's default value in list (swiftUI)
attValue = isEnableBatch ? "batch Enable" : "\(index)" This line cannot be placed inside of a view. It alters the state of the view whilst it is being rendered, which could caused it to re-render infinitely. It must be placed somewhere else, like a closure inside of a view. You can either place it in the button's action just after you toggle the boolean value: Button { isEnableBatch.toggle() attValue = isEnableBatch ? "batch Enable" : "\(index)" // update text field text } label: { ... } or place it in an onChange modifier: .onChange(of: isEnableBatch) { newValue in attValue = newValue ? "batch Enable" : "\(index)" // update text field text whenever boolean changes }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to NavigationSplitView crashes in Xcode, iOS Beta 7
From the iOS & iPadOS 17 Beta 8 Release Notes: SwiftUI Known Issues On iOS, using an Observable object’s property as a selection value of a List inside NavigationSplitView may cause a “Simultaneous accesses to …” error when a list selection is made via tap gesture. (113978783) Workaround: There is no current workaround for Observable properties. Alternatives include factoring out the selection value into separate state stored outside the object, or using ObservableObject instead. Your best bet is to wait for the next beta (or RC) and try again with that. I would only use the workaround if you need a fix right now.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to iOS 17b6: Simultaneous accesses to ..., but modification requires exclusive access crash using Observation and SwiftUI
From the iOS & iPadOS 17 Beta 8 Release Notes: SwiftUI Known Issues On iOS, using an Observable object’s property as a selection value of a List inside NavigationSplitView may cause a “Simultaneous accesses to …” error when a list selection is made via tap gesture. (113978783) Workaround: There is no current workaround for Observable properties. Alternatives include factoring out the selection value into separate state stored outside the object, or using ObservableObject instead. Your best bet is to wait for the next beta (or RC) and try again with that. I would only use the workaround if you need a fix right now.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to SwiftData: how to reference same model?
The inverse of a SwiftData relationship cannot point back to itself. This is what the circular reference error is referring to. You need to add another property that can be used for the inverse relationship. You don't have to use this property, it's just to satisfy SwiftData's relationship requirements. For example: @Model final class Person { var name = "" @Relationship(deleteRule: .nullify, inverse: \Person.inverseRef) var ref: Person? = nil // Call this whatever you want // Can make private if not using elsewhere @Relationship var inverseRef: Person? } Testing this and adding dump(peter.ref) after deleting cristina yields this output: Optional(Person) ▿ some: Person #0 - _name: Person._SwiftDataNoType ▿ _ref: Optional(Person._SwiftDataNoType()) - some: Person._SwiftDataNoType - _inverseRef: nil ... So peter.ref is not nil but it seems to be an "empty" model with "empty" values, essentially nullified. Whether this is the behaviour you want, I don't know, but I hope it fixes your inverse relationship problem. BTW, nullify is the default delete rule for relationships so it doesn't need specifying.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How do I filter @Query with bound item
In your Chore model class, you have the property assignedTo of type FamilyMember. Because you are using SwiftData, an implicit relationship is created. However, in order for the relationship to be complete it needs to have an inverse property in the other model. You need to add a property to FamilyMember that can be that inverse. Here's an example of what it could look like: @Model class Chore { @Relationship(inverse: \FamilyMember.chores) var assignedTo: FamilyMember } @Model class FamilyMember { @Relationship var chores: [Chore] } You could remove the Relationship macros and just have that chores property by itself and let SwiftData figure out the relationships, but I haven't tried this yet; it's best to do it yourself, plus you can visually see where the relationships are. In my model classes, relationships are marked as optional, but that's just to satisfy CloudKit. I don't know if they have to be optional regardless (or have default values), but if you run into issues try doing that. Also, I am just guessing that the lack of an inverse property is your problem based off of your code. I haven't tested anything so feel free to let me know if I'm talking about the right stuff.
Sep ’23
Reply to customized Navigation bar
There is a solution that dips into UIKit which will set the navigation bar's back button to display only the indicator image (chevron) without the title. // Requires iOS 14.0+ extension UINavigationController { open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() navigationBar.topItem?.backButtonDisplayMode = .minimal } } Note, however, that this will remove the title from every back button in your app.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to Weird @Binding behavior, doesn't update the value until I set it into different one
I remember seeing this somewhere before. The issue is that the contents of the sheet are initially created with the initial value (0) before the sheet is first shown. Subsequent sheet presentations then use the new changed value. The solution to this is to capture the current variable's value as the sheet is about to be shown. Here is how you would do that: .sheet(isPresented: $bindingValueBool, content: { [bindingValueInt] in // use the captured value which will be correct Text("This is the selected number \(bindingValueInt)") }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How to retrieve values set in Hashable paths when using a router to navigate through views
You will need to extract the id property from the Form case value using Swift's pattern matching capabilities. Here is an example of how you could achieve this: // Add a computed property to the view var formID: String { if case .Form(let id) = router.paths.last { id } else { "No ID" // handle the case where the ID couldn't be extracted } } // Use this id property as a string Text(formID)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23
Reply to How to change the background behind a presented view
You can use the presentationBackground(_:) modifier. Here is an example of how you can use it: .sheet(...) { SheetView() .presentationBackground(.red.opacity(0.5)) // pass in a colour or even a custom view } Note: the translucency of the sheet background means content behind the sheet presentation will show through. If you don't want this behaviour use the regular background(_:ignoresSafeAreaEdges:) modifier with a maximum frame.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’23