我的应用程序中有三个视图。第一个显示游戏列表。第二个有每个游戏的玩家列表。第三个为每个玩家提供一个得分列表。所以结构看起来像这样:
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在对象更新时更新视图。 EnvironmentObject
和ObservedObject
都要求该类遵循相同的ObservableObject
协议(以前称为BindableObject
)。
您将要做的主要更改是将ObservedObject
属性包装器添加到诸如game
和player
之类的视图输入中。如果还没有的话,可能还需要向ObservableObject
和Game
类添加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()
}
}
}