Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Examples/CaseStudies/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ SQLiteData, including:
query, such as when searching for rows in a table that contains a fragment of text.
* [@Observable Models](ObservableModelDemo.swift): Shows how to use the tools of this library
in an `@Observable` model.
* [Sectioned Queries](SectionedQuery.swift): Shows how to group the results of a query into
sections using the `sectionBy:` argument of `@FetchAll`.
* [SwiftUI](SwiftUIDemo.swift): Shows how to use the tools of this library directly in a SwiftUI
view.
* [Database Transactions](TransactionDemo.swift): Shows how to execute multiple queries within
Expand Down
131 changes: 131 additions & 0 deletions Examples/CaseStudies/SectionedQuery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import SQLiteData
import SwiftUI

struct SectionedQueryDemo: SwiftUICaseStudy {
let readMe = """
This demonstrates how to group the results of a `@FetchAll` query into sections by providing \
a `sectionBy:` argument.

Use the picker to change how the reminders are sectioned: by category, by priority, or with \
no sectioning at all. When grouping by priority, the section of reminders with no priority \
are ordered to the bottom of the list.
"""
let caseStudyTitle = "Sectioned Queries"

@FetchAll(Reminder.none) private var reminders
@State private var sectioning = Sectioning.category

@Dependency(\.defaultDatabase) var database

private enum Sectioning: String, CaseIterable {
case none = "None"
case category = "Category"
case priority = "Priority"
}

var body: some View {
List {
Section {
Picker("Section by", selection: $sectioning) {
ForEach(Sectioning.allCases, id: \.self) { sectioning in
Text(sectioning.rawValue)
}
}
.pickerStyle(.segmented)
}
ForEach($reminders.sections) { section in
Section {
ForEach(section) { reminder in
Text(reminder.title)
}
.onDelete { indices in
withErrorReporting {
try database.write { db in
let ids = indices.map { section[$0].id }
try Reminder
.where { $0.id.in(ids) }
.delete()
.execute(db)
}
}
}
} header: {
if sectioning != .none {
Text(section.name ?? "None")
}
}
}
}
.task(id: sectioning) {
await withErrorReporting {
_ = try await $reminders.load(
Reminder.order(by: \.title),
sectionBy: {
switch sectioning {
case .none:
nil
case .category:
$0.category
case .priority:
$0.priority.asc(nulls: .last)
}
},
animation: .default
)
}
}
}
}

@Table
nonisolated private struct Reminder: Identifiable {
let id: Int
var title: String
var category: String
var priority: String?
}

extension DatabaseWriter where Self == DatabaseQueue {
static var sectionedQueryDatabase: Self {
let databaseQueue = try! DatabaseQueue()
var migrator = DatabaseMigrator()
migrator.registerMigration("Create 'reminders' table") { db in
try #sql(
"""
CREATE TABLE "reminders" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"category" TEXT NOT NULL,
"priority" TEXT
) STRICT
"""
)
.execute(db)
}
migrator.registerMigration("Seed 'reminders' table") { db in
try Reminder.insert {
Reminder.Draft(title: "Call mom", category: "Family", priority: "High")
Reminder.Draft(title: "Plan vacation", category: "Family")
Reminder.Draft(title: "Buy groceries", category: "Personal", priority: "High")
Reminder.Draft(title: "Go to the gym", category: "Personal", priority: "Low")
Reminder.Draft(title: "Pick up dry cleaning", category: "Personal")
Reminder.Draft(title: "Prepare talk", category: "Work", priority: "High")
Reminder.Draft(title: "Send status report", category: "Work", priority: "Low")
}
.execute(db)
}
try! migrator.migrate(databaseQueue)
return databaseQueue
}
}

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = .sectionedQueryDatabase
}
NavigationStack {
CaseStudyView {
SectionedQueryDemo()
}
}
}
50 changes: 31 additions & 19 deletions Examples/CloudKitDemo/CountersListFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@ struct CountersListView: View {
.leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
.select {
Row.Columns(counter: $0, isShared: $1.isShared.ifnull(false))
}
) var rows
},
sectionBy: { _, metadata in
Case()
.when(metadata.isShared.eq(true), then: "Shared")
.else("Private")
.desc()
}
)
var rows

@State var sharedRecord: SharedRecord?

@Dependency(\.defaultDatabase) var database
@Dependency(\.defaultSyncEngine) var syncEngine

Expand All @@ -20,10 +30,12 @@ struct CountersListView: View {

var body: some View {
List {
if !rows.isEmpty {
Section {
ForEach(rows, id: \.counter.id) { row in
CounterRow(row: row)
ForEach($rows.sections) { section in
Section(section.name ?? "Private") {
ForEach(section, id: \.counter.id) { row in
CounterRow(row: row) {
shareButtonTapped(row: row)
}
.buttonStyle(.borderless)
}
.onDelete { indexSet in
Expand All @@ -49,6 +61,9 @@ struct CountersListView: View {
}
}
}
.sheet(item: $sharedRecord) { sharedRecord in
CloudSharingView(sharedRecord: sharedRecord)
}
}

func deleteRows(at indexSet: IndexSet) {
Expand All @@ -61,11 +76,19 @@ struct CountersListView: View {
}
}
}

func shareButtonTapped(row: Row) {
_ = Task {
sharedRecord = try await syncEngine.share(record: row.counter) { share in
share[CKShare.SystemFieldKey.title] = "Join my counter!"
}
}
}
}

struct CounterRow: View {
let row: CountersListView.Row
@State var sharedRecord: SharedRecord?
let onShare: () -> Void
@Dependency(\.defaultDatabase) var database
@Dependency(\.defaultSyncEngine) var syncEngine

Expand All @@ -84,23 +107,12 @@ struct CounterRow: View {
}
Spacer()
Button {
shareButtonTapped()
onShare()
} label: {
Image(systemName: "square.and.arrow.up")
}
}
}
.sheet(item: $sharedRecord) { sharedRecord in
CloudSharingView(sharedRecord: sharedRecord)
}
}

func shareButtonTapped() {
_ = Task {
sharedRecord = try await syncEngine.share(record: row.counter) { share in
share[CKShare.SystemFieldKey.title] = "Join my counter!"
}
}
}

func decrementButtonTapped() {
Expand Down
2 changes: 1 addition & 1 deletion Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 100;
objects = {

/* Begin PBXBuildFile section */
Expand Down
Loading