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

如何持久化修改UserDefaults中Song数组的指定字段值

问题:UserDefaults持久化Song数组及AlbumView数据显示异常

问题概述

  1. 希望App启动时将Song数组保存至UserDefaults,重启后可正确加载;
  2. 通过@Binding修改Song的isFavorite状态仅在当前会话生效,重启后修改丢失,需实现持久化;
  3. 使用@Published的songs数组时,AlbumView显示为空,仅使用songsData才能正常显示。

现有代码

Song 结构体

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

Songs 数据管理类

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(songsData) {
                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 = []
       }
}

SongView

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

AlbumView

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, songs: Songs())
                            
                        } 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))
            }
        }
    }
}

解决方案

1. 修复Songs类的核心逻辑

移除冗余的songsData,统一用@Published修饰的songs作为数据源,确保修改后能触发持久化:

class Songs: ObservableObject {
    @Published var songs = [Song]() {
        didSet {
            // 保存当前修改后的songs数组,而非旧的songsData
            if let encoded = try? JSONEncoder().encode(songs) {
                UserDefaults.standard.set(encoded, forKey: "demosongs")
            }
        }
    }
    
    init() {
        // 优先从UserDefaults加载历史数据
        if let savedItems = UserDefaults.standard.data(forKey: "demosongs") {
            if let decodedItems = try? JSONDecoder().decode([Song].self, from: savedItems) {
                songs = decodedItems
                return
            }
        }
        // 无历史数据时初始化默认歌曲列表
        songs = [
            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"),
        ]
    }
}

2. 修复AlbumView的导航链接错误

导航到SongView时,无需新建Songs实例,直接传递已绑定的$song即可:

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 {
                            // 直接传递song的绑定,无需新建Songs实例
                            SongView(song: $song)
                        } label: {
                            customText(image: "joinus", str: song.name)
                                .frame(maxWidth: .infinity)
                        }
                    }
                }.padding()
            }.zIndex(2)
        }
    }
    
    func customText(image: String, str: String) -> some View {
        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))
        }
    }
}

修复说明

  1. 持久化失效问题:原代码didSet中保存的是未修改的songsData,而非实际被修改的songs数组,导致修改无法持久化;
  2. AlbumView空数据问题:原初始化逻辑中,若无UserDefaults数据则将songs设为空数组,默认数据存在songsData中未被使用;
  3. 导航链接错误:新建Songs实例会导致数据源分离,直接传递$song绑定可确保修改同步回原数组,触发didSet完成持久化。

内容的提问来源于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 18:35:27