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

SwiftUI:点击按钮传递UserDefaults数据至下页及收藏数据为空排查

问题描述

已为结构体配置UserDefaults,想要实现点击爱心按钮将产品从列表页添加至收藏页,再次点击则移除,但目前favouritedData始终为空。需解决两个问题:

  1. 如何从UserDefaults中获取存储的值并展示在收藏视图中?
  2. 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类型的读写逻辑不匹配,以及初始化代码存在错误:

  1. 修正收藏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 = []
}
  1. 修正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("无收藏数据")
    }
}
  1. 统一读写逻辑(可选优化)
    将读取收藏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实例即可实现跨视图状态同步:

  1. 在App入口注入ViewModel
    确保全局只有一个ViewModel实例,所有视图共享:
@main
struct YourApp: App {
    @StateObject private var favVM = FavouriteViewModel()
    
    var body: some Scene {
        WindowGroup {
            ProductListView()
                .environmentObject(favVM)
        }
    }
}
  1. 列表页处理收藏按钮点击
    在产品行中绑定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)
            }
        }
    }
}
  1. 跳转至收藏视图
    由于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 01:55:02