Sorry, I wasn't able to test my answer earlier and was confident that this kind of query worked but I end up with the same error: to-many key not allowed here.
I tried other alternatives but couldn't find one that worked. Doesn't seem like it's possible to use optional to-many relationships inside a Predicate (even checking for $0.tags != nil or similar will crash)
A workaround would be to query on the to-one side of the relationship, i.e Tag and compute the array of Item from the results.
It's not ideal but I couldn't find another way. Since it's working for non-optional relationships we can only hope that this will possible in future releases.
struct ItemsView: View {
@Query private var tags: [Tag]
var items: [Item] {
self.tags.lazy.compactMap(\.items).flatMap { $0 }
}
init(searchText: String) {
self._tags = Query(filter: #Predicate<Tag> {
$0.name.localizedStandardContains(search)
})
}
}
Post
Replies
Boosts
Views
Activity
As stated in the thread 742807, you should not force unwrap your properties inside the predicate.
You could use flatMap to access the unwrapped array of tags
init(searchText: String) {
_items = Query(filter: #Predicate<Item> {
if searchText.isEmpty {
true
} else {
$0.tags.flatMap {
$0.contains { $0.name.localizedStandardContains(searchText) }
} == true
}
})
}
You cannot use any instance member inside a Predicate so this shouldn't even compile
struct MyView: View {
@State private var selectedMonth: String = ""
// Instance member 'selectedMonth' cannot be used on type 'MyView'; did you mean to use a value of this type instead?
@Query(filter: #Predicate<Transaction> { $0.date.monthString == selectedMonth })
var transactions: [Transaction]
}
You can achieve what you want by putting your Query into a subview where you can provide a predicate or sort value using dependency injection.
struct ContainerView: View {
@State private var selectedMonth: String = ""
@State private var selectedYear: String = ""
var body: some View {
TransactionList(month: selectedMonth, year: selectedYear) // called every time your state properties change
.toolbar {
Menu { ... } // update your states values
}
}
}
struct TransactionList: View {
@Query private var transactions: [Transaction]
init(month: String, year: String) {
self._transactions = Query(filter: #Predicate<Transaction> {
$0.date.monthString == month && $0.date.yearString == year
}, sort: \.date)
}
}
For your information, you can retrieve SwiftData models by using FetchDescriptor.
let fetchDescriptor = FetchDescriptor<Transaction>(predicate: #Predicate { ... }, sortBy: [SortDescriptor(\.date)])
do {
let transactions = try modelContext.fetch(fechDescriptor)
} catch {
}
You can find more examples in the official documentation like Preserving your app’s model data across launches
This compile as long as the login value you use for your action is a method. Moreover, AFAIK Button doesn't have a init that takes value:selected:. Do you have a custom type call Button in your code ?
SwiftData supports only primitive types (e.g Bool, Int, String) by default. If you want to use more complex types like struct, they need to conform to Codable. In your case, you just need to add Codable conformance to your Record type: struct Record : Identifiable, Codable { ... }
It's because you force unwrap your properties inside the predicate.
It works fine if you use flatMap to access the unwrapped date value and define your searched strings outside of the predicate scope
let searchCategory = "Income"
let searchType = "Business"
#Predicate<Transaction> {
$0.date.flatMap { $0 >= startDate && $0 <= endDate } == true &&
$0.category == searchCategory &&
$0.ttype == searchType
}
It's a little unclear without code example but I'm guessing your have something like that
var person: Person // SwiftData model retrieved from a @Query
@State private var text: String = "0"
TextField(person.name, text: self.$text)
This will show "0" since the text field display the content of the value it is binded to. The first argument is used as a placeholder to describe the purpose of this field to the user (and is displayed only when the text is an empty string which is not the case here).
You have 2 options
Use a State property for the textfield value that you update when the view appears. It will no update your SwiftData model when the value changes so you will need to implement a validation/save method
struct PersonForm: View {
var person: Person
@State private var text: String = "0"
var body: some View {
VStack {
Button("Save") {
self.person.name = text
}
Text("Name", text: self.$text)
}
.onAppear {
self.text = self.person.name
}
}
}
Bind the SwiftData model property to the text field. This will update your model directly according to the user input without needing validation. IMHO, this is not the best solution since It can trigger a lot of database updates (even if SwiftData batches them) and also view updates if you reference your model in another view, SwiftUI will ask for redraw at each change the user make inside the text field
struct PersonForm: View {
@Bindable var person: Person
@State private var text: String = "0"
var body: some View {
Text("Name", text: self.$person.name)
}
}
Hope this helps
You could use an enum to create a namespace for your models
enum App {
@Model
final class List {
...
}
}
struct ContentView: View {
@Query var elements: [App.List]
}
You can provide a SortDescriptor and a fetchLimit to achieve this.
If we take the example of @DelawareMathGuy with a Location object that have a position property, you could do something like that
var fetchDescriptor = FetchDescriptor<Location>(sortBy: [
SortDescriptor(\.position, order: .reverse) // sort the object by descending position
])
fetchDescriptor.fetchLimit = 1 // retrieve only one object
let locations = try? modelContext.fetch(fetchDescriptor)
let maxPositionLocation = locations?.first
As stated by @DelawareMathGuy, derived attributes are not yet supported by SwiftData. You could create your own by defining computed properties or by using the @Transient attributes but this will not be as efficient (I advise reading Paul Hudson from 'hackingwithswift' article about this)
Swift Charts allows us to provide a custom gesture for selection by using the chartGesture(_:) modifier.
You can for exemple configure a SpatialTapGesture to get the tap location and update the chart angle value by calling the selectAngleValue(at:) method.
By using the below code, I managed to get rid of the "hold down" behaviour.
.chartGesture { chart in
SpatialTapGesture()
.onEnded { event in
let angle = chart.angle(at: event.location)
chart.selectAngleValue(at: angle)
}
}