SwiftUI:点击按钮传递UserDefaults数据至下页及收藏数据为空排查
问题描述
已为结构体配置UserDefaults,想要实现点击爱心按钮将产品从列表页添加至收藏页,再次点击则移除,但目前favouritedData始终为空。需解决两个问题:
- 如何从UserDefaults中获取存储的值并展示在收藏视图中?
- SwiftUI中如何在按钮点击时将UserDefaults的数据传递至下一个视图?
现有代码
FavouriteViewModel
class FavouriteViewModel : ObservableObject { @Published var isfavourite = false @Published var productData = Set<ProductData>() // 用户标记为收藏的产品ID集合 @Published var favourites : Set<Int> let defaults = UserDefaults.standard var favItems : [ProductData] = [] init() { // 从UserDefaults加载数据 let decoder = PropertyListDecoder() if let data = defaults.data(forKey: "ProductItem") { productData = (try? decoder.decode(Set<ProductData>.self, from: data) ) ?? Set(favItems) } else { productData = Set(favItems) } self.favourites = Set(defaults.array(forKey: "Favourites") as? Set<Int> ?? []) getDataFromUserDefaults() } func getIds() -> Set<Int> { return favourites } func contains(productItem:ProductData) -> Bool { favourites.contains(productItem.id) } func addToFav(productItem:ProductData) { favourites.insert(productItem.id) save() } func removeFromFav(productItem:ProductData) { favourites.remove(productItem.id) save() } func save() { // 保存数据到UserDefaults let encoder = PropertyListEncoder() if let encoded = try? encoder.encode(self.productData) { self.defaults.set(encoded, forKey: "ProductItem") } self.defaults.set(Array(self.favourites), forKey: "Favourites") } func getDataFromUserDefaults() { let favouritedData = defaults.object(forKey: "Favourites") as? Set<Int> ?? [] print("the favourited data is \(favouritedData)") } }
模型结构
struct ProductModel : Decodable { let productData : [ProductData] private enum CodingKeys:String,CodingKey { case productData = "data" } } struct ProductData : Identifiable,Decodable,Hashable { var id : Int var title :String var type : String var description : String var filename : String var price : Float var rating : Int init() { self.id = 0 self.title = "" self.type = "" self.description = "" self.filename = "" self.price = 0.0 self.rating = 0 } }
解决方案
一、修复UserDefaults读取为空的问题
favouritedData为空的核心原因是Set类型的读写逻辑不匹配,以及初始化代码存在错误:
- 修正收藏ID的读取逻辑
UserDefaults无法直接存储Set<Int>,你存储时将其转为了Array,但读取时错误地尝试直接转为Set<Int>,导致强转失败。修改初始化代码:
// 原错误代码 // self.favourites = Set(defaults.array(forKey: "Favourites") as? Set<Int> ?? []) // 修复后 if let favArray = defaults.array(forKey: "Favourites") as? [Int] { self.favourites = Set(favArray) } else { self.favourites = [] }
- 修正
getDataFromUserDefaults方法
该方法的读取逻辑和初始化一致,需要先转成数组再转Set:
func getDataFromUserDefaults() { if let favArray = defaults.array(forKey: "Favourites") as? [Int] { let favouritedData = Set(favArray) print("the favourited data is \(favouritedData)") } else { print("无收藏数据") } }
- 统一读写逻辑(可选优化)
将读取收藏ID的逻辑抽成独立方法,避免重复代码:
private func loadFavourites() { if let favArray = defaults.array(forKey: "Favourites") as? [Int] { self.favourites = Set(favArray) } else { self.favourites = [] } }
然后在init()中调用loadFavourites()替代原有初始化代码。
二、在收藏视图展示收藏产品
收藏视图需要结合全局产品数据源和ViewModel中的favourites集合,筛选出已收藏的产品:
struct FavouritesView: View { @EnvironmentObject var favVM: FavouriteViewModel let allProducts: [ProductData] // 从父视图传入或全局数据源获取 var body: some View { List { ForEach(allProducts.filter { favVM.favourites.contains($0.id) }) { product in ProductRow(product: product) } } .navigationTitle("我的收藏") } }
三、SwiftUI中传递收藏数据到下一个视图
SwiftUI中无需直接传递UserDefaults数据,通过共享ObservableObject实例即可实现跨视图状态同步:
- 在App入口注入ViewModel
确保全局只有一个ViewModel实例,所有视图共享:
@main struct YourApp: App { @StateObject private var favVM = FavouriteViewModel() var body: some Scene { WindowGroup { ProductListView() .environmentObject(favVM) } } }
- 列表页处理收藏按钮点击
在产品行中绑定ViewModel,实现点击切换收藏状态:
struct ProductRow: View { let product: ProductData @EnvironmentObject var favVM: FavouriteViewModel var body: some View { HStack { Text(product.title) Spacer() Button(action: { if favVM.favourites.contains(product.id) { favVM.removeFromFav(productItem: product) } else { favVM.addToFav(productItem: product) } }) { Image(systemName: favVM.favourites.contains(product.id) ? "heart.fill" : "heart") .foregroundColor(.red) } } } }
- 跳转至收藏视图
由于favourites是@Published属性,当它变化时,所有绑定该ViewModel的视图会自动刷新,收藏视图会实时展示最新数据:
// 在产品列表页添加跳转按钮 NavigationLink(destination: FavouritesView(allProducts: allProducts)) { Text("查看收藏") }
额外优化建议
- 目前
productData集合未被使用,可考虑移除,或改为存储完整的收藏产品数据(无需依赖全局数据源)。 - 在
save()方法中添加错误捕获,避免编码失败静默发生:
func save() { // 保存收藏ID defaults.set(Array(self.favourites), forKey: "Favourites") // 保存完整产品数据(若需要) do { let encoded = try PropertyListEncoder().encode(self.productData) defaults.set(encoded, forKey: "ProductItem") } catch { print("产品数据编码失败:\(error)") } }
内容的提问来源于stack exchange,提问作者Vinduja Narayanan K
相关产品推荐
相关产品推荐

