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

如何将单例购物车数据保存到CoreData,实现登出后重新登录留存?

解决登录后购物车持久化(CoreData)的问题

首先,咱们先梳理下你当前代码的核心问题:内存中的单例购物车和CoreData存储完全脱节了——你在DetailVC里添加商品时既存了CoreData又加了单例,但登录/启动时没把CoreData的数据同步回单例;而且MyCartVC的表格直接用单例数据,根本没和CoreData关联。另外你之前用UserDefaults存UITableViewDataSource肯定不行,因为它不是可序列化的类型,UserDefaults只能存基础类型或遵循Codable/NSCoding的对象。

下面是完整的解决方案,核心思路是让单例作为购物车的唯一数据源,同时负责和CoreData的双向同步,避免各个VC重复写CoreData逻辑:


第一步:完善CoreData实体(必须加用户标识)

先给你的MyCartData实体新增一个userId字段(String类型),这是关键! 不然多个用户的购物车数据会混在一起。

第二步:重构SingletonCart,让它管理CoreData同步

把CoreData的读写逻辑封装到单例里,这样所有页面只需要调用单例的方法,不用自己操作CoreData:

import Foundation
import CoreData

class SingletonCart {
    static let shared = SingletonCart()
    var food: [Food] = []
    private init() {}
    
    /// 从CoreData加载当前用户的购物车
    func loadCart(for userId: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "MyCartData")
        // 只加载当前用户的购物车
        fetchRequest.predicate = NSPredicate(format: "userId == %@", userId)
        
        do {
            let savedItems = try managedContext.fetch(fetchRequest)
            // 把CoreData对象转成你的Food模型
            food = savedItems.compactMap { item in
                guard let title = item.value(forKey: "fromDetailFoodNames") as? String,
                      let priceStr = item.value(forKey: "fromDetailFoodPrices") as? String else {
                    return nil
                }
                // 根据你的Food模型初始化,这里假设你有对应的初始化方法
                return Food(ProductTitle: title, PriceString: priceStr)
            }
            print("购物车从CoreData加载完成,共\(food.count)件商品")
        } catch let error as NSError {
            print("加载购物车失败: \(error), \(error.userInfo)")
        }
    }
    
    /// 添加商品到购物车(自动同步CoreData)
    func addFood(_ foodItem: Food, userId: String) {
        food.append(foodItem)
        saveFoodToCoreData(foodItem, userId: userId)
    }
    
    /// 删除购物车商品(自动同步CoreData)
    func removeFood(at index: Int, userId: String) {
        let removedItem = food.remove(at: index)
        deleteFoodFromCoreData(removedItem, userId: userId)
    }
    
    /// 保存单个商品到CoreData
    private func saveFoodToCoreData(_ foodItem: Food, userId: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        
        let entity = NSEntityDescription.entity(forEntityName: "MyCartData", in: managedContext)!
        let cartItem = NSManagedObject(entity: entity, insertInto: managedContext)
        cartItem.setValue(foodItem.ProductTitle, forKey: "fromDetailFoodNames")
        cartItem.setValue(foodItem.PriceString, forKey: "fromDetailFoodPrices")
        cartItem.setValue(userId, forKey: "userId") // 关联当前用户
        
        do {
            try managedContext.save()
            print("商品已保存到CoreData")
        } catch let error as NSError {
            print("保存商品失败: \(error), \(error.userInfo)")
        }
    }
    
    /// 从CoreData删除单个商品
    private func deleteFoodFromCoreData(_ foodItem: Food, userId: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "MyCartData")
        fetchRequest.predicate = NSPredicate(format: "userId == %@ AND fromDetailFoodNames == %@ AND fromDetailFoodPrices == %@", userId, foodItem.ProductTitle, foodItem.PriceString)
        
        do {
            let itemsToDelete = try managedContext.fetch(fetchRequest)
            itemsToDelete.forEach(managedContext.delete)
            try managedContext.save()
            print("商品已从CoreData删除")
        } catch let error as NSError {
            print("删除商品失败: \(error), \(error.userInfo)")
        }
    }
    
    /// 清空内存中的购物车(登出时调用,CoreData数据保留)
    func clearCart() {
        food.removeAll()
    }
}

第三步:修改DetailVC的添加逻辑

删掉DetailVC里自己写的CoreData代码,直接调用单例的方法:

@IBAction func addBasket(_ sender: Any) {
    // 先判断用户是否登录,获取当前用户ID(这里假设你把用户ID存在UserDefaults里)
    guard let currentUserId = UserDefaults.standard.string(forKey: "currentUserId") else {
        print("用户未登录,无法保存购物车")
        return
    }
    guard let foodItem = food else { return }
    
    // 调用单例方法,自动同步内存和CoreData
    SingletonCart.shared.addFood(foodItem, userId: currentUserId)
    
    self.performSegue(withIdentifier: "toMyCart", sender: nil)
    self.navigationController?.navigationBar.isHidden = false
    self.tabBarController?.tabBar.isHidden = false
    self.isLoading(true)
}

第四步:简化MyCartVC,直接用单例数据

去掉MyCartVC里冗余的CoreData代码,所有操作都基于单例:

import UIKit
import CoreData

class MyCartViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    @IBOutlet weak var myCartTableView: UITableView!
    @IBOutlet weak var totalPriceLabel: UILabel!
    // 直接用单例的购物车数据
    private var cartFoods: [Food] { SingletonCart.shared.food }

    @IBAction func approveCart(_ sender: Any) {
        // 这里处理结算逻辑
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tabBarController?.tabBar.isHidden = false
        myCartTableView.dataSource = self
        myCartTableView.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        myCartTableView.reloadData()
        
        // 更新TabBar的badge
        let badgeValue = cartFoods.count > 0 ? "\(cartFoods.count)" : nil
        self.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = badgeValue
        
        // 显示空视图
        if cartFoods.isEmpty {
            myCartTableView.setEmptyView(title: "Sepetinizde ürün bulunmamaktadır", message: "Seçtiğiniz yemekler burada listelenir.")
        } else {
            myCartTableView.restore()
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cartFoods.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let foodItem = cartFoods[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCartCell", for: indexPath) as! MyCartTableViewCell
        cell.myCartFoodNameLabel.text = foodItem.ProductTitle
        cell.myCartFoodPriceLabel.text = foodItem.PriceString
        return cell
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            guard let currentUserId = UserDefaults.standard.string(forKey: "currentUserId") else {
                print("用户未登录,无法删除购物车商品")
                return
            }
            // 调用单例方法删除,自动同步CoreData
            SingletonCart.shared.removeFood(at: indexPath.row, userId: currentUserId)
            
            tableView.beginUpdates()
            tableView.deleteRows(at: [indexPath], with: .automatic)
            tableView.endUpdates()
            
            // 更新badge和空视图
            let badgeValue = cartFoods.count > 0 ? "\(cartFoods.count)" : nil
            self.tabBarController?.viewControllers?[1].tabBarItem.badgeValue = badgeValue
            
            if cartFoods.isEmpty {
                myCartTableView.setEmptyView(title: "Sepetinizde ürün bulunmamaktadır", message: "Seçtiğiniz yemekler burada listelenir.")
            }
        }
    }
}

第五步:处理登录/登出逻辑

  • 登录成功后:保存用户ID到UserDefaults,然后加载CoreData中的购物车:
// 登录成功的回调示例
func onLoginSuccess(userId: String) {
    UserDefaults.standard.set(userId, forKey: "currentUserId")
    // 加载当前用户的购物车到单例
    SingletonCart.shared.loadCart(for: userId)
}
  • 登出时:清空内存中的购物车,但保留CoreData数据(下次登录再加载):
// 登出方法示例
func logout() {
    UserDefaults.standard.removeObject(forKey: "currentUserId")
    SingletonCart.shared.clearCart()
    // 跳转到登录页面
    let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC")
    UIApplication.shared.keyWindow?.rootViewController = UINavigationController(rootViewController: loginVC)
}

额外注意事项

  1. 确保你的Food模型可以从CoreData的MyCartData实体正确转换,上面的代码假设Food有对应的初始化方法,你需要根据自己的模型调整;
  2. 如果支持同一商品多次添加,建议给MyCartData实体加quantity字段,而不是重复存储相同商品;
  3. 可以给MyCartData实体添加唯一约束(userId+fromDetailFoodNames+fromDetailFoodPrices),避免重复添加相同商品;
  4. 在App启动时,如果用户已经登录(UserDefaults里有currentUserId),要调用SingletonCart.shared.loadCart(for: userId)加载购物车。

内容的提问来源于stack exchange,提问作者Emre Değirmenci

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:56:07