Skip to content

Commit d4542be

Browse files
authored
Merge pull request #17 from groue/dev/query-initializers
Fine-Grained Query initializers
2 parents b3c91ac + 651f47e commit d4542be

5 files changed

Lines changed: 353 additions & 88 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SwiftUI
1414

1515
/// A view that displays an always up-to-date list of players in the database.
1616
struct PlayerList: View {
17-
@Query(AllPlayers())
17+
@Query(PlayerRequest())
1818
var players: [Player]
1919

2020
var body: some View {

Sources/GRDBQuery/Documentation.docc/Documentation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import SwiftUI
1212

1313
/// A view that displays an always up-to-date list of players in the database.
1414
struct PlayerList: View {
15-
@Query(AllPlayers())
15+
@Query(PlayerRequest())
1616
var players: [Player]
1717

1818
var body: some View {

Sources/GRDBQuery/Documentation.docc/GettingStarted.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import GRDB
7272
import GRDBQuery
7373

7474
/// Tracks the full list of players
75-
struct AllPlayers: Queryable {
75+
struct PlayerRequest: Queryable {
7676
static var defaultValue: [Player] { [] }
7777

7878
func publisher(in dbQueue: DatabaseQueue) -> AnyPublisher<[Player], Error> {
@@ -103,7 +103,7 @@ import GRDBQuery
103103
import SwiftUI
104104

105105
struct PlayerList: View {
106-
@Query(AllPlayers(), in: \.dbQueue)
106+
@Query(PlayerRequest(), in: \.dbQueue)
107107
var players: [Player]
108108

109109
var body: some View {
@@ -120,12 +120,31 @@ struct PlayerList: View {
120120

121121
> Tip: Some applications want to use `@Query` without specifying the key path to the database in each and every view.
122122
>
123-
> See how to make the environment key implicit in ``Query/init(_:in:)``.
123+
> To do so, add somewhere in your application those convenience `Query` initializers:
124+
>
125+
> ```swift
126+
> // Convenience Query initializers for requests
127+
> // that feed from `DatabaseQueue`.
128+
> extension Query where Request.DatabaseContext == DatabaseQueue {
129+
> init(_ request: Request) {
130+
> self.init(request, in: \.dbQueue)
131+
> }
132+
> init(_ request: Binding<Request>) {
133+
> self.init(request, in: \.dbQueue)
134+
> }
135+
> init(constant request: Request) {
136+
> self.init(constant:request, in: \.dbQueue)
137+
> }
138+
> }
139+
> ```
140+
>
141+
> These initializers will streamline your SwiftUI views:
124142
>
125143
> ```swift
126144
> struct PlayerList: View {
127-
> @Query(AllPlayers()) // Implicit key path to the database
145+
> @Query(PlayerRequest()) // Implicit key path to the database
128146
> var players: [Player]
147+
>
129148
> ...
130149
> }
131150
> ```
@@ -141,7 +160,7 @@ import Combine
141160
import GRDB
142161
import GRDBQuery
143162
144-
struct AllPlayers: Queryable {
163+
struct PlayerRequest: Queryable {
145164
typealias Value = Result<[Player], Error>
146165
static var defaultValue: Value { .success([]) }
147166

Sources/GRDBQuery/Documentation.docc/QueryableParameters.md

Lines changed: 155 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
# Adding Parameters to Queryable Types
22

3-
Learn how a SwiftUI view can configure the database content it displays.
3+
Learn how SwiftUI views can configure the database content displayed on screen.
44

55
## Overview
66

7-
When a SwiftUI view needs to configure the database values it displays, it will modify the ``Queryable`` request that feeds the `@Query` property wrapper.
7+
When a SwiftUI view needs to configure the database values displayed on screen, it will modify the ``Queryable`` request that feeds the `@Query` property wrapper.
8+
9+
Such configuration can be performed by the view that declares a `@Query` property. It can also be performed by the enclosing view. This article explores all your available options.
810

911
## A Configurable Queryable Type
1012

11-
As an example, let's extend the `AllPlayers` request type we have seen in <doc:GettingStarted>. It can now sort players by score, or by name, depending on its `ordering` property.
13+
As an example, let's extend the `PlayerRequest` request type we have seen in <doc:GettingStarted>. It can now sort players by score, or by name, depending on its `ordering` property.
1214

1315
```swift
14-
struct AllPlayers: Queryable {
16+
struct PlayerRequest: Queryable {
1517
enum Ordering {
1618
case byScore
1719
case byName
@@ -46,19 +48,19 @@ struct AllPlayers: Queryable {
4648

4749
The `@Query` property wrapper will detect changes in the `ordering` property, and update SwiftUI views accordingly.
4850

49-
> Experiment: You can adapt this example for your own needs. As you can see, you can modify the order to database values, but you can also change how they are filtered. All [ValueObservation] features are available.
51+
> Experiment: You can adapt this example for your own needs. As you can see, you can modify the order to database values, but you can also change how they are filtered. All [GRDB] features are available.
5052
5153
## Modifying the Request from the SwiftUI View
5254

53-
SwiftUI views can change the properties of the Queryable request with the SwiftUI Binding provided by the `@Query` property wrapper:
55+
SwiftUI views can change the properties of the Queryable request with the SwiftUI bindings provided by the `@Query` property wrapper:
5456

5557
```swift
5658
import GRDBQuery
5759
import SwiftUI
5860

5961
struct PlayerList: View {
6062
// Ordering can change through the $players.ordering binding.
61-
@Query(AllPlayers(ordering: .byScore))
63+
@Query(PlayerRequest(ordering: .byScore))
6264
var players: [Player]
6365

6466
var body: some View {
@@ -78,7 +80,7 @@ struct PlayerList: View {
7880
}
7981

8082
struct ToggleOrderingButton: View {
81-
@Binding var ordering: AllPlayers.Ordering
83+
@Binding var ordering: PlayerRequest.Ordering
8284

8385
var body: some View {
8486
switch ordering {
@@ -91,21 +93,160 @@ struct ToggleOrderingButton: View {
9193
}
9294
```
9395

96+
In the above example, `$players.ordering` is a SwiftUI binding to the `ordering` property of the `PlayerRequest` request.
97+
98+
This binding feeds `ToggleOrderingButton`, which lets the user change the ordering of the request. `@Query` then redraws the view with an updated the database content.
99+
100+
When appropriate, you can also use `$players.request`, a SwiftUI binding to the `PlayerRequest` request itself.
101+
102+
94103
## Configuring the Initial Request
95104

96-
The above example has the `PlayerList` view always start with the `.byScore` ordering. When you want to provide the initial ordering as a parameter to your view, modify the sample code as below:
105+
The above example has the `PlayerList` view always start with the `.byScore` ordering.
106+
107+
When you want to provide the initial request as a parameter to your view, provide a dedicated initializer:
108+
109+
```swift
110+
struct PlayerList: View {
111+
/// No default request
112+
@Query<PlayerRequest>
113+
var players: [Player]
114+
115+
/// Explicit initial request
116+
init(initialOrdering: PlayerRequest.Ordering) {
117+
_players = Query(PlayerRequest(ordering: initialOrdering))
118+
}
119+
120+
var body: some View { ... }
121+
}
122+
```
123+
124+
Defining a default ordering is still possible:
125+
126+
```swift
127+
struct PlayerList: View {
128+
/// Defines the default initial request (ordered by score)
129+
@Query(PlayerRequest(ordering: .byScore))
130+
var players: [Player]
131+
132+
/// Default initial request (by score)
133+
init() { }
134+
135+
/// Explicit initial request
136+
init(initialOrdering ordering: PlayerRequest.Ordering) {
137+
_players = Query(PlayerRequest(ordering: ordering))
138+
}
139+
140+
var body: some View { ... }
141+
}
142+
```
143+
144+
> IMPORTANT: The initial request is only used when `PlayerList` appears on screen. After that, and until `PlayerList` disappears, the request is only controlled by the `$players` bindings described above.
145+
>
146+
> This means that calling the `PlayerList(initialOrdering:)` with a different ordering will have no effect:
147+
>
148+
> ```swift
149+
> struct Container {
150+
> @State var ordering = PlayerRequest.Ordering.byScore
151+
>
152+
> var body: some View {
153+
> // No effect when the ordering State changes after the `PlayerList`
154+
> // has appeared on screen:
155+
> PlayerList(initialOrdering: ordering)
156+
> }
157+
> }
158+
> ```
159+
>
160+
> To let the enclosing view control the request after `PlayerList` has appeared on screen, you'll need one of the techniques described below.
161+
162+
## Initializing @Query from a Binding
163+
164+
The `@Query` property wrapper can be controlled with a SwiftUI binding, as in the example below:
165+
166+
```swift
167+
struct Container {
168+
@State var ordering = PlayerRequest.Ordering.byScore
169+
170+
var body: some View {
171+
PlayerList(ordering: $ordering) // Note the `$ordering` binding here
172+
}
173+
}
174+
175+
struct PlayerList: View {
176+
@Query<PlayerRequest>
177+
var players: [Player]
178+
179+
init(ordering: Binding<PlayerRequest.Ordering>) {
180+
_players = Query(Binding(
181+
get: { PlayerRequest(ordering: ordering.wrappedValue) },
182+
set: { request in ordering.wrappedValue = request.ordering }))
183+
}
184+
185+
var body: some View { ... }
186+
}
187+
```
188+
189+
With such a setup, `@Query` updates the database content whenever a change is performed by the `$ordering` Container binding, or the `$players` PlayerList bindings.
190+
191+
This is the classic two-way connection enabled by SwiftUI `Binding`.
192+
193+
## Initializing @Query from a Constant Request
194+
195+
Finally, the ``Query/init(constant:in:)`` initializer allows the enclosing Container view to control the request without restriction, and without any SwiftUI Binding. However, `$players` binding have no effect:
196+
197+
```swift
198+
struct Container {
199+
var ordering: PlayerRequest.Ordering
200+
201+
var body: some View {
202+
PlayerList(constantOrdering: ordering)
203+
}
204+
}
205+
206+
struct PlayerList: View {
207+
@Query<PlayerRequest>
208+
var players: [Player]
209+
210+
init(constantOrdering ordering: PlayerRequest.Ordering) {
211+
_players = Query(constant: PlayerRequest(ordering: ordering))
212+
}
213+
214+
var body: some View { ... }
215+
}
216+
```
217+
218+
## Summary
219+
220+
**All the ``Query`` initializers we have seen above can be used in any given SwiftUI view.**
97221

98222
```swift
99223
struct PlayerList: View {
100-
@Query<AllPlayers>
224+
/// Defines the default initial request (ordered by score)
225+
@Query(PlayerRequest(ordering: .byScore))
101226
var players: [Player]
102227

103-
init(initialOrdering: AllPlayers.Ordering) {
104-
_players = Query(AllPlayers(ordering: initialOrdering))
228+
/// Default initial request (by score)
229+
init() { }
230+
231+
/// Initial request
232+
init(initialOrdering ordering: PlayerRequest.Ordering) {
233+
_players = Query(PlayerRequest(ordering: ordering))
234+
}
235+
236+
/// Request binding
237+
init(ordering: Binding<PlayerRequest.Ordering>) {
238+
_players = Query(Binding(
239+
get: { PlayerRequest(ordering: ordering.wrappedValue) },
240+
set: { request in ordering.wrappedValue = request.ordering }))
241+
}
242+
243+
/// Constant request
244+
init(constantOrdering ordering: PlayerRequest.Ordering) {
245+
_players = Query(constant: PlayerRequest(ordering: ordering))
105246
}
106247

107-
...
248+
var body: some View { ... }
108249
}
109250
```
110251

111-
[ValueObservation]: https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation
252+
[GRDB]: https://github.com/groue/GRDB.swift

0 commit comments

Comments
 (0)