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

SwiftData+UIKit:保存不同模型时数据丢失并触发报错

SwiftData 数据异常问题:保存Disc到购物车时Bag丢失+偶发SQLite错误

我有Bag和Disc两个模型,Disc可添加至「购物车」或「Bag」中。当前存在两个问题:

  • 将Disc保存到购物车时,Disc能成功存储,但此前创建的所有Bag会被彻底删除,推测问题出在数据处理逻辑上。
  • 保存Disc到购物车时偶尔(约50%概率)触发报错,错误信息如下:

error: (1) I/O error for database at
/Users/kinneykare/Library/Developer/CoreSimulator/Devices/598D3C68-FEB3-4D2C-AE97-FA7B58D4CD0D/data/Containers/Data/Application/591F6E15-E5E9-4624-8267-DB838828D807/Library/Application
Support/default.store. SQLite error code:1, 'no such table:
ZDISCSWIFTDATAMODEL' error: SQLCore dispatchRequest: exception
handling request: <NSSQLFetchRequestContext: 0x600003b0ed80> , I/O
error for database at
/Users/kinneykare/Library/Developer/CoreSimulator/Devices/598D3C68-FEB3-4D2C-AE97-FA7B58D4CD0D/data/Containers/Data/Application/591F6E15-E5E9-4624-8267-DB838828D807/Library/Application
Support/default.store. SQLite error code:1, 'no such table:
ZDISCSWIFTDATAMODEL' with userInfo of {
NSFilePath = "/Users/kinneykare/Library/Developer/CoreSimulator/Devices/598D3C68-FEB3-4D2C-AE97-FA7B58D4CD0D/data/Containers/Data/Application/591F6E15-E5E9-4624-8267-DB838828D807/Library/Application
Support/default.store";
NSSQLiteErrorDomain = 1; }


相关代码

1. 数据库操作核心类(DatabaseService)

import UIKit
import SwiftData

class DatabaseService {
    static let shared = DatabaseService()

    private var discContext: ModelContext?
    private var bagContext: ModelContext?

    private init() {
        do {
            discContext = try ModelContainer(for: DiscSwiftDataModel.self).map(ModelContext.init)
            bagContext = try ModelContainer(for: BagSwiftDataModel.self).map(ModelContext.init)
        } catch {
            print("Swift Data Error: \(error)")
        }
    }

    // MARK: Discs

    func saveDisc(
        discName: String,
        discImageData: Data,
        discStability: String,
        discSpeed: String,
        discGlide: String,
        discTurn: String,
        discFade: String,
        discBrand: String,
        viewController: UIViewController
    ) {
        saveData(
            context: discContext,
            data: DiscSwiftDataModel(
                id: UUID().uuidString,
                name: discName,
                imageData: discImageData,
                stability: discStability,
                speed: discSpeed,
                glide: discGlide,
                turn: discTurn,
                fade: discFade,
                brand: discBrand
            ),
            viewController: viewController
        )
    }

    func fetchDiscList(onCompletion: @escaping ([DiscSwiftDataModel]?, Error?) -> Void) {
        fetchData(context: discContext, descriptor: FetchDescriptor<DiscSwiftDataModel>(sortBy: [SortDescriptor<DiscSwiftDataModel>(\.brand)]), onCompletion: onCompletion)
    }

    func updateDisc(disc: DiscSwiftDataModel, newDiscName: String) {
        updateData(data: disc, updateBlock: { $0.name = newDiscName }, context: discContext)
    }

    func deleteDisc(disc: DiscSwiftDataModel) {
        deleteData(data: disc, context: discContext)
    }

    // MARK: Bags

    func saveBag(bagName: String, bagType: String, bagHexColor: String, viewController: UIViewController) {
        saveData(
            context: bagContext,
            data: BagSwiftDataModel(
                id: UUID().uuidString,
                bagHexColor: bagHexColor,
                bagTitle: bagName,
                bagType: bagType
            ),
            viewController: viewController
        )
    }

    func fetchBagList(onCompletion: @escaping ([BagSwiftDataModel]?, Error?) -> Void) {
        fetchData(context: bagContext, descriptor: FetchDescriptor<BagSwiftDataModel>(sortBy: [SortDescriptor<BagSwiftDataModel>(\.bagTitle)]), onCompletion: onCompletion)
    }

    func updateBag(bag: BagSwiftDataModel, with disc: DiscGolfDisc) {
        updateData(data: bag, updateBlock: { $0.discs.append(disc) }, context: bagContext)
    }

    func removeDiscFromBag(disc: DiscGolfDisc, from bag: BagSwiftDataModel) {
        if let index = bag.discs.firstIndex(where: { $0.id == disc.id }) {
            bag.discs.remove(at: index)
            updateData(data: bag, updateBlock: { _ in }, context: bagContext)
        }
    }

    func deleteBag(bag: BagSwiftDataModel) {
        deleteData(data: bag, context: bagContext)
    }

    // MARK: Generic Functions

    private func saveData<T: Storable>(context: ModelContext?, data: T, viewController: UIViewController) {
        guard let context = context else {
            print("ERROR SAVING DATA!")
            // Show an alert to the user
            let alert = UIAlertController(title: "Error", message: "An error occurred while saving the data.", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            viewController.present(alert, animated: true, completion: nil)
            return
        }

        context.insert(data)
    }

    private func fetchData<T: Storable>(
        context: ModelContext?,
        descriptor: FetchDescriptor<T>,
        onCompletion: @escaping ([T]?, Error?) -> Void
    ) {
        guard let context = context else {
            onCompletion(nil, NSError(domain: "", code: 0, userInfo: nil))
            return
        }

        do {
            let data = try context.fetch(descriptor)
            onCompletion(data, nil)
        } catch {
            onCompletion(nil, error)
        }
    }

    private func updateData<T: Storable>(data: T, updateBlock: (T) -> Void, context: ModelContext?) {
        guard let context = context else { return }
        updateBlock(data)
    }

    private func deleteData<T: Storable>(data: T, context: ModelContext?) {
        guard let context = context else { return }
        context.delete(data)
    }
}

2. 委托协议与操作视图(AddDiscToView)

import UIKit

protocol AddDiscToViewDelegate {
    func dissmiss()
    func saveDiscToCart()
    func saveDiscToBag()
}

class AddDiscToView: UIView {

    var delegate: AddDiscToViewDelegate?
    
    @IBAction func addToCartTapped(_ sender: Any) {
        //Send user to fillout form then pop to view
        delegate?.saveDiscToCart()
        delegate?.dissmiss()
    }
    
    
    @IBAction func addToBagTapped(_ sender: Any) {
        //Send user to fillout form then pop to view
        delegate?.saveDiscToBag()
        delegate?.dissmiss()
    }
    
    @IBAction func cancelButtonTapped(_ sender: Any) {
        print("Cancel Tapped")
        delegate?.dissmiss()
    }   
}

3. 触发保存的视图控制器(DiscViewController)

import UIKit

class DiscViewController: UIViewController {
    
    @IBOutlet weak var discNameLabel: UILabel!
    @IBOutlet weak var discImageView: UIImageView!
    @IBOutlet weak var speedLabel: UILabel!
    @IBOutlet weak var glideLabel: UILabel!
    @IBOutlet weak var turnLabel: UILabel!
    @IBOutlet weak var fadeLabel: UILabel!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var discView: UIView!
    @IBOutlet weak var mainView: UIView!    
    @IBOutlet weak var simularDiscsCollectionView: UICollectionView!
       
    var dropDownView: AddDiscToView!
    var disc: DiscGolfDisc?
    var similarDiscs: [DiscGolfDisc] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
   
        setupCollectionView()
        setupAddDiscToView()
        
        // Add a "Select All" button to the navigation bar
        let selectAllButton = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .plain, target: self, action: #selector(addDiscTapped))
        navigationItem.rightBarButtonItem = selectAllButton
    }
    
    private func setupAddDiscToView() {
        dropDownView = Bundle.main.loadNibNamed("AddDiscToView", owner: nil, options: nil)!.first as? AddDiscToView
        
        dropDownView.delegate = self
        dropDownView.translatesAutoresizingMaskIntoConstraints = false
        mainView.addSubview(dropDownView)
        dropDownView.isHidden = true
        
        dropDownView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 5).isActive = true
        dropDownView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 25).isActive = true
        dropDownView.widthAnchor.constraint(equalToConstant: 249).isActive = true
        dropDownView.heightAnchor.constraint(equalToConstant: 115).isActive = true
    }
    
    private func setupCollectionView() {
        simularDiscsCollectionView.dataSource = self
        simularDiscsCollectionView.delegate = self
        
        let flowLayout = UICollectionViewFlowLayout()
        
        // Set the scroll direction to horizontal
        flowLayout.scrollDirection = .horizontal
        
        // Calculate the item width based on your desired width
        let itemWidth: CGFloat = 200.0 // Adjust this value to your preferred width
        
        flowLayout.itemSize = CGSize(width: itemWidth, height: simularDiscsCollectionView.frame.height)
        flowLayout.minimumInteritemSpacing = 10.0 // Adjust spacing between items
        
        simularDiscsCollectionView.collectionViewLayout = flowLayout
    }
  
    
    @objc private func addDiscTapped() {
        dropDownView.isHidden.toggle()
    } 
}


extension DiscViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return similarDiscs.count
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell =  collectionView.dequeueReusableCell(withReuseIdentifier: "discCell", for: indexPath) as! DiscCell
        
        let disc = similarDiscs[indexPath.item]
        
        cell.similarDiscNameBottomLabel.text =  disc.name
        
        getImageFromLink(imageView: cell.discImageView, disc: disc)
        return cell
        
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        disc = similarDiscs[indexPath.item]
        
        DispatchQueue.main.async {
            self.createSimilarDiscsCollection(disc: self.disc)
            self.setupFlightPathAnimationView()
        }
    }
}

extension DiscViewController: AddDiscToViewDelegate {
    func saveDiscToBag() {
        
        BagDatabaseService.shared.fetchDiscBagList { [weak self] bags, error in
            guard let self else { return }
            if error == nil, bags == [] || bags == nil  {
                K.showAlert(title: "No Bags Created", message: "You need to add a bag before you can add a disc to your bags. Click the bag tab and tap the + to add your first bag.", presentingViewController: self)
            } else {
                let storyboard = UIStoryboard(name: "AddDiscToBag", bundle: nil)
                
                if let vc = storyboard.instantiateViewController(withIdentifier: "addDiscToBagVC") as? AddDiscToBagViewController, let disc = disc {
                    vc.disc = disc
                    dropDownView.isHidden = true
                    hidesBottomBarWhenPushed = true
                    navigationController?.pushViewController(vc, animated: true)
                    hidesBottomBarWhenPushed = false
                }
            }
        }
    }
    
    func saveDiscToCart() {
        
        guard let disc = disc else {
            K.showAlertWithRetryOption(title: "Issues Getting Disc Data", message: "We ran into a problem attempting to get the disc's data.", presentingViewController: self) { [weak self] _ in
                guard let self else { return }
                self.saveDiscToCart()
            }
            return
        }
        
        if let image = discImageView.image {
            if let imageData = image.pngData() {
                SwiftSpinner.show(delay: 3, title: "Saving Disc To Cart!", animated: true)
                DiscDatabaseService.shared.saveDisc(discName: disc.name, discImageData: imageData, discStability: disc.stability, discSpeed: disc.speed, discGlide: disc.glide, discTurn: disc.turn, discFade: disc.fade, discBrand: disc.brand, viewController: self)
                SwiftSpinner.hide()
            }
        } else {
            K.showAlert(title: "Issues Saving Disc", message: "We ran into a problem attempting to save the disc.", presentingViewController: self)
            SwiftSpinner.hide()
        }
    }
    
    
    func dissmiss() {
        dropDownView.isHidden = true
    }
}

内容的提问来源于stack exchange,提问作者KinneyKare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 13:52:02