Continuing from my previous question, what is the best way to observe several state changes from a UIViewRepresentable
? Previously, I required to only observe changes to one property userWon
. However, as new functionality was introduced in the app, I need to now observe new changes. Assuming these could increase even more, is the best way to do this by adding more State
properties and binding them to the UIViewRepresentable
? It feels somewhat hacky at this point to add more and more bindings and onChange
modifiers to observe for changes.
Example code is as follows:
struct GameView: View {
@State private var userWon: Bool = false
@State private var propA: Bool = false
@State private var propB: Int = 0
@State private var propC: Int = 0
...
...
...
@State private var propZ: Int = 0
@StateObject private var gameService = GameService()
var body: some View {
ZStack {
VStack {
GameViewRepresentable(userWon: $userWon,
propA: $propA,
propB: $propB,
propC: $propC,
...
propZ: $propZ) // <- Add more bindings??
.onChange(of: userWon) { newValue in
if newValue {
gameService.updateUserScore()
}
}
.onChange(of: propA) { newValue in
gameService.doSomethingA()
}
.onChange(of: propB) { newValue in
gameService.doSomethingB()
}
.onChange(of: propZ) { newValue in
gameService.doSomethingZ()
}
}
.clipped()
}
}
}
struct GameViewRepresentable: UIViewRepresentable {
typealias UIViewType = GameView
@Binding var userWon: Bool
func makeUIView(context: Context) -> GameView {
let gameView = GameView()
gameView.delegate = context.coordinator
return gameView
}
func updateUIView(_ uiView: GameView, context: Context) {
}
func makeCoordinator() -> GameViewCoordinator {
GameViewCoordinator(self)
}
class GameViewCoordinator: GameViewDelegate {
var parent: GameViewRepresentable
init(_ parent: GameViewRepresentable) {
self.parent = parent
}
func userWon() {
self.parent.userWon = true
}
}
}
Alternate approach that I can think of is:
gameService
that contains the aforementioned properties to be observed and pass that as binding to the UIViewRepresentable
.gameService
observe this enveloped object, probably using Combine
? for changes but I'm not sure how to do this or if it'd work at all.
Any help is appreciated on how to do this efficiently.You can move all the @State var
s into your GameService
as @Published var
s.
class GameService: ObservableObject {
@Published var someGameState = 1
@Published var anotherGameState = 1
func doSomething() {
print(someGameState)
}
func doSomethingElse() {
print(anotherGameState)
}
}
Then you can pass this to GameViewRepresentable
through an @ObservedObject
, to avoid writing out all the states the game view needs.
struct ContentView: View {
@StateObject var game = GameService()
var body: some View {
GameViewRepresentable(game: game)
}
}
struct GameViewRepresentable: UIViewRepresentable {
@ObservedObject var game: GameService
// ...
}
Since this is a UIViewRepresentable
, I think you would likely be updating game
through its properties' setters (rather than say, a Binding
produced from the projected value of ObservedObject
). In that case, you can just use didSet
to detect the changes:
@Published var someGameState = 1 {
didSet { self.doSomething() }
}
@Published var anotherGameState = 1 {
didSet { self.doSomethingElse() }
}
Otherwise, changes can be detected using onReceive
:
// add .dropFirst() if you don't want the initial value to trigger this
.onReceive(game.$someGameState) { _ in
game.doSomething()
}
.onReceive(game.$anotherGameState) { _ in
game.doSomethingElse()
}