Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions Examples/CaseStudies/Regression Coverage/ChildIdentityReset.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import SQLiteData
import SwiftUI

struct ChildIdentityResetCaseStudy: SwiftUICaseStudy {
let readMe = """
This demonstrates that the `@Fetch*` tools behave like `@State` when a view's identity \
changes: their state survives re-renders of the parent view, but is discarded and rebuilt \
when the parent changes the child view's identity with the `id` view modifier.

Toggle "Favorites only" in the child view to load a filtered query, then tap "Reset child \
identity". The child view is rebuilt from scratch: the toggle returns to its default and \
the list returns to the unfiltered query, keeping the UI and data consistent.
"""
let caseStudyTitle = "Resetting child identity"

@State private var resetCount = 0

var body: some View {
List {
Section {
Button("Reset child identity: \(resetCount)") {
resetCount += 1
}
}
FactsListView()
.id(resetCount)
}
}
}

private struct FactsListView: View {
@State private var isFavoritesOnly = false
@FetchAll(Fact.all) private var facts

var body: some View {
Section {
Toggle("Favorites only", isOn: $isFavoritesOnly)
ForEach(facts) { fact in
HStack {
Text(fact.body)
Spacer()
if fact.isFavorite {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
}
}
}
}
.task(id: isFavoritesOnly) {
await withErrorReporting {
if isFavoritesOnly {
try await $facts.load(Fact.where(\.isFavorite)).task
} else {
try await $facts.load(Fact.all).task
}
}
}
}
}

@Table
nonisolated private struct Fact: Identifiable {
let id: Int
var body: String
var isFavorite = false
}

extension DatabaseWriter where Self == DatabaseQueue {
static var childIdentityResetDatabase: Self {
let databaseQueue = try! DatabaseQueue()
var migrator = DatabaseMigrator()
migrator.registerMigration("Create 'facts' table") { db in
try #sql(
"""
CREATE TABLE "facts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"body" TEXT NOT NULL,
"isFavorite" INTEGER NOT NULL DEFAULT 0
) STRICT
"""
)
.execute(db)
try Fact.insert {
Fact.Draft(body: "SQLite was first released in the year 2000.", isFavorite: true)
Fact.Draft(body: "SQLite is the most widely deployed database in the world.")
Fact.Draft(body: "SQLite is a C library, not a client-server database.", isFavorite: true)
Fact.Draft(body: "SQLite databases are a single file on disk.")
}
.execute(db)
}
try! migrator.migrate(databaseQueue)
return databaseQueue
}
}

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = .childIdentityResetDatabase
}
NavigationStack {
CaseStudyView {
ChildIdentityResetCaseStudy()
}
}
}
103 changes: 103 additions & 0 deletions Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import SQLiteData
import SwiftUI

struct ParentDrivenQueryCaseStudy: SwiftUICaseStudy {
let readMe = """
This demonstrates how to drive a child view's query from parent state by constructing the \
`@FetchAll` in the child's initializer, analogous to constructing a SwiftData `@Query` with a \
dynamic predicate in a view's initializer.

Toggling "Favorites only" re-initializes the child view with a different query, and the child \
should immediately display the results of the new query. Tapping "Re-render parent" \
re-initializes the child with the same query, which should have no effect.
"""
let caseStudyTitle = "Parent-driven queries"

@State private var isFavoritesOnly = false
@State private var rerenderCount = 0

var body: some View {
List {
Section {
Toggle("Favorites only", isOn: $isFavoritesOnly)
Button("Re-render parent: \(rerenderCount)") {
rerenderCount += 1
}
}
FactsListView(isFavoritesOnly: isFavoritesOnly)
}
}
}

private struct FactsListView: View {
@FetchAll private var facts: [Fact]

init(isFavoritesOnly: Bool) {
if isFavoritesOnly {
_facts = FetchAll(Fact.where(\.isFavorite))
} else {
_facts = FetchAll(Fact.all)
}
}

var body: some View {
Section {
ForEach(facts) { fact in
HStack {
Text(fact.body)
Spacer()
if fact.isFavorite {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
}
}
}
}
}
}

@Table
nonisolated private struct Fact: Identifiable {
let id: Int
var body: String
var isFavorite = false
}

extension DatabaseWriter where Self == DatabaseQueue {
static var parentDrivenQueryDatabase: Self {
let databaseQueue = try! DatabaseQueue()
var migrator = DatabaseMigrator()
migrator.registerMigration("Create 'facts' table") { db in
try #sql(
"""
CREATE TABLE "facts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"body" TEXT NOT NULL,
"isFavorite" INTEGER NOT NULL DEFAULT 0
) STRICT
"""
)
.execute(db)
try Fact.insert {
Fact.Draft(body: "SQLite was first released in the year 2000.", isFavorite: true)
Fact.Draft(body: "SQLite is the most widely deployed database in the world.")
Fact.Draft(body: "SQLite is a C library, not a client-server database.", isFavorite: true)
Fact.Draft(body: "SQLite databases are a single file on disk.")
}
.execute(db)
}
try! migrator.migrate(databaseQueue)
return databaseQueue
}
}

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = .parentDrivenQueryDatabase
}
NavigationStack {
CaseStudyView {
ParentDrivenQueryCaseStudy()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import SQLiteData
import SwiftUI

struct ParentRerenderAnimationsCaseStudy: SwiftUICaseStudy {
let readMe = """
This demonstrates that animations provided to the `@Fetch*` tools continue to work after a \
parent view re-renders.

The list below is loaded in the child view's `task` with an `animation` parameter, and so \
tapping "Add fact" animates the new fact into the list. Tapping "Re-render parent" changes \
`@State` in the parent view, which causes the child view (and its `@FetchAll`) to be \
re-initialized. Adding a fact should continue to animate afterwards.
"""
let caseStudyTitle = "Animations with re-rendered parent"

@State private var rerenderCount = 0
@Dependency(\.defaultDatabase) var database

var body: some View {
List {
Section {
Button("Re-render parent: \(rerenderCount)") {
rerenderCount += 1
}
Button("Add fact") {
withErrorReporting {
try database.write { db in
try Fact.insert {
Fact.Draft(body: Date.now.formatted(date: .omitted, time: .standard))
}
.execute(db)
}
}
}
}
FactsListView()
}
}
}

private struct FactsListView: View {
@FetchAll(Fact.order { $0.id.desc() })
private var facts

var body: some View {
Section {
ForEach(facts) { fact in
Text(fact.body)
}
}
.task {
await withErrorReporting {
try await $facts.load(Fact.order { $0.id.desc() }, animation: .default).task
}
}
}
}

@Table
nonisolated private struct Fact: Identifiable {
let id: Int
var body: String
}

extension DatabaseWriter where Self == DatabaseQueue {
static var parentRerenderAnimationsDatabase: Self {
let databaseQueue = try! DatabaseQueue()
var migrator = DatabaseMigrator()
migrator.registerMigration("Create 'facts' table") { db in
try #sql(
"""
CREATE TABLE "facts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"body" TEXT NOT NULL
) STRICT
"""
)
.execute(db)
}
try! migrator.migrate(databaseQueue)
return databaseQueue
}
}

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = .parentRerenderAnimationsDatabase
}
NavigationStack {
CaseStudyView {
ParentRerenderAnimationsCaseStudy()
}
}
}
Loading