SwiftUI中点击心形按钮实现商品收藏/取消收藏及列表展示问题
收藏视图无法展示数据的问题修复方案
核心问题分析
ViewModel数据不一致:
- 你在
FavouriteViewModel里维护了favourites和favItems两个数组,业务操作(添加/删除)基于favourites,但收藏视图展示的是favItems,两者未正确同步。 favItems未用@Published修饰,数据更新时无法触发UI刷新。addToFav方法同时调用insert和append,导致同一商品被重复添加,数据冗余。
- 你在
视图层ViewModel实例不共享:
ProductListView和FavouriteView各自初始化独立的FavouriteViewModel实例,数据完全隔离,列表页的操作无法同步到收藏视图。
具体修复步骤
1. 重构FavouriteViewModel
简化数据源,确保数据变更能触发UI更新:
import Foundation class FavouriteViewModel : ObservableObject { // 统一用该数组维护收藏数据,@Published确保变更通知视图 @Published var favourites = [ProductData]() let defaults = UserDefaults.standard init() { // 初始化时从UserDefaults加载数据到favourites guard let data = defaults.data(forKey: "Favourites") else { return } self.favourites = (try? PropertyListDecoder().decode([ProductData].self, from: data)) ?? [] } func contains(productItem: ProductData) -> Bool { favourites.contains(productItem) } func addToFav(productItem: ProductData) { guard !contains(productItem: productItem) else { return } // 避免重复添加 favourites.append(productItem) save() } func removeFromFav(productItem: ProductData) { if let index = favourites.lastIndex(of: productItem) { favourites.remove(at: index) } save() } func save() { defaults.set(try? PropertyListEncoder().encode(favourites), forKey: "Favourites") } }
- 移除冗余的
favItems和getDataFromUserDefaults方法,直接用favourites作为唯一数据源。 - 修复
addToFav的重复添加问题,添加前先判断是否已存在。 - 所有操作直接修改
@Published修饰的favourites,确保UI能感知数据变化。
2. 共享ViewModel实例
使用环境对象(EnvironmentObject)确保两个视图共用同一个ViewModel:
步骤1:在父视图注入ViewModel
假设根视图为ContentView,初始化ViewModel并注入环境:
struct ContentView: View { @StateObject private var favVM = FavouriteViewModel() var body: some View { TabView { ProductListView(item: "商品列表", filteredData: [/*你的商品数据*/]) .tabItem { Label("商品", systemImage: "list.bullet") } FavouriteView() .tabItem { Label("收藏", systemImage: "heart.fill") } } .environmentObject(favVM) // 注入环境对象 } }
步骤2:在子视图中获取共享的ViewModel
修改ProductListView:
struct ProductListView: View { @EnvironmentObject var favouriteViewModel: FavouriteViewModel // 从环境获取 @State var item : String @State var filteredData : [ProductData] // 其余代码保持不变,按钮逻辑无需修改 }
修改FavouriteView:
struct FavouriteView: View { @EnvironmentObject var favouriteViewModel: FavouriteViewModel // 从环境获取 var body: some View { NavigationStack { List { ForEach(favouriteViewModel.favourites) { item in // 直接使用favourites数组 ZStack { RoundedRectangle(cornerRadius: 20,style: .circular) .fill(.white) .shadow(radius: 1) .frame(width: 360,height: 120) HStack(spacing:10){ KFImage(URL(string: item.filename)) .resizable() .frame(width: 50,height: 50) .scaledToFit() .padding(.leading,20) VStack(alignment: .leading,spacing: 10) { Text(item.title) Text("\(item.price,specifier: "%.2f")/1Kg") .foregroundColor(Color.gray) } Spacer() Button(action: { // 收藏视图支持取消收藏 favouriteViewModel.removeFromFav(productItem: item) }) { Image("heartfill") .padding(.trailing,10) } } } } } .frame(maxWidth: .infinity) .edgesIgnoringSafeArea(.horizontal) .listStyle(GroupedListStyle()) .navigationBarTitle(Text("Favourites"),displayMode: .inline) } } }
- 移除
onAppear里的getDataFromUserDefaults调用,ViewModel初始化时已加载数据,且数据变更会自动通知视图。 - 优化收藏视图的按钮逻辑,支持直接取消收藏。
3. 优化Hashable实现(可选)
如果希望仅用id判断商品是否为同一收藏项,可自定义Hashable实现:
struct ProductData : Identifiable,Codable,Hashable { var id : Int var title :String var type : String var description : String var filename : String var price : Float var rating : Int // 自定义相等判断,仅比较id static func == (lhs: ProductData, rhs: ProductData) -> Bool { lhs.id == rhs.id } // 自定义哈希值,仅基于id func hash(into hasher: inout Hasher) { hasher.combine(id) } // 其余代码不变 }
修复后效果
- 列表页点击心形按钮,收藏数据同步保存到UserDefaults,按钮样式正确切换。
- 收藏视图自动刷新,展示最新收藏列表,支持点击取消收藏。
- 两个视图共享同一数据源,数据完全同步。
内容的提问来源于stack exchange,提问作者Vinduja Narayanan K
相关产品推荐
相关产品推荐

