如何持久化修改UserDefaults中Song数组的指定字段值
问题:UserDefaults持久化Song数组及AlbumView数据显示异常
问题概述
- 希望App启动时将Song数组保存至UserDefaults,重启后可正确加载;
- 通过@Binding修改Song的
isFavorite状态仅在当前会话生效,重启后修改丢失,需实现持久化; - 使用
@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)) } } }
修复说明
- 持久化失效问题:原代码
didSet中保存的是未修改的songsData,而非实际被修改的songs数组,导致修改无法持久化; - AlbumView空数据问题:原初始化逻辑中,若无UserDefaults数据则将
songs设为空数组,默认数据存在songsData中未被使用; - 导航链接错误:新建Songs实例会导致数据源分离,直接传递
$song绑定可确保修改同步回原数组,触发didSet完成持久化。
内容的提问来源于stack exchange,提问作者multiverse
相关产品推荐
相关产品推荐

