Skip to content

Commit cb1a8b9

Browse files
authored
Add query and cloud code view models for SwiftUI (#183)
* Add QueryViewModel. Switch ParseSubscription to QuerySubscribable * Update playgrounds * Playground nits * Add test cases * Fix link in change log * Fix min version in README * nits * Improve Playgrounds by moving SwiftUI to new pages * Add required initializer * Update Changelog * Playground nits * Update build script name to SwiftLint * Update change log * Remove unused constant * document nits * Fix printing error in playgrounds * Update changelog * Fix badge in readme * Add changelog * Update changelog * nits * Update change log * Add view model for Cloud Code * Switch CloudViewModel return type to optional * test removal of internal(set) on viewModels * allow properties of ViewModels to be overriden when subclassed * Update changelog * Update uniquePointer in encoder * Fix cycle detection * nits * nits * Update changelog * Fix viewModel publishers * Update changelog * Make subscribe public for LiveQuery * Update caching directories * Fix cache directories * Use original cache method * Use cache directly from URLSession only. Other way causes random errors * Attempt to use new cache method again
1 parent 1f0f614 commit cb1a8b9

File tree

42 files changed

+1420
-231
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1420
-231
lines changed

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ coverage:
44
status:
55
patch:
66
default:
7-
target: 71
7+
target: auto
88
changes: false
99
project:
1010
default:

CHANGELOG.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
# Parse-Swift Changelog
22

33
### main
4-
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.6...main)
4+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.0...main)
55
* _Contributing to this repo? Add info about your change here to be included in the next release_
66

7+
### 1.9.0
8+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.6...1.9.0)
9+
10+
__New features__
11+
- (Breaking Change) Added a new type, QueryViewModel which conforms to ObservableObject. The new type serves as a view model property for any Parse Query. Simply call query.viewModel to use the view model with any SwiftUI view. QueryViewModel can be subclassed for customization. In addition, developers can create their own view models for queries by conforming to QueryObservable. LiveQuery Subscription's inherrit from QueryViewModel meaning instances of Subscription provides a single view model that publishes updates from LiveQuery events and traditional find, first, count, and aggregate queries. A breaking change is introduced for those use custom subscriptions as ParseSubscription has been renamed to QuerySubscribable ([#183](https://github.com/parse-community/Parse-Swift/pull/183)), thanks to [Corey Baker](https://github.com/cbaker6).
12+
- Added a new type, CloudViewModel which conforms to ObservableObject. The new type serves as a view model property for any Cloud Code. Simply call cloud.viewModel to use the view model with any SwiftUI view. CloudViewModel can be subclassed for customization. In addition, developers can create their own view models for queries by conforming to CloudObservable ([#183](https://github.com/parse-community/Parse-Swift/pull/183)), thanks to [Corey Baker](https://github.com/cbaker6).
13+
- Added two missing Parse types, ParseBytes and ParsePolygon ([#190](https://github.com/parse-community/Parse-Swift/pull/190)), thanks to [Corey Baker](https://github.com/cbaker6).
14+
- Added caching of http requests along with adding additional headers. Caching and additional headers can be set when initializing the SDK. Caching can also be set per request using API.Options. ([#196](https://github.com/parse-community/Parse-Swift/pull/196)), thanks to [Corey Baker](https://github.com/cbaker6).
15+
16+
__Improvements__
17+
- Removed CommonCrypto and now uses encoded string as a hash for child ParseObjects across all OS's ([#184](https://github.com/parse-community/Parse-Swift/pull/184)), thanks to [Corey Baker](https://github.com/cbaker6).
18+
- All types now conform to CustomStringConvertible ([#185](https://github.com/parse-community/Parse-Swift/pull/185)), thanks to [Corey Baker](https://github.com/cbaker6).
19+
- Setting limit = 0 of a query doesn't query the server and instead just returns empty or no results depending on the query ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6).
20+
- ParseGeoPoint initializer now throws if geopoints are out-of-bounds instead of asserting ([#190](https://github.com/parse-community/Parse-Swift/pull/190)), thanks to [Corey Baker](https://github.com/cbaker6).
21+
- Persist all properties of ParseUser and ParseInstallation to keychain so they can be accessed via current. Developers don't have to fetch the ParseUser or ParseInstlation after app restart anymore ([#191](https://github.com/parse-community/Parse-Swift/pull/191)), thanks to [Corey Baker](https://github.com/cbaker6).
22+
23+
__Fixes__
24+
- Fixed a bug when signing up from a ParseUser instance resulted in custom keys not being persisted to the keychain ([#187](https://github.com/parse-community/Parse-Swift/pull/187)), thanks to [Corey Baker](https://github.com/cbaker6).
25+
- Fixed a bug where countExplain query result wasn't returned as an array ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6).
26+
- The query withinPolygon(key: String, points: [ParseGeoPoint]) now works correctly and sends an array of doubles instead of an array of GeoPoint's ([#190](https://github.com/parse-community/Parse-Swift/pull/190)), thanks to [Corey Baker](https://github.com/cbaker6).
27+
- Fixed a bug where the ParseEncoder incorrectly detects a circular dependency when two child objects are the same ([#194](https://github.com/parse-community/Parse-Swift/pull/194)), thanks to [Corey Baker](https://github.com/cbaker6).
28+
- Make sure all LiveQuery socket changes are received on the correct queue to prevent threading issues ([#195](https://github.com/parse-community/Parse-Swift/pull/195)), thanks to [Corey Baker](https://github.com/cbaker6).
29+
730
### 1.8.6
831
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.5...1.8.6)
932

ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,5 +346,4 @@ do {
346346
}
347347

348348
PlaygroundPage.current.finishExecution()
349-
350349
//: [Next](@next)

ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,4 @@ score.save(options: [.context(["hello": "world"])]) { result in
113113
}
114114

115115
PlaygroundPage.current.finishExecution()
116-
117116
//: [Next](@next)

ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
//: [Previous](@previous)
22

3-
//: For this page, make sure your build target is set to ParseSwift (iOS) and targeting
4-
//: an iPhone, iPod, or iPad. Also be sure your `Playground Settings`
5-
//: in the `File Inspector` is `Platform = iOS`. This is because
6-
//: SwiftUI in macOS Playgrounds doesn't seem to build correctly
7-
//: Be sure to switch your target and `Playground Settings` back to
8-
//: macOS after leaving this page.
9-
103
import PlaygroundSupport
114
import Foundation
125
import ParseSwift
13-
#if canImport(SwiftUI)
146
import SwiftUI
15-
#if canImport(Combine)
16-
import Combine
17-
#endif
18-
#endif
7+
198
PlaygroundPage.current.needsIndefiniteExecution = true
209

2110
initializeParse()
@@ -45,62 +34,6 @@ struct GameScore: ParseObject {
4534
//: Create a query just as you normally would.
4635
var query = GameScore.query("score" < 11)
4736

48-
#if canImport(SwiftUI)
49-
//: To use subscriptions inside of SwiftUI
50-
struct ContentView: View {
51-
52-
//: A LiveQuery subscription can be used as a view model in SwiftUI
53-
@ObservedObject var subscription = query.subscribe!
54-
55-
var body: some View {
56-
VStack {
57-
58-
if subscription.subscribed != nil {
59-
Text("Subscribed to query!")
60-
} else if subscription.unsubscribed != nil {
61-
Text("Unsubscribed from query!")
62-
} else if let event = subscription.event {
63-
64-
//: This is how you register to receive notifications of events related to your LiveQuery.
65-
switch event.event {
66-
67-
case .entered(let object):
68-
Text("Entered with score: \(object.score)")
69-
case .left(let object):
70-
Text("Left with score: \(object.score)")
71-
case .created(let object):
72-
Text("Created with score: \(object.score)")
73-
case .updated(let object):
74-
Text("Updated with score: \(object.score)")
75-
case .deleted(let object):
76-
Text("Deleted with score: \(object.score)")
77-
}
78-
} else {
79-
Text("Not subscribed to a query")
80-
}
81-
82-
Spacer()
83-
84-
Text("Update GameScore in Parse Dashboard to see changes here")
85-
86-
Button(action: {
87-
try? query.unsubscribe()
88-
}, label: {
89-
Text("Unsubscribe")
90-
.font(.headline)
91-
.background(Color.red)
92-
.foregroundColor(.white)
93-
.padding()
94-
.cornerRadius(20.0)
95-
.frame(width: 300, height: 50)
96-
})
97-
}
98-
}
99-
}
100-
101-
PlaygroundPage.current.setLiveView(ContentView())
102-
#endif
103-
10437
//: This is how you subscribe to your created query using callbacks.
10538
let subscription = query.subscribeCallback!
10639

ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,5 +229,4 @@ let relation = User.current!.relation
229229
//: similar to `users` and `roles`.
230230

231231
PlaygroundPage.current.finishExecution()
232-
233232
//: [Next](@next)

ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,4 @@ config.fetch { result in
6969
print(Config.current ?? "No config")
7070

7171
PlaygroundPage.current.finishExecution()
72-
7372
//: [Next](@next)

ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,5 @@ scoreToFetch.fetch { result in
118118
}
119119
}
120120

121+
PlaygroundPage.current.finishExecution()
121122
//: [Next](@next)

ParseSwift.playground/Pages/16 - Analytics.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,4 @@ friendEvent.track(dimensions: ["more": "info"]) { result in
3636
}
3737

3838
PlaygroundPage.current.finishExecution()
39-
4039
//: [Next](@next)
Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
//: [Previous](@previous)
22

3+
//: If you are using Xcode 13+, ignore the comments below:
34
//: For this page, make sure your build target is set to ParseSwift (iOS) and targeting
45
//: an iPhone, iPod, or iPad. Also be sure your `Playground Settings`
56
//: in the `File Inspector` is `Platform = iOS`. This is because
67
//: SwiftUI in macOS Playgrounds doesn't seem to build correctly
78
//: Be sure to switch your target and `Playground Settings` back to
89
//: macOS after leaving this page.
910

10-
#if canImport(SwiftUI)
1111
import PlaygroundSupport
1212
import Foundation
1313
import ParseSwift
1414
import SwiftUI
15-
#if canImport(Combine)
16-
import Combine
17-
#endif
1815

1916
PlaygroundPage.current.needsIndefiniteExecution = true
2017

@@ -25,11 +22,10 @@ struct GameScore: ParseObject, Identifiable {
2522

2623
//: Conform to Identifiable for iOS13+
2724
var id: String { // swiftlint:disable:this identifier_name
28-
if let objectId = self.objectId {
29-
return objectId
30-
} else {
25+
guard let objectId = self.objectId else {
3126
return UUID().uuidString
3227
}
28+
return objectId
3329
}
3430

3531
//: These are required for any Object.
@@ -52,68 +48,38 @@ struct GameScore: ParseObject, Identifiable {
5248

5349
//: To use queries with SwiftUI
5450

55-
//: Create a custom view model that queries GameScore's.
56-
class ViewModel: ObservableObject {
57-
@Published var objects = [GameScore]()
58-
@Published var error: ParseError?
59-
60-
private var subscriptions = Set<AnyCancellable>()
61-
62-
init() {
63-
fetchScores()
64-
}
65-
66-
func fetchScores() {
67-
let query = GameScore.query("score" > 2)
68-
.order([.descending("score")])
69-
let publisher = query
70-
.findPublisher()
71-
.sink(receiveCompletion: { result in
72-
switch result {
73-
case .failure(let error):
74-
// Publish error.
75-
self.error = error
76-
case .finished:
77-
print("Successfully queried data")
78-
}
79-
},
80-
receiveValue: {
81-
// Publish found objects
82-
self.objects = $0
83-
print("Found \(self.objects.count), objects: \(self.objects)")
84-
})
85-
publisher.store(in: &subscriptions)
86-
}
87-
}
88-
8951
//: Create a SwiftUI view.
9052
struct ContentView: View {
9153

9254
//: A view model in SwiftUI
93-
@ObservedObject var viewModel = ViewModel()
55+
@ObservedObject var viewModel = GameScore.query("score" > 2)
56+
.order([.descending("score")])
57+
.viewModel
9458

9559
var body: some View {
9660
NavigationView {
9761
if let error = viewModel.error {
98-
Text(error.debugDescription)
62+
Text(error.description)
9963
} else {
10064
//: Warning - List seems to only work in Playgrounds Xcode 13+.
101-
List(viewModel.objects, id: \.objectId) { object in
65+
List(viewModel.results, id: \.id) { result in
10266
VStack(alignment: .leading) {
103-
Text("Score: \(object.score)")
67+
Text("Score: \(result.score)")
10468
.font(.headline)
105-
if let createdAt = object.createdAt {
69+
if let createdAt = result.createdAt {
10670
Text("\(createdAt.description)")
10771
}
10872
}
10973
}
11074
}
11175
Spacer()
112-
}
76+
}.onAppear(perform: {
77+
viewModel.find()
78+
})
11379
}
11480
}
11581

11682
PlaygroundPage.current.setLiveView(ContentView())
117-
#endif
11883

84+
PlaygroundPage.current.finishExecution()
11985
//: [Next](@next)

0 commit comments

Comments
 (0)