-
-
Notifications
You must be signed in to change notification settings - Fork 726
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
56 changed files
with
1,195 additions
and
984 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 14 additions & 3 deletions
17
Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/GRDBCombineDemoApp.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,23 @@ | ||
import GRDB | ||
import SwiftUI | ||
|
||
@main | ||
struct GRDBCombineDemoApp: App { | ||
let appDatabase = AppDatabase.shared | ||
|
||
var body: some Scene { | ||
WindowGroup { | ||
PlayerList(viewModel: PlayerListViewModel(database: appDatabase)) | ||
AppView().environment(\.appDatabase, AppDatabase.shared) | ||
} | ||
} | ||
} | ||
|
||
// Let SwiftUI views access the database through the SwiftUI environment | ||
private struct AppDatabaseKey: EnvironmentKey { | ||
static let defaultValue: AppDatabase? = nil | ||
} | ||
|
||
extension EnvironmentValues { | ||
var appDatabase: AppDatabase? { | ||
get { self[AppDatabaseKey.self] } | ||
set { self[AppDatabaseKey.self] = newValue } | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/PlayerRequest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import GRDB | ||
|
||
/// A player request defines how to feed the player list | ||
struct PlayerRequest { | ||
enum Ordering { | ||
case byScore | ||
case byName | ||
} | ||
|
||
var ordering: Ordering | ||
} | ||
|
||
/// Make `PlayerRequest` able to be used with the `@Query` property wrapper. | ||
extension PlayerRequest: Queryable { | ||
static var defaultValue: [Player] { [] } | ||
|
||
func fetchValue(_ db: Database) throws -> [Player] { | ||
switch ordering { | ||
case .byScore: return try Player.all().orderedByScore().fetchAll(db) | ||
case .byName: return try Player.all().orderedByName().fetchAll(db) | ||
} | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Query.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// | ||
// Query.swift | ||
// | ||
// A property wrapper inspired from | ||
// https://davedelong.com/blog/2021/04/03/core-data-and-swiftui/ | ||
// | ||
|
||
import Combine | ||
import GRDB | ||
import SwiftUI | ||
|
||
/// The protocol that feeds the `@Query` property wrapper. | ||
protocol Queryable: Equatable { | ||
/// The type of the fetched value | ||
associatedtype Value | ||
|
||
/// The default value, used whenever the database is not available | ||
static var defaultValue: Value { get } | ||
|
||
/// Fetches the database value | ||
func fetchValue(_ db: Database) throws -> Value | ||
} | ||
|
||
/// The property wrapper that observes a database query | ||
@propertyWrapper | ||
struct Query<Query: Queryable>: DynamicProperty { | ||
/// The database reader that makes it possible to observe the database | ||
@Environment(\.appDatabase?.databaseReader) private var databaseReader: DatabaseReader? | ||
@StateObject private var core = Core() | ||
private var baseQuery: Query | ||
|
||
/// The fetched value | ||
var wrappedValue: Query.Value { | ||
core.value ?? Query.defaultValue | ||
} | ||
|
||
/// A binding to the query, that lets your views modify it. | ||
/// | ||
/// This is how the demo app changes the player ordering. | ||
var projectedValue: Binding<Query> { | ||
Binding( | ||
get: { core.query ?? baseQuery }, | ||
set: { core.query = $0 }) | ||
} | ||
|
||
init(_ query: Query) { | ||
baseQuery = query | ||
} | ||
|
||
func update() { | ||
guard let databaseReader = databaseReader else { | ||
fatalError("Attempting to use @Query without any database in the environment") | ||
} | ||
// Feed core with necessary information, and make sure tracking has started | ||
if core.query == nil { core.query = baseQuery } | ||
core.startTrackingIfNecessary(in: databaseReader) | ||
} | ||
|
||
private class Core: ObservableObject { | ||
private(set) var value: Query.Value? | ||
var databaseReader: DatabaseReader? | ||
var query: Query? { | ||
willSet { | ||
if query != newValue { | ||
// Stop tracking, and tell SwiftUI about the update | ||
objectWillChange.send() | ||
cancellable = nil | ||
} | ||
} | ||
} | ||
private var cancellable: AnyCancellable? | ||
|
||
init() { } | ||
|
||
func startTrackingIfNecessary(in databaseReader: DatabaseReader) { | ||
if databaseReader !== self.databaseReader { | ||
// Database has changed. Stop tracking. | ||
self.databaseReader = databaseReader | ||
cancellable = nil | ||
} | ||
|
||
guard let query = query else { | ||
// No query set | ||
return | ||
} | ||
|
||
guard cancellable == nil else { | ||
// Already tracking | ||
return | ||
} | ||
|
||
cancellable = ValueObservation | ||
.tracking(query.fetchValue) | ||
.publisher( | ||
in: databaseReader, | ||
scheduling: .immediate) | ||
.sink( | ||
receiveCompletion: { _ in | ||
// Ignore errors | ||
}, | ||
receiveValue: { [weak self] value in | ||
guard let self = self else { return } | ||
// Tell SwiftUI about the new value | ||
self.objectWillChange.send() | ||
self.value = value | ||
}) | ||
} | ||
} | ||
} |
Oops, something went wrong.