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)
}
}}}
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())
}