如何在 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。
使用class
和NSSecureCoding
是最好的方法。最灵活的。
///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
只需添加一个名为 lendingDataJSON
的 lendingDataJSON
类型 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 中的第一个视图控制器?