SwiftUI 中的数据流与@EnvironmentObject

6 refactoring view swiftui

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

struct GameView: View {

  @environmentObject var model: Model

  var body: some View {
    NavigationView {
      List(model.games) {game in
        NavigationLink(destination: PlayerView(game: game)) {
          // Some View
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
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
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Ben*_*dle 1

对于像你正在做的事情,我会使用ObservedObjectthis 来将特定数据从一个视图传递到另一个视图 - 特别是当对象中的属性可能会更改并且需要做出反应时。

您拥有的输入很有意义,但我认为您应该只能使用输入而不是访问model EnvironmentObject. 例如,在PlayerView代码中,您可以game.title代替$model.games[gameIndex].title. 问题是,正如您的代码一样,对游戏标题的更改不会反映出来,这就是ObservedObject出现的地方。像 一样EnvironmentObjectObservedObjects 会告诉 SwiftUI 在对象更新时更新视图。和EnvironmentObjectObservedObject要求类符合相同的ObservableObject协议(以前称为BindableObject)。

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

本文介绍了 SwiftUI 中不同类型的数据绑定,包括ObservedObject.

编辑 - 更改未显示

可能导致更改不显示的一个可能的“问题”是,每个对象ObjectWillChange不仅需要在其自己的值发生更改时发出事件,而且还需要在其任何子对象的值发生更改时发出事件。如果涉及到 s 数组,这就更复杂了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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)