I have a Song View where I can add or remove a song as favourite, all works well and the update takes place, now what I want is a View where I can see list of all songs that are favourites, so I use an @ObservedObject property wrapper and then use it to display the list …
The problem comes when I go to a songs page and add/remove a song as favourite, the favourite View is unable to redraw it self and continues to show old list of favourites, however if I restart the app it works, how can I overcome this, the @ObservedObject wrapper must be watching any changes to the @Published property which the array of songs is , so why is it not working instantly, thanks …
Where I define ObservableObject class
struct Song: Identifiable, Codable {
var id = UUID()
var name: String
var album: String
var isFavorite: Bool
var genre: String
var artist: String
init(name: String, album: String, isFavorite: Bool, genre: String, artist: String) {
self.name = name
self.album = album
self.artist = artist
self.genre = genre
self.isFavorite = isFavorite
}
static let `default` = Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo")
}
class Songs: ObservableObject {
var songsData: [Song] =
[
Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
]
@Published var songs = [Song]() {
didSet {
if let encoded = try? JSONEncoder().encode(songs) {
UserDefaults.standard.set(encoded, forKey: "demosongs")
}
}
}
init(){
if let savedItems = UserDefaults.standard.data(forKey: "demosongs") {
if let decodedItems = try? JSONDecoder().decode([Song].self, from: savedItems) {
songs = decodedItems
return
}
}
songs = songsData
}
}
Where I add/remove favourites
struct SongView: View {
@Binding var song: Song
var body: some View {
Text("\(song.name)")
Button(song.isFavorite ? "Remove from favorite" : "add to favorites") {
song.isFavorite.toggle()
}
Text("Hello World")
}
}
where I try and display favourites
struct FavoritesView: View {
@ObservedObject var songs: Songs
var body: some View {
List(songs.songs) { song in
if song.isFavorite {
Text(song.name)
}
}
}
}
// How I use SongView
struct AlbumView: View {
@ObservedObject var songData: Songs
var body: some View {
ZStack {
Image("cover")
.resizable().opacity(0.5)
.zIndex(1)
ScrollView {
VStack {
ForEach($songData.songs) { $song in
NavigationLink {
SongView(song: $song)
} label: {
customText(image: "joinus", str: song.name)
.frame(maxWidth: .infinity)
}
}
}.padding()
}.zIndex(2)
}
}
func customText(image: String, str: String) -> some View {
Group {
VStack {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.padding()
.frame(width: 300, height: 150)
.overlay(Image(image)
.resizable()
.frame(width: 300, height: 150))
Text(str)
.scaledFont(name: "Gothic", size: 20)
.foregroundColor(.white)
.padding()
.background(RoundedRectangle(cornerRadius: 20, style: .continuous).fill(Color.gray))
}
}
}
}
CodePudding user response:
Here is some example code that lets you change the song.isFavorite and
have it reflected in AlbumView. It shows AlbumView is updated as you
press song.isFavorite.toggle() in SongView. It really depends on how you use your code.
struct ContentView: View {
@StateObject var songs = Songs() // <-- here
var body: some View {
NavigationView { // <-- here
AlbumView(songData: songs) // <-- here
}
}
}
struct AlbumView: View {
@ObservedObject var songData: Songs
var body: some View {
ZStack {
Image("cover")
.resizable().opacity(0.5)
.zIndex(1)
ScrollView {
VStack (spacing: 44) {
ForEach($songData.songs) { $song in
NavigationLink {
SongView(song: $song)
} label: {
// for testing
Text(song.name) Text(song.isFavorite ? " is favorite" : " is NOT favorite")
// customText(image: "joinus", str: song.name)
// .frame(maxWidth: .infinity)
}
}
}.padding()
}.zIndex(2)
}
}
func customText(image: String, str: String) -> some View {
Group {
VStack {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.padding()
.frame(width: 300, height: 150)
.overlay(Image(image)
.resizable()
.frame(width: 300, height: 150))
Text(str)
// .scaledFont(name: "Gothic", size: 20)
// .foregroundColor(.white)
.padding()
.background(RoundedRectangle(cornerRadius: 20, style: .continuous).fill(Color.gray))
}
}
}
}
struct SongView: View {
@Binding var song: Song
var body: some View {
VStack {
// -- here for testing
Text(song.name) Text(song.isFavorite ? " is favorite" : " is NOT favorite")
Button(song.isFavorite ? "Remove from favorite" : "add to favorites") {
song.isFavorite.toggle()
}
}
}
}
struct Song: Identifiable, Codable {
var id = UUID()
var name: String
var album: String
var isFavorite: Bool
var genre: String
var artist: String
init(name: String, album: String, isFavorite: Bool, genre: String, artist: String) {
self.name = name
self.album = album
self.artist = artist
self.genre = genre
self.isFavorite = isFavorite
}
static let `default` = Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo")
}
class Songs: ObservableObject {
var songsData: [Song] =
[
Song(name: "demo1", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo2", album: "demo", isFavorite: true, genre: "demo", artist: "demo"),
Song(name: "demo3", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
Song(name: "demo4", album: "demo", isFavorite: true, genre: "demo", artist: "demo"),
Song(name: "demo5", album: "demo", isFavorite: false, genre: "demo", artist: "demo"),
]
@Published var songs = [Song]() {
didSet {
if let encoded = try? JSONEncoder().encode(songs) {
UserDefaults.standard.set(encoded, forKey: "demosongs")
}
}
}
init(){
if let savedItems = UserDefaults.standard.data(forKey: "demosongs") {
if let decodedItems = try? JSONDecoder().decode([Song].self, from: savedItems) {
songs = decodedItems
return
}
}
songs = songsData
}
}
