【问题标题】:ObservedObject resets when I update the view using a property. Hence it leads to loss of data当我使用属性更新视图时,ObservedObject 会重置。因此会导致数据丢失
【发布时间】:2020-04-14 06:59:10
【问题描述】:

我有一个问题,当视图更新时ViewModel 会重新初始化。

我有 2 个视图,SongListViewPlayerView,它们共享 Player 的对象。当玩家的 Playing 状态改变(isPlaying == true) 时,SongListView 中的viewModel 重置并变为空数组。因此,我视图上的列表变为空。

歌曲列表视图:

struct SongListView: View {

    @ObservedObject var model: SongListViewModel = SongListViewModel() // This resets when player.isPlaying is set to true

    @ObservedObject var player: Player

    var body: some View {

        List(model.songs, id: \.id) { song in
            Button(action: {
                self.player.play(link: song.link)
            }) {
                TitleRowView(title: song)
            }
        }
        .onAppear {
            self.model.get()
        }
        .navigationBarTitle(Text("Songs"), displayMode: .inline)
    }
}

SongListViewModel:

class SongListViewModel: ObservableObject {

    @Published var songs: [Song] = [Song(id: 2, name: "ish", link: "ishm")] // When I tap the row, the songs var is re-initialized

    func get() {

        guard let url = URL(string: "apiPath") else { return }

        URLSession(configuration: URLSessionConfiguration.default).dataTask(with: url) {data, response, error 
               // Some more code
               self.songs = data
        }.resume()
    }
}

播放器视图:

struct PlayerView: View {

    @ObservedObject var player: Player

    var body: some View {
        HStack {
            Button(action: {
                if self.player.isPlaying {
                    self.player.pause()
                } else {
                    self.player.play()
                }
            }) {
                // This change causes the viewModel to reset to empty array
                if self.player.isPlaying { 
                    Image(systemName: "pause.fill")
                        .resizable()
                } else {
                    Image(systemName: "play.fill")
                        .resizable()
                }
            }
        }
    }
}

玩家:

class Player : ObservableObject
{

    @Published var isPlaying: Bool = false

    private var player: AVPlayer?

    // This method is called when the user taps on a row in List
    func play(link: String) {
        guard let url = URL(string: link) else { return }
        let playerItem = AVPlayerItem(url: url)
        player = AVPlayer(playerItem: playerItem)
        player?.play()
        isPlaying = true // If I comment this line, the songs list in viewModel does not changes
    }    
}

提前致谢!

更新:仍然不起作用

struct SongListView: View {

    @ObservedObject var model: SongListViewModel

    var body: some View {
        // View 
    }
}

struct CategoryListView: View {
    var categoryData : [Category]
    @ObservedObject var player: Player

    var body: some View {
        List(categoryData, id: \.id) { category in
            if category.id == 3 {
                NavigationLink(destination: SongListView(model: SongListViewModel(), player: self.player)) {
                    TitleRowView(title: category)
                }
            }

        }
    }
}

【问题讨论】:

  • 您是否尝试使用环境对象?这可能会解决您的问题,因为我可能认为您的整个视图正在以某种方式重新初始化......
  • @TobiasHesselink 我试过了,不行
  • @TobiasHesselink 使用EnvironmentObject 完美运行。我犯了另一个错误,并在下面的 Paulw11 提到的结构中初始化 SongListViewModel。您可以将此添加为答案,以便我可以将其标记为下面的答案。谢谢
  • 相反,现在我每第二次或第三次点击都会看到这个问题。 SongListViewModel.init() 每次视图更新时

标签: ios swift swiftui observableobject


【解决方案1】:

SwiftUI 视图是结构体,因此是不可变的。当您更新状态并导致视图重绘时,它实际上会创建一个新的视图实例。

在您的SongListView 中有

@ObservedObject var model: SongListViewModel = SongListViewModel()

这意味着每次重新绘制 SongListView 时(包括更改 player.isPlaying 的任何时间),您将使用 SongListViewModel 的新实例初始化 model

您应该删除默认值并通过参数将模型提供给SongListView的初始化器-

@ObservedObject var model: SongListViewModel

【讨论】:

  • 还是不行。通过将视图模型作为参数传递来更新问题
  • 你必须去掉SongListViewModelsongs的默认值。
  • 如果我这样做,编译器会抱怨初始化程序。
  • 是的;它必须是可选的;您的歌曲列表最初是空的。
  • 还是不行。将歌曲声明更改为@Published var songs: [Song]?
【解决方案2】:

使用 @StateObject 而不是 @ObservedObject 对我有用。

只要需要视图,标记为 @StateObject 的属性将保留其最初分配的 ObservedObject 实例,即使结构被 SwiftUI 重新创建。

这允许您维护 ObservedObject 数据的状态。

【讨论】:

    【解决方案3】:

    改用@StateObject! ObservedObject 似乎每次都在重新创建整个对象。

    【讨论】:

      【解决方案4】:

      所以最后我能够通过删除player: Player 上的@ObservedObject 属性包装器来解决这个问题。我不确定为什么会这样。似乎在一个视图中有多个 ObservedObject 会导致此问题。现在我的代码如下所示:

      struct SongListView: View {
      
          @ObservedObject var model: SongListViewModel
      
          @State var player: Player
      
          var body: some View {
      
              List(model.songs, id: \.id) { song in
                  Button(action: {
                      self.player.play(link: song.link)
                  }) {
                      TitleRowView(title: song)
                  }
              }
              .onAppear {
                  self.model.get()
              }
              .navigationBarTitle(Text("Songs"), displayMode: .inline)
          }
      }
      
      struct CategoryListView: View {
          var categoryData : [Category]
          @ObservedObject var player: Player
          let viewModel = SongListViewModel()
      
          var body: some View {
              List(categoryData, id: \.id) { category in
      
                  if category.id == 3 {
                      NavigationLink(destination: SongListView(player: self.player).environmentObject(self.viewModel)) {
                          TitleRowView(title: category)
                      }
                  }
      
              }
          }
      }
      

      【讨论】:

      • 我认为这应该不是问题。一个视图中可以有多个 ObservableObjects。
      • 我也相信。不知道为什么这有效,但任何其他解决方案都没有。然而,我确实意识到我不需要我的玩家对象在这个视图中是可观察的。
      • 之所以有效,是因为@State 保存在由 SwiftUI 管理的单独内存地址上,并且与您的视图相关联,但与它的绘图周期无关。当您查看重绘时,数据保持不变,并且不受您的视图重绘和重置其值的影响
      • 我和@Ishmeet 一起讨论这个问题。 ObservedObject,不管它是如何初始化的,都会导致整个 ViewStack 刷新任何导致 objectWillChange 的东西。
      猜你喜欢
      • 1970-01-01
      • 2014-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-02
      • 1970-01-01
      相关资源
      最近更新 更多