无法在 SwiftUI 中重新呈现 CoreData 更新视图

Cannot rerender View on CoreData Update in SwiftUI

我能够更新收藏夹部分中的收藏夹列表,但只有在我重新启动应用程序后,我有多个答案建议添加 @ObservedObject var asset: Artists 等并添加托管对象上下文,我试过所有,但是收藏夹部分不会在核心数据更改时更新,任何人都可以提出解决此问题的方法,下面是文件的代码,我希望在核心数据更新后看到添加和显示的收藏夹,但目前是这个视图只有在我重新启动应用程序后才会更新。

代码已分为 SongCell 部分,显示每个单元格及其进一步提取的播放按钮。当我重新加载应用程序时,还会显示一张图片,以查看我在收藏夹中想要的内容 section.Thanks。

[![在此处输入图片描述][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())
        }
    }
}

//loremipsum 答案后更新代码

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

//最喜欢的仓库

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

//根据 loremipsum 的建议更新以删除 ViewModel 和存储库

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

您的代码不是 Minimal Reproducible Example,因此无法知道这是否会修复它,但我看到了一些“错误”。

首先,你应该只在 View 中使用 @StateObject initialize 一个 ObservableObject,所以像这样更改所有具有 init 的代码

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

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

其次,ObservableObject 不应是 @Binding,而应是 StateObjectObservedObjectEnvironmentObject。所以,

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

被滥用。

第三,您似乎使用了 2 个不同的 FavoriteListVM,一个将无法看到另一个在做什么。

第一个例子是

@Binding var favListVM1: FavoriteListVM

第二个是

@ObservedObject  var favListVM = FavoriteListVM()

那么,你如何解决这个问题...

Favorites变化

@Binding var favListVM1: FavoriteListVM 

@ObservedObject var favListVM1: FavoriteListVM 

然后删除@ObservedObject var favListVM = FavoriteListVM()

并将对 favListVM 的引用更改为 favListVM1

SongCell 中将 @Binding var favListVM1: FavoriteListVM 更改为 @ObservedObject var favListVM1: FavoriteListVM

ExtractedView变化

@Binding var favoriteListVM2: FavoriteListVM

@ObservedObject var favoriteListVM2: FavoriteListVM

此外,这个@Published var favRepository = FavoriteRepository()不知道@ObservedObject var repo = FavoriteRepository()在做什么。因此,如果您期望一个人知道另一个人在做什么,您就会遇到脱节。

总结

所有的变化加起来只有一个FavoriteListVM在每个View.

第一个 FavoriteListVM 应该是一个 @StateObject,它可能在 Favorites 的父视图中。 FavoriteListVM 数组除外,它应该在存储单元 vms 时存储。

并且子视图中的所有后续引用都应该是 @ObservedObject 而不是 @Binding

每次初始化某些东西(例如 FavoriteRepository()FavoriteListVM())时,您都在创建完全独立于另一个的不同实例。比如有两个人,两辆车,两栋房子,两首歌等等。坚持创建尽可能少的实例。

旁注: 一旦你让它工作,就摆脱额外的变量,比如

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

您有视图模型,单独引用它没有意义

最后更新

替换此行

@Binding var songLVM: SongListVM

@ObservedObject var avManager: ArtistsAVManager

当然,现在您必须对 SongListVM 进行更改,使其看起来像这样

//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"
            }
        }
    }
}

现在您的 play/pause 按钮看起来像这样

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

如果你想添加一个停止按钮,你可以这样做

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

首先,ViewModel 最好使用@StateObject 而不是@ObservedObject。

所以问题是在您的 ViewModel 中,favCellVMs 没有更新更改,它只在初始化时设置一次。

只要不更新,就没有新值发布。

如何修复:

通常最喜欢的列表可以由用户从任何地方更改,所以将您的 SongRepository 写成这样:

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

在您的应用视图中添加 (@main):

@StateObject var songRepository = SongRepository

并像这样将其传递给您的根视图:

.environmentObject(songRepository)

最后在您的选项卡中添加

@EnvoirmentObject var songRepository : SongRepository

现在您可以在任何地方从 songRepository.favSongs 表单中删除、添加和读取项目。

构造收藏夹:查看 {

@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)
                }
            } ....