Delete entity data from CoreData - ios

as in the title, I want to delete data from the CoreData when the user logs out from his account
What I wrote is this:
let moc = DataController().managedObjectContext
var context : NSManagedObjectContext?
var entityCurrentValue : NSEntityDescription?
var entityPreviousValue : NSEntityDescription?
var entityLastAccess : NSEntityDescription?
override func viewDidLoad() {
super.viewDidLoad()
loadCoreData()
}
}
func loadCoreData(){
context = moc
entityCurrentValue = NSEntityDescription.entityForName("CurrentValue", inManagedObjectContext: context!)
entityPreviousValue = NSEntityDescription.entityForName("PreviousValue", inManagedObjectContext: context!)
entityLastAccess = NSEntityDescription.entityForName("LastAccess", inManagedObjectContext: context!)
}
#IBAction func logoutButton_clicked(Sender: UIButton!) {
PFUser.logOut()
deleteElement(entityCurrentValue!)
deleteElement(entityPreviousValue!)
deleteElement(entityLastAccess!)
self.performSegueWithIdentifier("loginSegue", sender: Sender)
}
in this function i try to delete elements from the entity
func deleteElement(entity : NSEntityDescription){
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 50
var fetchResult = Array<AnyObject>()
do{
try fetchResult = (context?.executeFetchRequest(fetchRequest))!
}catch{
fatalError()
}
for entity in fetchResult as! [NSManagedObject] {
self.context?.deleteObject(entity)
}
}
The problem is that when i log out, and than I log in with another account, the app loads data that are already stored into the memory. Can someone help me? Thanks

A managed object context is only a virtual representation of the data on disk. So when you delete an object from a managed object context, it is not deleted from disk but simply marked as deleted inside the context.
In order to persist the changes of you context on disk, you need to save the context after performing the delete operations, which is what is missing from your code.

Related

Core Data - Fetch all entities using the same field

in my ios swift application I have a database using Core Data.
It has many entities, all entities have an integer field called syncStatus. it can be 0, 1, or 2.
On startup, I want to loop through ALL the entities that have syncStatus = 1 and change it to 0
Is there a way to do it without fetching each type alone and changing it?
So what I want is:
fetch ALL entities with syncStatus = 1
Loop through them and set syncStatus = 0
Currently I'm doing them one by one:
fetch UserEntities with syncStatus = 1
Loop through them and set syncStatus = 0
fetch countryEntities with syncStatus = 1
Loop through them and set syncStatus = 0
Do the same for every entity one by one
code:
let allUsers = context?.fetch(FetchRequest<UserEntity>().filtered(with: "syncStatus", equalTo: "1"))
let allCountries = context?.fetch(FetchRequest<CountryEntity>().filtered(with: "syncStatus", equalTo: "1"))
.
.
.
I'm just trying to find a generic approach, in case later we add another entity/table we don't have to come back to this code and add it here also.
First of all, fetching all entries and filter them is much more expensive than applying a predicate.
I recommend to use a protocol extension with static methods. The benefit is that you can call the methods directly on the type
protocol SyncStatusResettable
{
associatedtype Entity: NSManagedObject = Self
var syncStatus : String {get set}
static var entityName : String { get }
static func resetSyncStatus(in context: NSManagedObjectContext) throws
}
extension SyncStatusResettable where Entity == Self
{
static var entityName : String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
static func resetSyncStatus(in context: NSManagedObjectContext) throws
{
let request = NSFetchRequest<Entity>(entityName: entityName)
request.predicate = NSPredicate(format: "syncStatus == 1")
let items = try context.fetch(request)
for var item in items { item.syncStatus = "0" }
if context.hasChanges { try context.save() }
}
}
To use it, adopt SyncStatusResettable for all NSManagedObject subclasses and call
do {
try UserEntity.resetSyncStatus(in: managedObjectContext)
try CountryEntity.resetSyncStatus(in: managedObjectContext)
} catch { print(error) }
managedObjectContext is the current NSManagedObjectContext instance
NSManagedObjectModel allows you to enumerate through the entities it contains, and NSEntityDescription can give you properties for each entity. Once you have a reference to the model:
let entitiesWithSync = model.entities.filter {
$0.properties.contains(where: { $0.name == "syncStatus" })
}
Will give you all of the relevant entities. You can then use this list of entities to drive your updates - note that using NSBatchUpdateRequest is faster if you're doing this on startup. You can create batch update requests using the entity descriptions obtained in the loop above.
In the past I have looped through all the entitiesByName from the object model:
lazy var managedObjectModel: NSManagedObjectModel = {
let modelUrl = NSBundle.mainBundle().URLForResource("SomeProject", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelUrl)
}
func updateAllData() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
context.performAndWait {
let allEntities = self.managedObjectModel.entitiesByName
for (entity, items) in allEntities {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
...
}
}
}

Use Core Data to save a changing integer in swift

Hi just wondering if this is possible, i have a simple counter app where a counter variable is incremented by 1 every time a button is pushed. is it possible to save the counter when the user exits the app using core data? i know NSUserdefaults would work here but im exploring core data and was wondering if it could be used in cases like this
class ViewController: UIViewController {
var counter = Int()
#IBOutlet var label: UILabel!
#IBAction func button(sender: AnyObject) {
counter += 1
label.text = "\(counter)"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
NSManagedObject subclass Error
class IntegerEntity: NSManagedObject {
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?, value: Int) {
self.init(entity: entity, insertIntoManagedObjectContext: context)
self.value
// ERROR value of type IntegerEntity has no member value
}
}
ViewController.swift errors
import UIKit
import CoreData
class ViewController: UIViewController {
var counter = Int()
#IBOutlet var label: UILabel!
#IBAction func button(sender: AnyObject) {
counter += 1
label.text = "\(counter)"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
if integer != nil && !integer!.isEmpty {
(integer?.first! as! IntegerEntity).value = counter
// ERROR value of type IntegerEntity has no member value
} else {
let newInt = IntegerEntity(entity: entity, insertIntoManagedObjectContext: context, value: counter)
// ERROR Use of unresolved identifier context
dataContext.saveData()
}
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
if integer != nil && !integer!.isEmpty {
counter = (integer!.first! as! IntegerEntity).value
// ERROR value of type IntegerEntity has no member value
} else {
counter = 0
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NSManagedObjectContext {
func saveData() -> Bool {
do {
try self.save()
return true
} catch let error as NSError {
print(error)
return false;
}
}
}
This is possible.
First, create a Core Data model and add an entity called IntegerEntity. In the entity, add a property called value whose type is Integer 64. Now generate the NSManagedObject subclass.
In IntegerEntity,swift, add the following initializer:
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?, value: Int) {
self.init(entity: entity, insertIntoManagedObjectContext: context)
self.value = value
}
This is because later on, we can use this directly to create an entity with a value.
I suppose your app has only one view controller, so you have two options of when to save the data:
Use an NSTimer to save the data every 60 seconds or some other time interval
override viewDidDisappear in the view controller or applicationWillResignActive in the app delegate.
To save the data, you first need to get the saved integer back (if present).
// Just some boilerplate code here
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
// If some integer has already been saved...
if integer != nil && !integer.isEmpty {
(integer?.first! as! IntegerEntity).value = counter
} else {
// If no integer has been saved before...
let newInt = IntegerEntity(entity: entity, insertIntoManagedObjectContext: context, value: counter)
dataContext.saveData()
}
}
You may be wondering, what's saveData? I think my version of Xcode has a bug. Xcode always insists that save() method throws an error and returns Void. So I created this extension:
extension NSManagedObjectContext {
func saveData() -> Bool {
do {
try self.save()
return true
} catch let error as NSError {
print(error)
return false;
}
}
}
And BOOM! that's it!
"But how do I fetch the data back?" you asked. Actually, you did it before! Here's the code:
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
// If some integer has already been saved...
if integer != nil && !integer.isEmpty {
counter = (integer!.first! as! IntegerEntity).value.integerValue
} else {
// If no integer has been saved before...
counter = 0
}
}
Looks familiar? It's almost the same code as before!
WARNING
IMO, it's not worth it to write so much code just to save one single integer, just so you know. Use user defaults instead. If you want to explore Core Data, you can try saving lists of words in Core Data. And with the press of a button , AVSpeechSynthesizer read them all aloud.

Saving text from a UITextView using core data Swift

Completely new to iOS Development and Swift so I hope you'll bear with me. I'm creating a simple note app based off the apple Notes app.
I am also completely new to core data. What I'm trying to do is save the text from the UITextView. Strangely enough this works but only after I remove this piece of code,
if let detail: Note = self.detailItem {
if let textView = self.textView {
textView.text = detail.noteText
}
create a new note, then reintroduce the code...
func configureView() {
// Update the user interface for the detail item.
if let detail: Note = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.timeStamp.description
}
if let detail: Note = self.detailItem {
if let textView = self.textView {
textView.text = detail.noteText
}
}
}
For a note that has already been created, this code is able to save the text in the textView. If I try to create a new note and then open it, the code above will crash the app.
This error comes up: Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
I think I have made a silly mistake like not initializing my model "Note.swift" class.
I'm using the boilerplate code for a master detail app with core data. I also have two "insertNewObject" functions. One is in the MasterViewController and the other is in the DetailViewController.
DetailViewController.swift
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
Here's the full code including the fetchedResultsController.
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(self.textView.text, forKey: "noteText")
newManagedObject.setValue("I changed", forKey: "noteTitle")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
detailItem!.noteText = self.textView.text
detailItem!.noteTitle = "I changed"
var error: NSError? = nil
if !self.managedObjectContext!.save(&error) {
abort()
}
}
I was able to figure it out. The issue was that I didn't set a value for "noteText" when I first called func insertNewObject(sender: AnyObject). This resulted in a nil being set to textView.text and therefore crashing the app. Thanks for your help #BaseZen!

Update CoreData Object

I'd like to update a CoreData Object.
Backgrund: I made an app which includes a UITableView. In the textLabel of the UITableViewCell is a name. In the detailTextLabel of this cell is a date which can be changed/updated. Now I'd like to change this date.
I wrote the following code:
var people = [NSManagedObject]()
func saveDate(date: NSDate) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext:managedContext)
let person = people[dateIndexPath.row]
//3
person.setValue(date, forKey: "datum")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
tableView.reloadData()
}
Now, if I run this code:
The date is successfully updated but the cell in which the date has been updated is displayed 2 times. For example if I added 3 cells and changed the date in the 3rd cell, I now get 4 cells displayed and 2 of them have the same content/are duplicated.
Does someone knows how to solve this problem?
You're adding an additional object to your array each time. Your updated Person is already in the array and will display the new information when you reload your table data. To fix this, just take out this line:
people.append(person)
You're going to want to associate some kind of unique identifier attribute to your Person class. This allows to retrieve that same object later using it identifier. I would suggest using a UUID string value, called personID, identifier, or something similar.
You can override the awakeFromInsert method on your Person class like so:
// This is called when a new Person is inserted into a context
override func awakeFromInsert()
{
super.awakeFromInsert()
// Automatically assign a randomly-generated UUID
self.identifier = NSUUID().UUIDString
}
When you want to edit an existing Person, you want to retrieve it by the UUID. I suggest a class function like so (in the Person class):
class func personWithIdentifier(identifier: String, inContext context: NSManagedObjectContext) -> Person?
{
let fetchRequest = NSFetchRequest(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "identifier ==[c] %#", identifier)
fetchRequest.fetchLimit = 1 // Only want the first result
var error : NSError?
let results = context.executeFetchRequest(fetchRequest, error: &error) as [Person]
// Handle error here
return results.first?
}
This way you can use the following function:
let identifier = ...
let context = ...
var person = Person.personWithIdentifier(identifier, inContext: context)
if let person = person
{
// Edit the person
person.value = // change the values as you need
}
else
{
// Person does not exist!
person = // possibly create a person?
}

CoreData doesn't save data to database

I am programming an iOS app using Swift, following a tutorial on Youtube. The app will have the same function as a to-do-list-app, but another use. However, when I expect the data to be saved (and printed in the debugger) nothing happens. Have I done something wrong?
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista", inManagedObjectContext: contxt)
// Create instance of our data model and initialize
var nyTankning = Model(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
contxt.save(nil)
println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
}
You need to fetch the data first in order to display. Now you only save it. See my additions in your code after saving the data.
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista",inIntoManagedObjectContext: contxt)
// Create instance of our data model and initialize
// Use your NSManagedModel to initialize not only the Model Keyword!
var nyTankning = Tankningslista(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
if contxt.save(nil) {
// Fetch the Data from Core Data first....
let fetchRequest = NSfetchRequest(entityName: "Tankningslista")
var error:NSError?
var result = contxt.executeFetchRequest(fetchRequest, error: &error) as [Tankningslista]
for res in result {
// Now you can display the data
println(res.liter)
......
......
}
// End of the fetching
} else {
println(error)
}
//println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
I hope that solution will solve your problem.
Puuh, there is a lot to improve, I really suggest you take a "real" iOS/ swift course on a platform like Udemy/ Bitfountain or Udacity. First, you need to fetch the ManagedObject somewhere either in a function (then store the results in an array) or with NSFetchedResultController (which is mostly used for CoreData with TableViews) then I am not sure what you have in your numberOfRowsInSection function, here you should also have the correct return values. As I said, to fix that here is really too much...

Resources