我的应用程序中有三个视图。第一个显示游戏列表。第二个有每个游戏的玩家列表。第三个为每个玩家提供一个得分列表。所以结构看起来像这样:

struct GameView: View {

  @environmentObject var model: Model

  var body: some View {
    NavigationView {
      List(model.games) {game in
        NavigationLink(destination: PlayerView(game: game)) {
          // Some View
        }
      }
    }
  }
}


struct PlayerView: View {

  @environmentObject var model: Model
  var game Game
  var gameIndex: Int {
    model.games.firstIndex() {$0 == game}!
  }

  var body: some View {
    TextField("Game", $model.games[gameIndex].title)
    List(game.players) {player in
      NavigationLink(destination: ScoreView(player: player, gameIndex: gameIndex)) {
        // Some View
      }
    }
  }
}


struct ScoreView: View {

  @environmentObject var model: Model
  var player: Player
  var gameIndex: Int

  var playerIndex: Int {
    model.games[gameIndex].players.firstIndex() { $0 == player }!
  }


  var body: some View {
    TextField("Player", $model.games[gameIndex].players[playerIndex].name)
    List(player.scores) {score in
        // Some View
    }
  }
}


我的问题是:对于深入到层次结构的每个视图,我都必须一直回到我的environmentObject,并通过带有索引的模型数组获得路径,而我必须通过每个视图。如果要更改TextField值(例如,作为应用程序中每个数据更改的示例),并且要在NavigationView中来回移动,我希望每个视图都得到更新。我敢肯定,有一些错误的构造错误,但是我没有得到正确的答案。

最佳答案

对于类似您正在执行的操作,我将使用ObservedObject,它非常适合将特定数据从一个视图传递到另一个视图-尤其是当对象中的某些属性可能更改并且需要响应时。

您输入的内容很有意义,但我认为您应该只能使用输入内容,而不能访问model EnvironmentObject。例如,在PlayerView代码中,您可以执行game.title而不是$model.games[gameIndex].title。问题在于,正如您的代码一样,对游戏标题的更改将不会被反映出来。这就是ObservedObject的所在。与EnvironmentObject一样,ObservedObject会告诉SwiftUI在对象更新时更新视图。 EnvironmentObjectObservedObject都要求该类遵循相同的ObservableObject协议(以前称为BindableObject)。

您将要做的主要更改是将ObservedObject属性包装器添加到诸如gameplayer之类的视图输入中。如果还没有的话,可能还需要向ObservableObjectGame类添加Player一致性。

this article解释了SwiftUI中不同类型的数据绑定,包括ObservedObject

编辑-更改未显示

可能导致未显示更改的一个可能的“陷阱”是,每个对象不仅需要在其自身的值发生更改时,而且在其子项的任何值发生更改时也需要发出ObjectWillChange事件。如果涉及到一个ObservableObject数组,则更为复杂。

这是我在这种情况下使用的代码:

// an array of cancelables because we need to subscribe to every object in the players array.
var playerCans: [Cancellable]?

@Published var players: [Player] = [Player(), Player()] {
    didSet {
        // necessary because if the array gets set to something new,
        // we want to notify of changes to the new array, not the old one
        setupCans()
    }
}

init() {
    setupCans()
}

func setupCans() {
    // Not sure if canceling is necessary, but doing it just in case
    self.playerCans?.forEach({ (can) in
        can.cancel()
    })
    self.playerCans = players.map { (player) in
        return player.objectWillChange.sink {
            self.objectWillChange.send()
        }
    }
}

07-26 05:18