Home > other >  Swift Observe dynamic dictionary
Swift Observe dynamic dictionary

Time:04-29

I'm trying to listen to changes of an element in a dictionary using @Published.

I have a dictionary, containing a list of player scores, indexed by player id. It looks like this:

class PlayersScore: ObservableObject {
    @Published var scores: [String: PlayerScore] = [:];
}

This dictionary is exposed through the property scores of a shared instance of a class called Api.

class Api: NSObject {
   static var shared = Api()
   @Published public var scores = PlayersScore()

   func updatePlayerScore(playerId: String, score: PlayerScore) {
       this.scores.scores[playerId] = score
   }
}

Sometimes, the score of a player is updated.

I have a view dedicated to a Player that looks like this:

struct PlayerView: View {
   var player: Player
   @ObservedObject private var scores = Api.shared.scores

   var body: some View {
       HStack {
           Text(player.name)
           Text("Latest score: \(scores.scores[player.id] ?? "not found")")
       }
   }
}

When the score of a player is updated, my view re-render.
My problem is that ... when a player score is updated, ALL players views are re-rendered as they all react to changes of the main dictionary.

So, I would like to subscribe to changes on a specific player score in his corresponding view, so only the appropriate view re-renders when necessary.

The player score may not be yet initialised when subscribing or accessing (ie scores.scores[player.id] may not exists yet).

Any idea how I could achieve this?

Thanks!

CodePudding user response:

Here is a possible approach - make PlayerScore as a observable class, so PlayersScore will be updated only when new player added, and due to observed specific PlayerScore explicitly only corresponding view will be updated on value changes.

class PlayerScore: ObservableObject {
  var id: String  // say back ref to user

  @Published var value: Int
  // ... other published properties
}

struct PlayerView: View {
   var player: Player
   @ObservedObject private var scores = Api.shared.scores

   var body: some View {
       HStack {
           Text(player.name)
           if let score = scores.scores[player.id] {
              PlayerScoreView(playerScore: score)
           } else {
              Text("No scores")
           }
       }
   }
}

struct PlayerScoreView: View {
  @ObservedObject var playerScore: PlayerScore

  var body: some View {
     Text("Latest score: \(playerScore.value)")
  }
}

Note: this is just a sketch coded here, so might be typo

  • Related