Delete and Update Data in Core Data in iOS

delete and Update Data in Core Data in iOS

you can delete data like :

 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"entityname" inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userID like %@",userID];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];

NSError *error;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *managedObject in items)
{
[context deleteObject:managedObject];
}

and for Update :

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"entityname" inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userID like %@",userID];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchLimit:1];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *arrResult = [context executeFetchRequest:fetchRequest error:&error];
YourEntityname *entity = arrResult[0];
entity.userID = @"2"
[appdelegate saveContext];

How to delete and update structure type array in Core Data iOS Swift?

Option 1.

Using a class and NSSecureCoding is the best way of doing this. The most flexible.

///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

Photo for Step 4

Sample Image

Code

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)
}
}

The below SwiftUI code works for both option 1 or option 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
}
}
}

Like I said at the start class is the safest way but you can use the struct.

Option 2

Just add an an attribute named lendingDataJSON of Type String? INSTEAD of the lendingData of type 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) ?? ""
}
}
}
}

All the View code will work the same with the class option or with the struct option

how to delete particular record in coredata using Swift?

Don't use a custom class. Use only the provided User class.

First of all declare a data source array (replacing displayDatasssss)

var users = [User]()

In the tap method load the data and insert new items in the Core Data stack. Consider that each tap on the button inserts duplicate items into the database. Older entries are not removed.

As User has only name and id properties email is assigned to id.

The items are appended to the data source array and saved in the context.

@IBAction func tap(_ sender: Any) {
let url = "http://jsonplaceholder.typicode.com/users")!
let task = session.dataTask(with: url){ [unowned self] (data, response,error)in
if let error = error { print(error); return }
do {
// Array of Data
let fetchData = try JSONSerialization.jsonObject(with: data!) as! [[String:Any]]
for eachDataItem in fetchData {
let name = eachdataitem["name"] as! String
let email = eachdataitem["email"] as! String
let newUser = User(context: self.context)
newUser.name = name
newUser.id = email
self.users.append(newUser)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
try self.context.save()
} catch{
print("Error 2", error)
}
}
task.resume()
}

In viewDidLoad fetch the data from CoreData and reload the table view

override func viewDidLoad() {
super.viewDidLoad()
do {
let request : NSFetchRequest<User> = User.fetchRequest()
users = try context.fetch(request)
tableView.reloadData()
} catch { print(error) }
}

In cellForRow assign the property value(s) to the labels, nothing else

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell1") as! TableViewCell1
let user = users[indexPath.row]
cell.label.text = user.name
return cell
}

The delete method is quite similar to yours

let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { [unowned self] (action, indexPath) in
print("Delete tapped")

// remove the deleted item from the model
let objectToDelete = self.users.remove(at: indexPath.row)
self.context.delete(objectToDelete)
do {
try self.context.save()
self.tableView.deleteRows(at: [indexPath], with: .automatic)
} catch {
print(error)
}
}
return [editAction, deleteAction]

Note: Print always errors, don't ignore them or print only meaningless literal strings

Swift delete core data on background thread not updating MOC

Ok, I’ve sussed it

Each time View controller B is closed, it calculates totals from the remaining (Secondary) records and writes the data back to the (Main) record on the ViewContext.

This was causing a merge conflict due to the backgroundContext and the ViewContext both trying to update the persistent store at the same time.

Update:
To get around this, I queued all context writes, so that only one write was being performed on the store at any given time

let persistentContainerQueue = OperationQueue()
persistentContainerQueue.maxConcurrentOperationCount = 1

func SaveBackgroundContext(backgroundContext: NSManagedObjectContext) {

//add the save operation to the back of the queue
persistentContainerQueue.addOperation(){

backgroundContext.performAndWait{

do {
//update core data
try backgroundContext.save()


} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
}
}


Related Topics



Leave a reply



Submit