如何在 Core Data iOS Swift 中删除和更新结构类型数组?

Posted

技术标签:

【中文标题】如何在 Core Data iOS Swift 中删除和更新结构类型数组?【英文标题】:How to delete and update structure type array in Core Data iOS Swift? 【发布时间】:2021-11-08 05:19:02 【问题描述】:

如何在 Core Data ios Sswift 中删除和更新结构类型数组?我正在像这样保存核心数据。我需要删除和更新包含值的选定单元格

let projectsInfo = NSEntityDescription.insertNewObject(forEntityName:"ItemsInfo", into: delegate.persistentContainer.viewContext) as! ItemsInfo
let auditArray:[String:[lendingData]] = ["allcreditData":SaveWitnessData.shared.LendingDataArray]
let jsonData = try! JSONEncoder().encode(auditArray) projectsInfo.values = jsonData
delegate.saveContext()

我的结构是这样的

struct lendingData : Codable 
    let userName : String
    let amount : String
    let date : String
    let type : String
    var witnessDetails : [witnessData]

【问题讨论】:

您必须创建一个安全的转换器并在数据模型检查器中设置转换器和自定义类 我该怎么做?你能多解释一点或展示一些示例吗? 那里有很多教程。这里是one SO question,所以你可以看到一个开始 This 有一张您将设置模型检查器的照片 我尝试了很多次,但保存数据时仍然出错 【参考方案1】:

选项 1。

使用classNSSecureCoding 是最好的方法。最灵活的。

///To See the whole thing in action you have to follow a few steps
///Step 1. Create an new SwiftUI project with CoreData
///Step 2. Copy all the code in Option 1 into a `.swift` file
///Step 3. Go to the `Persistence.swift` file
///         Place these 2 lines
///            `WitnessDataTransformer.register()`
///            `LendingDataTransformer.register()`
///         Just under `container = NSPersistentCloudKitContainer(name: "YourAppName")
///Step 4. Go to the CoreData model
///         Select the `Item` Entity
///         Add a `lendingData` attribute of type `Transformable`
///         Update the `Transformer` and `Custom Class` in the `Data Model Inspector` as shown
///Step 5. You should see the View on Canvas in this point

第 4 步的照片

代码

import SwiftUI
//struct and class should start with an uppercase
//You need secureCoding not codable
//You have to change to class because NSSecurecoding is not available for a struct -https://developer.apple.com/documentation/foundation/nssecurecoding
public class LendingData : NSObject, Identifiable, ObservableObject
    public let id: String
    @Published var userName : String
    @Published var amount : String
    @Published var date : String
    @Published var type : String
    //WitnessData needs to conform to secure coding as well
    @Published var witnessDetails : [WitnessData]

    static func sample() -> LendingData 
        LendingData(id: UUID().uuidString, userName: "sample name", amount: "10.00", date: "\(Date())", type: "sample type", witnessDetails: [WitnessData.sample(), WitnessData.sample()])
    
    static func blank() -> LendingData 
        LendingData(id: UUID().uuidString, userName: "", amount: "", date: "", type: "", witnessDetails: [])
    
    public enum CodingKeys: String, CodingKey 
        case id
        case userName
        case amount
        case date
        case type
        case witnessDetails
    
    public init(id: String, userName : String, amount : String, date : String, type : String, witnessDetails : [WitnessData]) 
        self.id = id
        self.userName = userName
        self.amount = amount
        self.date = date
        self.type = type
        self.witnessDetails = witnessDetails
    
    public required init?(coder: NSCoder) 
        id = coder.decodeObject(forKey: CodingKeys.id.rawValue) as! String
        userName = coder.decodeObject(forKey: CodingKeys.userName.rawValue) as! String
        amount = coder.decodeObject(forKey: CodingKeys.amount.rawValue) as! String
        date = coder.decodeObject(forKey: CodingKeys.date.rawValue) as! String
        type = coder.decodeObject(forKey: CodingKeys.type.rawValue) as! String
        witnessDetails = coder.decodeArrayOfObjects(ofClass: WitnessData.self, forKey: CodingKeys.witnessDetails.rawValue)  ?? []
    

extension LendingData: NSSecureCoding
    public static var supportsSecureCoding: Bool
        return true
    
    public func encode(with coder: NSCoder) 
        coder.encode(id, forKey: CodingKeys.id.rawValue)
        coder.encode(userName, forKey: CodingKeys.userName.rawValue)
        coder.encode(amount, forKey: CodingKeys.amount.rawValue)
        coder.encode(date, forKey: CodingKeys.date.rawValue)
        coder.encode(type, forKey: CodingKeys.type.rawValue)
        coder.encode(witnessDetails, forKey: CodingKeys.witnessDetails.rawValue)
    

///MUST CALL LendingDataTransformer.register() right after creating the Persistent Container before setup and loading store
@objc(LendingDataTransformer)
public final class LendingDataTransformer: NSSecureUnarchiveFromDataTransformer 
    public static let name = NSValueTransformerName(rawValue: String(describing: LendingDataTransformer.self))
    public override static var allowedTopLevelClasses: [AnyClass] 
        return [LendingData.self, NSString.self, NSArray.self, WitnessData.self]
    

    //Register before CoreData setup starts
    @objc dynamic
    public static func register() 
        let transformer = LendingDataTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    

//You have to change to class because NSSecurecoding is not available for a struct -https://developer.apple.com/documentation/foundation/nssecurecoding
public class WitnessData: NSObject, Identifiable, ObservableObject
    public let id: String
    //This is just a sample since you did not provide the struct
    //Add your variables to
    //    the class,
    //    the CodingKeys,
    //    init?(coder: NSCoder),
    //    encode(with coder: NSCoder), and
    //    init(id: String, name : String).
    //  Just follow the pattern.
    @Published var name: String

    static func sample() -> WitnessData
        WitnessData(id: UUID().uuidString, name: UUID().uuidString)
    
    static func blank() -> WitnessData
        WitnessData(id: UUID().uuidString, name: "")
    
    public enum CodingKeys: String, CodingKey 
        case id
        case name
    
    public init(id: String, name : String) 
        self.id = id
        self.name = name

    
    public required init?(coder: NSCoder) 
        id = coder.decodeObject(forKey: CodingKeys.id.rawValue) as? String ?? ""
        name = coder.decodeObject(forKey: CodingKeys.name.rawValue) as? String ?? ""
    

extension WitnessData: NSSecureCoding
    public static var supportsSecureCoding: Bool
        return true
    
    public func encode(with coder: NSCoder) 
        coder.encode(id, forKey: CodingKeys.id.rawValue)
        coder.encode(name, forKey: CodingKeys.name.rawValue)
    

///MUST CALL WitnessDataTransformer.register() right after creating the Persistent Container before setup and loading store
@objc(WitnessDataTransformer)
public final class WitnessDataTransformer: NSSecureUnarchiveFromDataTransformer 
    public static let name = NSValueTransformerName(rawValue: String(describing: WitnessDataTransformer.self))
    public override static var allowedTopLevelClasses: [AnyClass] 
        return [WitnessData.self, NSString.self, NSArray.self]
    
    //Register before CoreData setup starts
    @objc dynamic
    public static func register() 

        let transformer = WitnessDataTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    

以下 SwiftUI 代码适用于选项 1 或选项 2

///This is just a sample View
struct LendingDataView: View 
    //You will need the original ObservableObject if you want to be able to show changes
    //SwiftUI depends on being told that there are chagnes so it can reload Views
    @ObservedObject var item: Item
    var body: some View 
        if item.lendingData != nil
            List
                TextField("username",text: $item.lendingData.bound.userName)
                TextField("amount",text: $item.lendingData.bound.amount)
                TextField("date",text: $item.lendingData.bound.date)
                TextField("type",text: $item.lendingData.bound.type)
                Section(content: 
                    ForEach($item.lendingData.bound.witnessDetails, content:  $witness in
                        HStack
                            TextField("name",text: $witness.name)
                            Spacer()
                            //For deleting by object
                            Image(systemName: "trash")
                                .foregroundColor(.red)
                                .onTapGesture 
                                    let idx = item.lendingData!.witnessDetails.firstIndex(where: 
                                        $0.id == witness.id
                                    )
                                    if idx != nil
                                        item.lendingData!.witnessDetails.remove(at: idx!)
                                    
                                    //Because you are so far down the line you have to tell the ObservableObject there is a change
                                    //If you dont you won't see the new items until something happens to trigger a refresh
                                    //item.objectWillChange.send()
                                    item.objectWillChange.send()
                                
                        
                    )
                    //For deleting by index
                        .onDelete(perform:  indexSet in
                            for idx in indexSet
                                item.lendingData!.witnessDetails.remove(at: idx)
                            
                        )
                , header: 
                    HStack
                        Text("Witness Data")
                        Button(action: 
                            item.lendingData!.witnessDetails.append(WitnessData.blank())
                            //Because you are so far down the line you have to tell the ObservableObject there is a change
                            //If you dont you won't see the new items until something happens to trigger a refresh
                            item.objectWillChange.send()
                        , label: 
                            Image(systemName: "plus")
                        )
                    
                )
            
        else
            VStack
                Text("no lending data")
                Button(action: 
                    item.lendingData = LendingData.blank()
                , label: 
                    Image(systemName: "plus")
                )
            
        
    

//Standard Preview
struct LendingDataView_Previews: PreviewProvider 
    //Use the preview container
    static let context = PersistenceController.preview.container.viewContext
    static var sampleItem = Item(context: context)
    static var previews: some View 
        LendingDataView(item: sampleItem)
    


extension Optional where Wrapped == LendingData 
    var _bound: LendingData? 
        get 
            return self
        
        set 
            self = newValue
        
    
    var bound: LendingData 
        get 
            return _bound ?? LendingData.blank()
        
        set 
            _bound = newValue
        
    

就像我在开头所说的那样,class 是最安全的方式,但您可以使用 struct

选项 2

只需添加一个名为 lendingDataJSONlendingDataJSON 类型 String? INSTEAD 的 lendingData 类型 Transformable

struct LendingData : Codable, Identifiable
    let id: String
    var userName : String
    var amount : String
    var date : String
    var type : String
    var witnessDetails : [WitnessData]
    
    static func sample() -> LendingData 
        LendingData(id: UUID().uuidString, userName: "sample name", amount: "10.00", date: "\(Date())", type: "sample type", witnessDetails: [WitnessData.sample(), WitnessData.sample()])
    
    static func blank() -> LendingData 
        LendingData(id: UUID().uuidString, userName: "", amount: "", date: "", type: "", witnessDetails: [])
    

struct WitnessData: Codable, Identifiable
    let id: String
    var name: String
    static func sample() -> WitnessData
        WitnessData( id: UUID().uuidString, name: UUID().uuidString)
    
    static func blank() -> WitnessData
        WitnessData( id: UUID().uuidString, name: "")
    

//The App's CoreData Model will need an attibute
// named lendingDataJSON of Type String
extension Item
    //This computed property should be the only way that the app alters the LendingData
    //If you use the lendingDataJSON directly you can corrupt all of it
    var lendingData: LendingData?
        get
            let decoder = JSONDecoder()
            if let obj = try? decoder.decode(LendingData.self, from: self.lendingDataJSON?.data(using: .utf8) ?? Data()) 
                return obj
            else
                return nil
            
        
        set
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            if let encoded = try? encoder.encode(newValue) 
                self.lendingDataJSON = String(data: encoded, encoding: .utf8) ?? ""
            
        
    

所有View 代码都将与class 选项或struct 选项一起使用

【讨论】:

以上是关于如何在 Core Data iOS Swift 中删除和更新结构类型数组?的主要内容,如果未能解决你的问题,请参考以下文章

IOS - 如何在 Core Data Swift 中将数据插入到具有关系的不同表中

如何在 SWIFT 的 IOS CORE-DATA 请求中使用 SQL GROUP BY 和 SUM 函数?

Swift - 如何检查现有的 Core Data Store iOS

如何正确设置 Core Data Stack 到 iOS 和 Swift 中的第一个视图控制器?

Swift之深入解析如何结合Core Data和SwiftUI

Swift 3 Core Data - 如何同步保存上下文?