0

I am able to update the favorite list in the favorite section , but only after i restart the app, i have multiple answers suggesting to add @ObservedObject var asset: Artists etc and also adding the managed obbject context, i tried all, but the Favorite section will not update on coredata change , can any one kindly suggest a way out of this, below is the code of the file where i am hoping to see the Favorites being added and shown after coredata update but currently this view is getting updated only after i restart the app.

The code has been divided in sections where SongCell, shows each cell and its play button further extracted . An image is also shown of when i reload the app , to see what i want in Favorites section.Thanks.

[![enter image description here][1]][1]

import Foundation
import SwiftUI
import Combine
import AVFoundation

struct Favorites: View {
    
    
    
    
    @ObservedObject  var favListVM = FavoriteListVM()
    @ObservedObject var repo = FavoriteRepository()
    @Binding var favListVM1: FavoriteListVM
   
    
    
    var body: some View {
        
        VStack {
            
            NavigationView {
                List {
                    
                    ForEach(favListVM.favCellVMs) {
                        songCellVM in
                        
                     
                        SongCell(isVisible: $favListVM.isVisible, favCellVM: songCellVM, selectedSong: $favListVM.selectedSong, favListVM1: $favListVM1, isLoved: favListVM.isFavorite ?? false)
                    }
                }
                .navigationTitle("Favorites")
                .font(.subheadline)
            }
            
        
            if favListVM.isVisible  {
                HStack(alignment: .bottom){
                    Image(uiImage: UIImage(data: favListVM.selectedSong?.artistImage ?? Data()) ?? UIImage())
                        .resizable()
                        .frame(width: 50, height: 50, alignment: .leading)
                        .scaledToFit()
                        .cornerRadius(10)
                    Spacer()
                    VStack {
                        Text(favListVM.selectedSong?.songname ?? " ")
                        Text(favListVM.selectedSong?.artistname ?? " ")
                    }
                    
                    
                    ExtractedView(isVisible: $favListVM.isVisible, selectedSong: $favListVM.selectedSong, favoriteListVM2: $favListVM1, favCellVM: FavoriteCellVM(song: Song(album: favListVM.selectedSong?.album ?? "no album found", artistImage: favListVM.selectedSong?.artistImage, artistname: favListVM.selectedSong?.artistname ?? "unknown", genre: favListVM.selectedSong?.genre, songMp3: favListVM.selectedSong?.songMp3, songname: favListVM.selectedSong?.songname ?? "no songs found", id: favListVM.selectedSong?.id ?? UUID())))
                    
                }
            }
        }
        
    }
    
    struct SongCell: View {
        
        @Binding var isVisible: Bool
        @ObservedObject var favCellVM: FavoriteCellVM
        @State var playButton: Bool = false
        @Binding var selectedSong: Song?
        @Binding  var favListVM1: FavoriteListVM
        var isSelected: Bool { favCellVM.song.id == selectedSong?.id }
        @Environment(\.managedObjectContext) var managedObjectContext
        @State var isLoved:Bool
        
        
        @FetchRequest(entity: Artists.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Artists.artistname, ascending: true)]) var artists: FetchedResults<Artists>
        
        
        
        var onCommit: () -> () = {  }
        var body: some View {
            
            HStack {
                let result =  artists.filter { artist in
                    artist.id == favCellVM.song.id
                    
                }
                
                Image(uiImage: UIImage(data: favCellVM.song.artistImage ?? Data()) ?? UIImage())
                    .resizable()
                    .frame(width: 70, height: 70, alignment: .center)
                    .scaledToFit()
                    .cornerRadius(20)
                Spacer()
                Text(favCellVM.song.artistname)
                
                
                Button(action: {
                    
                    print(favCellVM.song.id!)
                    print(result[0].id!)
                    
                    if (result[0].isFavorite == nil){
                        result[0].isFavorite = true
                    }
                    else if(result[0].isFavorite == false) {
                        result[0].isFavorite = true
                    }
                    
                    else {
                        result[0].isFavorite = false
                    }
                    
                    
                    do {
                        try managedObjectContext.save()
                        print("done")
                        print(result)
                    }
                    catch {
                        print("\(error.localizedDescription)")
                    }
                    
                    
                    
                }) {  Image(systemName: result[0].isFavorite == true  ? "suit.heart.fill" : "suit.heart")
                    .resizable()
                    .frame(width: 25, height: 25, alignment: .center)
                    .padding()
                }
                .buttonStyle(PlainButtonStyle())
                
                //--
                
                ExtractedView(isVisible: $isVisible, selectedSong: $selectedSong, favoriteListVM2: $favListVM1,  favCellVM: favCellVM)
                
                
                
            }
        }
    }
    
    
    
    struct ExtractedView: View {
        @Binding var isVisible: Bool
        @Binding var selectedSong: Song?
        @Binding var favoriteListVM2: FavoriteListVM
        
        @ObservedObject var favCellVM: FavoriteCellVM
        var isSelected: Bool { favCellVM.song.id == selectedSong?.id }
        
        
        var body: some View {
            Button(action: {
                
                print(isSelected)
                isVisible.toggle()
                if isSelected  {
                    selectedSong = nil
                    favoriteListVM2.audioPlayer?.stop()
                    
                } else {
                    selectedSong = favCellVM.song
                    isVisible = true
                    do {
                        favoriteListVM2.audioPlayer?.stop()
                        favoriteListVM2.audioPlayer = try AVAudioPlayer(data: favCellVM.song.songMp3!)
                        favoriteListVM2.audioPlayer?.prepareToPlay()
                        favoriteListVM2.audioPlayer?.play()
                    } catch let error {
                        print("\(error.localizedDescription)")
                    }
                    
                }
                
                
            }){     Image(systemName: isSelected ? "pause.fill" : "play.fill")
                .resizable()
                .frame(width: 25, height: 25, alignment: .center)
                .padding()
            }
            .buttonStyle(PlainButtonStyle())
        }
    }
}

//Updated code after loremipsum answer

import Foundation
import SwiftUI
import Combine
import AVFoundation

struct Favorites: View {
    
    
    
    //  @ObservedObject var songsListVM = SongListVM()
   // @ObservedObject  var favListVM = FavoriteListVM()
  //  @StateObject  var favListVM: FavoriteListVM
    @StateObject var repo = FavoriteRepository()
    @ObservedObject var favListVM1: FavoriteListVM
   
    
    var body: some View {
        
        VStack {
            
            NavigationView {
                List {
                    
                    ForEach(favListVM1.favCellVMs) {
                        songCellVM in
                        
                        //                        SongCell(isVisible: $favListVM.isVisible , songCellVM: songCellVM, selectedSong: $favListVM.selectedSong, songsListVM1: $favListVM1)
                        SongCell(isVisible: $favListVM1.isVisible, favCellVM: songCellVM, selectedSong: $favListVM1.selectedSong,  isLoved: favListVM1.isFavorite ?? false)
                    }
                }
                .navigationTitle("Favorites")
                .font(.subheadline)
            }
            
            
            //--
            
            //--
            if favListVM1.isVisible  {
                HStack(alignment: .bottom){
                    Image(uiImage: UIImage(data: favListVM1.selectedSong?.artistImage ?? Data()) ?? UIImage())
                        .resizable()
                        .frame(width: 50, height: 50, alignment: .leading)
                        .scaledToFit()
                        .cornerRadius(10)
                    Spacer()
                    VStack {
                        Text(favListVM1.selectedSong?.songname ?? " ")
                        Text(favListVM1.selectedSong?.artistname ?? " ")
                    }
                    
                    
                    ExtractedView(isVisible: $favListVM1.isVisible, selectedSong: $favListVM1.selectedSong, favoriteListVM2: favListVM1, favCellVM: FavoriteCellVM(song: Song(album: favListVM1.selectedSong?.album ?? "no album found", artistImage: favListVM1.selectedSong?.artistImage, artistname: favListVM1.selectedSong?.artistname ?? "unknown", genre: favListVM1.selectedSong?.genre, songMp3: favListVM1.selectedSong?.songMp3, songname: favListVM1.selectedSong?.songname ?? "no songs found", id: favListVM1.selectedSong?.id ?? UUID())))
                    
                }
            }
        }
        
    }
    
    struct SongCell: View {
        
        @Binding var isVisible: Bool
        @ObservedObject var favCellVM: FavoriteCellVM
        @State var playButton: Bool = false
        @Binding var selectedSong: Song?
     //   @Binding  var favListVM1: FavoriteListVM
        var isSelected: Bool { favCellVM.song.id == selectedSong?.id }
        @Environment(\.managedObjectContext) var managedObjectContext
        @State var isLoved:Bool
        
        
        @FetchRequest(entity: Artists.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Artists.artistname, ascending: true)]) var artists: FetchedResults<Artists>
        
        
        
        var onCommit: () -> () = {  }
        var body: some View {
            
            HStack {
                let result =  artists.filter { artist in
                    artist.id == favCellVM.song.id
                    
                }
                
                Image(uiImage: UIImage(data: favCellVM.song.artistImage ?? Data()) ?? UIImage())
                    .resizable()
                    .frame(width: 70, height: 70, alignment: .center)
                    .scaledToFit()
                    .cornerRadius(20)
                Spacer()
                Text(favCellVM.song.artistname)
                
                
                Button(action: {
                    
                    print(favCellVM.song.id!)
                    print(result[0].id!)
                    
                    if (result[0].isFavorite == nil){
                        result[0].isFavorite = true
                    }
                    else if(result[0].isFavorite == false) {
                        result[0].isFavorite = true
                    }
                    
                    else {
                        result[0].isFavorite = false
                    }
                    
                    
                    do {
                        try managedObjectContext.save()
                        
                        print("done")
                      //  print(result)
                    }
                    catch {
                        print("\(error.localizedDescription)")
                    }
                    
                    
                    
                }) {  Image(systemName: result[0].isFavorite == true  ? "suit.heart.fill" : "suit.heart")
                    .resizable()
                    .frame(width: 25, height: 25, alignment: .center)
                    .padding()
                }
                .buttonStyle(PlainButtonStyle())
                
                //--
                
                ExtractedView(isVisible: $isVisible, selectedSong: $selectedSong, favoriteListVM2: favCellVM,  favCellVM: favCellVM)
                
                
                
            }
        }
    }
    
    
    
    struct ExtractedView: View {
        @Binding var isVisible: Bool
        @Binding var selectedSong: Song?
        @ObservedObject var favoriteListVM2: FavoriteListVM
        
        @ObservedObject var favCellVM: FavoriteCellVM
        var isSelected: Bool { favCellVM.song.id == selectedSong?.id }
        
        
        var body: some View {
            Button(action: {
                
                print(isSelected)
                isVisible.toggle()
                if isSelected  {
                    selectedSong = nil
                    favoriteListVM2.audioPlayer?.stop()
                    
                } else {
                    selectedSong = favCellVM.song
                    isVisible = true
                    do {
                        favoriteListVM2.audioPlayer?.stop()
                        favoriteListVM2.audioPlayer = try AVAudioPlayer(data: favCellVM.song.songMp3!)
                        favoriteListVM2.audioPlayer?.prepareToPlay()
                        favoriteListVM2.audioPlayer?.play()
                    } catch let error {
                        print("\(error.localizedDescription)")
                    }
                    
                }
                
                
            }){     Image(systemName: isSelected ? "pause.fill" : "play.fill")
                .resizable()
                .frame(width: 25, height: 25, alignment: .center)
                .padding()
            }
            .buttonStyle(PlainButtonStyle())
        }
    }
}

//Repository for favorite

import Foundation
import SwiftUI
import CoreData
import AVFoundation
import Combine

class FavoriteRepository: ObservableObject, Identifiable {
    @Published var song = [Song]()
   
       
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @FetchRequest(entity: Artists.entity(), sortDescriptors: []) var artists1: FetchedResults<Artists>
   
   
 
    init(){
        
        loadData()
    }
    
    
    
    func loadData() {
      

     
      

        let context = PersistenceManager.shared.container.viewContext
        let fetchRequest: NSFetchRequest<Artists>
        
        fetchRequest = Artists.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "isFavorite == %@", NSNumber(value: true))
        let objects = try! context.fetch(fetchRequest)
        song = objects.map {
                        artist in

            Song(album: artist.album!, artistImage: artist.artistImage, artistname: artist.artistname!, genre: artist.genre, songMp3: artist.songMp3, songname: artist.songname!, id: artist.id)
        
            }
 
        }
    }
        

//Update after advise from loremipsum to remove the ViewModel and repository

import Foundation
import SwiftUI
import Combine
import AVFoundation

struct Favorites: View {
    
    @Binding var songLVM: SongListVM
   
    
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(entity: Artists.entity(), sortDescriptors: [], predicate: NSPredicate(format: "isFavorite == %@ ", NSNumber(value: true))) var artists1: FetchedResults<Artists>
   
    var body: some View {
        
        VStack {
            
            NavigationView {
                List {
                    
                    ForEach(artists1) {
                        artist in
                        
                        HStack {
                            Image(uiImage: UIImage(data: artist.artistImage ?? Data()) ?? UIImage())
                                .resizable()
                                .frame(width: 50, height: 50, alignment: .leading)
                                .scaledToFit()
                                .cornerRadius(10)
                            Spacer()
                            Text(artist.artistname ?? "no name")
                            Text(artist.songname ?? "no song name")
                            
                            //-
                            Button(action: {
                                
                                //    print(artist.song.id!)
                                print(artist.id!)
                                
                                if (artist.isFavorite == nil){
                                    artist.isFavorite = true
                                }
                                else if(artist.isFavorite == false) {
                                    artist.isFavorite = true
                                }
                                
                                else {
                                    artist.isFavorite = false
                                }
                                
                                
                                do {
                                    try managedObjectContext.save()
                                    
                                    print("done")
                                    //  print(result)
                                }
                                catch {
                                    print("\(error.localizedDescription)")
                                }
                                
                                
                                
                            }) {  Image(systemName: artist.isFavorite == true  ? "suit.heart.fill" : "suit.heart")
                                .resizable()
                                .frame(width: 25, height: 25, alignment: .center)
                                .padding()
                            }
                            .buttonStyle(PlainButtonStyle())
                            // --
                            Button(action: {
                                do {
                                        songLVM.audioPlayer?.stop()
                                        songLVM.audioPlayer = try AVAudioPlayer(data: artist.songMp3!)
                                        songLVM.audioPlayer?.prepareToPlay()
                                        songLVM.audioPlayer?.play()
                                        
                                    }
                                catch {
                                        print("\(error.localizedDescription)")
                                    }
                            }){ Image(systemName:  false ? "pause.fill" : "play.fill")
                                .resizable()
                                .frame(width: 25, height: 25, alignment: .center)
                                .padding()
                            }
                            .buttonStyle(PlainButtonStyle())
                            // --
                            
                        }
                        
                        
                    }
                    
                }
                .navigationTitle("Favorites")
                .font(.subheadline)
                
            }
}}}

2 Answers 2

1

First of all use it's better to use @StateObject instead of @ObservedObject for ViewModel.

So the problem is that in your ViewModel, favCellVMs is not getting updated to changes , it is only set once at initialize.

as long as it is not updated, there is not new value to be published.

How to fix :

Usually favorite list can change from anywhere by the user , so write your SongRepository to something like this :

class SongRepository : ObservableObject {
    @Published var favSongs : [SongModel] = []
}

in your App View add (@main):

@StateObject var songRepository = SongRepository

and pass it to your root view like this :

.environmentObject(songRepository)

and finally in your tabs add

@EnvoirmentObject var songRepository : SongRepository

now you can remove , add and read items from songRepository.favSongs form anywhere.

struct Favorites: View {

@EnvoirmentObject var songRepository : SongRepository
@StateObject  var favListVM = FavoriteListVM()
//@ObservedObject var repo = FavoriteRepository()
//@Binding var favListVM1: FavoriteListVM



var body: some View {
    
    VStack {
        
        NavigationView {
            List {
                
                ForEach(songRepository.favSongs) {
                    songCellVM in
                    
                 
                    SongCell(SongModel)
                }
            } ....
Sign up to request clarification or add additional context in comments.

4 Comments

That is an improper initialization of @StateObject developer.apple.com/documentation/swiftui/…
That is an improper initialization of @ObservedObject and an improper use of @Binding
Oh and SongDataModel doesn't exist
he should Create a struct for storing song data like artist name , song name and ... and name it something like SongModel. its part of mvvm architecture its much cleaner .
0

Your code is not a Minimal Reproducible Example so it is impossible to know if this will fix it but a few "mistakes" I see.

First, you should only initialize an ObservableObject inside a View using @StateObject so change all the code that has an init like this

@ObservedObject  var favListVM = FavoriteListVM()
@ObservedObject var repo = FavoriteRepository()

To

@StateObject  var favListVM = FavoriteListVM()
@StateObject var repo = FavoriteRepository()

Second, and ObservableObject shouldn't be an @Binding it should be a StateObject, ObservedObject or EnvironmentObject. So,

@Binding var favListVM1: FavoriteListVM
@Binding var favoriteListVM2: FavoriteListVM

are being misused.

Third, you seem to be using 2 different FavoriteListVM and one will not be able to see what the other is doing.

The first instance is

@Binding var favListVM1: FavoriteListVM

and the second is

@ObservedObject  var favListVM = FavoriteListVM()

So, how do you fix this...

In Favorites change

@Binding var favListVM1: FavoriteListVM 

To

@ObservedObject var favListVM1: FavoriteListVM 

Then delete @ObservedObject var favListVM = FavoriteListVM()

And change the references to favListVM to say favListVM1

In SongCell change @Binding var favListVM1: FavoriteListVM to @ObservedObject var favListVM1: FavoriteListVM

In ExtractedView change

@Binding var favoriteListVM2: FavoriteListVM

To

@ObservedObject var favoriteListVM2: FavoriteListVM

Also, this @Published var favRepository = FavoriteRepository() has no idea what @ObservedObject var repo = FavoriteRepository() is doing. So, if you are expecting that one knows what the other is doing you will encounter a disconnect.

Summary

All the changes sum up to only have one FavoriteListVM in each View.

The first FavoriteListVM should be an @StateObject which will likely be in the parent view of Favorites. with the exception of an array of FavoriteListVM that should be stored as you store the cell vms.

And all subsequent references in the child Views should be an @ObservedObject not an @Binding.

Every time you initialize something (example FavoriteRepository() and FavoriteListVM()) you are creating different instances that are completely separate from the other. Like having two people, two cars, two houses, two songs, etc. Stick to creating single as little instances as possible.

Side note: Once you get it working get rid of the extra variables like

@Binding var isVisible: Bool
@Binding var selectedSong: Song?

You have the view model there is no point in referencing it separately

Last Update

Replace this line

@Binding var songLVM: SongListVM

With

@ObservedObject var avManager: ArtistsAVManager

Now of course you have to make changes to SongListVM to look something like this

//This class will keep track of everything to do with AVFoundation
//Will replace SongListVM


class ArtistsAVManager:ObservableObject{
    //You want this private because you dont want it modied on its own. Only via Play and Stop methods
    @Published private (set)var nowPlaying: Artists?
    @Published private (set)var status: Status = .stop
    @Published var alert: Alert?
    //Include other code you might have in SongListVM such as audioPlayer
    func play(artist: Artists){
        do {
            audioPlayer?.stop()
            audioPlayer = try AVAudioPlayer(data: artist.songMp3!)
            audioPlayer?.prepareToPlay()
            audioPlayer?.play()
            status = .play
            nowPlaying = artist
        }
        catch {
            print("\(error)")
            let nsError: NSError = error as NSError
            let message = "\(nsError.localizedDescription) \(nsError.localizedFailureReason ?? "") \(nsError.localizedRecoveryOptions?.first ?? "") \(nsError.localizedRecoverySuggestion ?? "")"
            alert = Alert(title: Text("Error: \(nsError.code)"), message: Text(message), dismissButton: .cancel())
            stop()
        }
        
        
    }
    
    func stop(){
        status = .stop
        audioPlayer?.stop()
        nowPlaying = nil
    }
    
    func pause(){
        status = .pause
        //Your code here
    }
    enum Status: String{
        case play
        case pause
        case stop
        
        func systemImageName() -> String{
            switch self {
            case .play:
                return "play.fill"
            case .pause:
                return "pause.fill"
            case .stop:
                return "stop.fill"
            }
        }
    }
}

Now your play/pause button would look something like this

Button(action: {
    if avManager.nowPlaying == artist{
        avManager.pause()
    }else{
        avManager.play(artist: artist)
    }
}){ Image(systemName: (avManager.nowPlaying == artist && avManager.status != .pause) ? ArtistsAVManager.Status.pause.systemImageName() : ArtistsAVManager.Status.play.systemImageName())
        .resizable()
        .frame(width: 25, height: 25, alignment: .center)
        .padding()
}
.buttonStyle(PlainButtonStyle())

And if you want to add a stop button you can do something like this

if avManager.nowPlaying == artist && avManager.status != .stop{
    Button(action: {
        avManager.stop()
    }){ Image(systemName:  ArtistsAVManager.Status.stop.systemImageName())
            .resizable()
            .frame(width: 25, height: 25, alignment: .center)
            .padding()
    }
    .buttonStyle(PlainButtonStyle())
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.