vadimkrutovlv/swift-data-helpers
A growing set of SwiftData helpers for SwiftUI and app architecture.
Overview
SwiftDataHelpers is a small, focused package that collects convenience APIs for working with SwiftData in SwiftUI apps. The goal is to keep common patterns simple, readable, and testable.
The library provides:
@LiveQuery— a SwiftUI property wrapper for live, dependency-driven SwiftData queries.@CRUD— a macro that synthesizes fetch, upsert, and delete methods on@Modeltypes.@RelationshipQueries— a macro that synthesizes typed query methods for@Relationship(inverse:)collection properties.
Sections
Requirements
- iOS 17, macOS 14, watchOS 10, tvOS 17, visionOS 2
- Swift tools version 6.2
- SwiftData (Apple framework, available on the platforms above)
Dependencies
- pointfreeco/swift-dependencies
- SwiftData (Apple framework)
Installation
Swift Package Manager
let package = Package(
dependencies: [
.package(
url: "https://github.com/vadimkrutovlv/swift-data-helpers.git",
from: "1.0.0"
)
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "SwiftDataHelpers", package: "SwiftDataHelpers")
]
)
]
)Update the version as new releases are published.
Using Xcode
- Open your Xcode project.
- Navigate to File > Add Packages...
- Enter the following URL in the search field:
https://github.com/vadimkrutovlv/swift-data-helpers.git - Choose the latest available version (starting at
1.0.0). - Click Add Package to finish.
Getting Started
Using LiveQuery
LiveQuery is a SwiftUI property wrapper that keeps your view state in sync with SwiftData. It refreshes when the underlying model container saves, so your lists and sections stay current without manual fetch logic.
It can be thought of as an alternative to SwiftData's @Query macro, with explicit dependency-based context configuration.
You can also use @LiveQuery in @Observable models (for example, a feature model that owns shared data) and any other main-actor-isolated type where you want live SwiftData-backed state.
Configure the ModelContext (primary)
@LiveQuery relies on the Dependencies value liveQueryContext.modelContext. Configure it once at your app entry point so every view has a consistent source of truth. This is the recommended setup for most apps.
import Dependencies
import SwiftData
import SwiftDataHelpers
import SwiftUI
@main
struct MyApp: App {
let container: ModelContainer = try! ModelContainer(
for: MyModel.self
)
init() {
let container = self.container
prepareDependencies {
$0.liveQueryContext.modelContext = { container.mainContext }
}
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
}
}
}Use @LiveQuery
import SwiftDataHelpers
import SwiftUI
struct PeopleList: View {
@LiveQuery(
predicate: #Predicate<Person> { $0.isActive },
sort: [SortDescriptor(\Person.name)]
)
private var people: [Person]
var body: some View {
List(people) { person in
Text(person.name)
}
}
}Consume Values as AsyncStream
Use the projected value ($people) to iterate over live snapshots:
@MainActor
@Observable
final class PeopleFeatureModel {
@ObservationIgnored
@LiveQuery(sort: [SortDescriptor(\Person.name)])
var people: [Person]
@ObservationIgnored
private var observationTask: Task<Void, Never>?
func startObservingPeople() {
observationTask?.cancel()
observationTask = Task {
for await snapshot in $people.valuesStream {
print("Current people count: \(snapshot.count)")
}
}
}
deinit {
observationTask?.cancel()
}
}Multiple Containers (optional)
If your app uses more than one ModelContainer (for example, main and private stores), use LiveQueryBindable to scope queries to the desired container.
LiveQueryBindable(modelContainer: .privatePersons) {
PrivatePeopleView()
}Previews and Tests (optional)
#Preview {
prepareDependencies {
$0.liveQueryContext.modelContext = { ModelContainer.main.mainContext }
}
return PeopleList()
.modelContainer(.main)
}Macros
The SwiftDataHelpersMacros product provides @CRUD and @RelationshipQueries macros. Add the macros product to your target alongside the core library:
.target(
name: "YourApp",
dependencies: [
.product(name: "SwiftDataHelpers", package: "SwiftDataHelpers"),
.product(name: "SwiftDataHelpersMacros", package: "SwiftDataHelpers"),
]
)@CRUD
Attach @CRUD to a @Model type to generate fetch, fetchOne, upsert, upsertCollection, deleteCollection, and delete methods.
import SwiftDataHelpersMacros
@Model
@CRUD
final class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// Generated usage:
let people = try Person.fetch(
predicate: #Predicate { $0.age >= 18 },
sort: [SortDescriptor(\.name)]
)
let person = try Person.upsert(Person(name: "Alice", age: 30))
try person.delete()Every generated method accepts an optional modelContext: parameter. When omitted, the context is resolved through @Dependency(\.liveQueryContext).
@RelationshipQueries
Attach @RelationshipQueries to a @Model type that has @Relationship(inverse:) collection properties. A typed query method is generated for each qualifying relationship.
@Model
@CRUD
@RelationshipQueries
final class Person {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Pet.owner)
var pets: [Pet]
init(name: String) {
self.name = name
self.pets = []
}
}
// Generated usage — fetch pets for a person with optional filter and sort:
let sortedPets = person.pets(sort: [SortDescriptor(\.name)])FAQ
I see a fatal error about liveQueryContext.modelContext not set. What does it mean? @LiveQuery requires a ModelContext from DependencyValues.liveQueryContext. Configure it once at your app entry point with prepareDependencies, or scope a subtree with LiveQueryBindable.
Can I use @LiveQuery outside of views? Yes. It works in @Observable models or other main-actor-isolated types. When used inside an observable model, mark the property wrapper with @ObservationIgnored to avoid conflicts with Observation.
How do I query multiple containers? Use LiveQueryBindable to scope queries to a specific container, or override the dependency for a particular subtree.
Documentation
- Stable (latest release): Docs
- Main (latest
mainbranch): Docs - Versioned release docs:
https://vadimkrutovlv.github.io/swift-data-helpers/<version>/documentation/swiftdatahelpers/
The version switcher includes stable, main, and the latest 5 release versions. Older release docs remain available by direct version URL even when they are not shown in the dropdown.
Contributing
Contribution workflow, review rules, and release flow are documented in CONTRIBUTING.md.
Example App
A working example app is included in SwiftDataHelpersExample/ and shows:
@CRUDand@RelationshipQueriesmacros on model types- Multiple containers
- Dynamic predicates and sorting
@LiveQueryinside an@Observablemodel@LiveQueryinside a UIKitUIViewControllervia$valuesStream- Background writes with
@ModelActor - Test and preview setup
Package Metadata
Repository: vadimkrutovlv/swift-data-helpers
Default branch: main
README: README.md