SwiftUI中带有@EnvironmentObject的数据流

时间:2019-08-23 05:11:21

标签: view refactoring 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
        }
      }
    }
  }
}
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中来回移动,我希望每个视图都得到更新。我敢肯定,这里有一些错误的构造错误,但是我没有得到正确的答案。

3 个答案:

答案 0 :(得分:1)

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

您的输入很有道理,但是我认为您应该只能使用输入,而不能访问model EnvironmentObject。例如,在PlayerView代码中,您可以执行game.title而不是$model.games[gameIndex].title。问题在于,正如您的代码所示,对游戏标题的更改将不会被反映出来。这就是ObservedObject的来源。像EnvironmentObject一样,ObservedObject s会告诉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()
        }
    }
}

答案 1 :(得分:1)

我有完全相同的用例,并且也在@ObservedObject和@EnvironmentObject之间进行选择。

我看了很多书,还看过开发人员的一些视频(在YouTube和Udemy上)。像Nico一样,我也偏爱@EnviromentObject。我还连接了两个列表视图,然后是详细信息视图,一些编辑视图,设置视图等。

使用@EnvironmentObject似乎比使用@ObservedObject来回传递数据更容易。但是……数据总量是否也是一个论点?

有什么想法吗?

答案 2 :(得分:0)

给出一些环境对象,例如:

class EnvironmentObject1: ObservableObject {
    @Published var value: Bool
    ...
}

在需要这些环境对象之一的任何视图上,只需在其上添加@EnvironmentObject批注:

struct MenuContent: View {
    @EnvironmentObject var object1: EnvironmentObject1
}

您可以创建一个简化所有视图的ViewModifier,而不是通过所有中间视图链传递环境对象:

struct EnvironmentObjectsHolder: ViewModifier {
    static var object1: EnvironmentObject1 = EnvironmentObject1()
    static var object2: EnvironmentObject1 = EnvironmentObject2()

    func body(content: Content) -> some View {
        content
            .environmentObject(Self.object1)
            .environmentObject(Self.object2)
    }
}

当您需要视图时,只需使用修饰符:

struct SomeView: View {

    var body: some View {
        MenuContent()
           .modifier(EnvironmentObjectsHolder())

    }
}

检查此帖子: https://medium.com/swlh/swiftui-and-the-missing-environment-object-1a4bf8913ba7