Reputation: 705
I have an issue where the ViewModel
would re-initialize when view is updated.
I have 2 views, SongListView
and PlayerView
which share an object of Player
. When player's Playing state is changed (isPlaying == true)
, the viewModel
in SongListView
resets and becomes empty array. Due to which, the list on my view becomes empty.
SongListView:
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()
}
}
PlayerView:
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()
}
}
}
}
}
Player:
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
}
}
Thanks in Advance!
UPDATE: Still doesn't work
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)
}
}
}
}
}
Upvotes: 4
Views: 4204
Reputation: 81
Using @StateObject
instead of @ObservedObject
worked for me.
A property marked as @StateObject
will keep its initially assigned ObservedObject instance as long as the view is needed, even when the struct gets recreated by SwiftUI.
This allows you to maintain the state of your ObservedObject data.
Upvotes: 8
Reputation: 263
Use @StateObject instead! ObservedObject seems to be recreating the entire object every time.
Upvotes: 4
Reputation: 705
So finally I was able to fix this issue by removing @ObservedObject
property wrapper on player: Player
. I am not sure why this works. Seems like having more than one ObservedObject
in a view causes this problem. Now my code looks like this:
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)
}
}
}
}
}
Upvotes: 0
Reputation: 114773
SwiftUI views are structs, and therefore, immutable. When you update the state and cause the view to redraw, it actually creates a new instance of the view.
In your SongListView
you have
@ObservedObject var model: SongListViewModel = SongListViewModel()
This means that each time your SongListView
is re-drawn (which includes any time that player.isPlaying
is changed), you are initialising model
with a new instance of SongListViewModel
.
You should remove the default value and supply the model via a parameter to the initialiser of SongListView
-
@ObservedObject var model: SongListViewModel
Upvotes: 6