You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中@ObservedObject无法实时更新收藏列表问题求助

问题原因分析

你的问题核心在于SwiftUI的@Published属性只会监听数组本身的引用变化,而不会监听数组内部元素的属性变化。

具体来说:

  • Songs类中的songs是@Published修饰的数组,当你直接修改数组里某个Song的isFavorite属性时,数组的引用并没有改变(还是同一个数组对象),所以@Published不会发送变更通知,导致FavoritesView无法感知到变化,也就不会重新绘制。
  • 重启应用后,数据从UserDefaults重新加载,此时数组是新的引用,@ObservedObject能正确读取到最新的isFavorite状态。
解决办法

以下提供三种可行方案,可根据项目需求选择:

方案一:将Song改为ObservableObject类

把Song从结构体改成遵循ObservableObject的类,用@Published修饰isFavorite属性,这样修改属性时会主动发送通知,SwiftUI能实时感知到变化。

修改后的Song及兼容代码:

class Song: Identifiable, Codable, ObservableObject {
    var id = UUID()
    var name: String
    var album: String
    @Published 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")
    
    // 手动实现Codable协议,适配类类型的编码解码
    enum CodingKeys: String, CodingKey {
        case id, name, album, isFavorite, genre, artist
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        album = try container.decode(String.self, forKey: .album)
        isFavorite = try container.decode(Bool.self, forKey: .isFavorite)
        genre = try container.decode(String.self, forKey: .genre)
        artist = try container.decode(String.self, forKey: .artist)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(album, forKey: .album)
        try container.encode(isFavorite, forKey: .isFavorite)
        try container.encode(genre, forKey: .genre)
        try container.encode(artist, forKey: .artist)
    }
}

同时修改FavoritesView,监听单个Song的变化:

struct FavoritesView: View {
    @ObservedObject var songs: Songs
    
    var body: some View {
        List(songs.songs) { song in
            @ObservedObject var observedSong = song
            if observedSong.isFavorite {
                Text(observedSong.name)
            }
        }
    }
}

方案二:修改元素时替换数组中的对象

如果不想改动Song的结构体类型,可以在修改isFavorite时,找到对应索引并替换整个Song对象,让数组的引用发生变化,触发@Published的通知。

首先给Songs类添加切换收藏状态的方法:

class Songs: ObservableObject {
    // ... 原有代码 ...
    
    func toggleFavorite(for songId: UUID) {
        if let index = songs.firstIndex(where: { $0.id == songId }) {
            var updatedSong = songs[index]
            updatedSong.isFavorite.toggle()
            // 替换数组元素,触发@Published通知
            songs[index] = updatedSong
        }
    }
}

然后修改SongView,通过songs对象和songId来操作:

struct SongView: View {
    @ObservedObject var songs: Songs
    let songId: UUID
    
    private var song: Song? {
        songs.songs.first(where: { $0.id == songId })
    }
    
    var body: some View {
        Text(song?.name ?? "")

        Button(song?.isFavorite ?? false ? "Remove from favorite" : "add to favorites") {
            songs.toggleFavorite(for: songId)
        }
        Text("Hello World")
    }
}

在AlbumView中调整调用方式:

ForEach(songData.songs) { song in
    NavigationLink {
        SongView(songs: songData, songId: song.id)
    } label: {
        customText(image: "joinus", str: song.name)
            .frame(maxWidth: .infinity)
    }
}

方案三:手动发送变更通知

在修改Song属性后,手动调用objectWillChange.send()来强制通知视图更新,这种方式比较直接但不够优雅,适合临时场景:

修改SongView,在按钮点击时手动触发通知:

struct SongView: View {
    @Binding var song: Song
    @ObservedObject var songs: Songs // 需要传入songs对象
    
    var body: some View {
        Text("\(song.name)")

        Button(song.isFavorite ? "Remove from favorite" : "add to favorites") {
            song.isFavorite.toggle()
            // 手动发送视图更新通知
            songs.objectWillChange.send()
        }
        Text("Hello World")
    }
}

内容的提问来源于stack exchange,提问作者multiverse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 19:25:23