coredata after switch to swift 2 - core-data

i optimize my code to swift 2
now my core data doesn't work correctly.
i think, that my entrie will save, but my tableview do not show any data.
can you find any mistake in the code below?:
#IBOutlet weak var Table: UITableView!
#IBOutlet weak var name: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lm = [LM_ITEMS]()
/*************** DATEN ABRUFEN ***************/
func DatenAbrufen() {
let fetchRequest = NSFetchRequest(entityName: "LM_ITEMS")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
try managedObjectContext.executeFetchRequest(fetchRequest) as? [LM_ITEMS]
} catch { print("Error")}
Table.reloadData()
}
override func viewWillAppear(animated: Bool) {
self.DatenAbrufen()
}
/*************** ANZAHL DER ZELLEN ERMITTELN ***************/
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lm.count
}
/*************** ZELLEN MIT INHALT FÜLLEN ***************/
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("lmCell") as! ModifyCells
let LM_ITEM = lm[indexPath.row]
cell.name.text = LM_ITEM.name
return cell
}
}

Yes, some pretty serious problems.
DatenAbrufen executes a fetch request but does not assign the result to anything. You call executeFetchRequest but you don't do anything with the result. So, the results are lost, and the fetch serves no purpose.
You declare lm as an array but you never put anything into the array. So it's empty, which means that tableView(_:numberOfRowsInSection:) will always return 0-- since lm.count will always be 0.
It looks like you probably intended to put the results of the fetch into lm.

You should move away from using arrays to populate your table view and embrace the NSFetchedResultsController API. Check out the Xcode templates. It is the best way to populate a Core Data backed UITableView.

Related

Deleting all data in a Core Data entity in Swift 3

Is there a way to do a batch delete of all data stored in all of the entities in core data?
I read somewhere that in iOS 9 or 10 that apple introduced a way to do batch deletes, but I can't seem to find any good information on it.
Ultimately, I just need a function that goes through an entity, and deletes all of the data in it. Seems like it should be simple enough, but documentation/tutorials for it have proven exceedingly difficult to find.
Any thoughts?
Edit
I added the following code into an IBAction attached to a button:
#IBAction func clearAllData(_ sender: AnyObject) {
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PLProjects")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
//get the data from core data
getPLData()
//reload the table view
tableView.reloadData()
}
This does not seem to work however. If I close down the project and reopen it, the data is still there. I am assuming this is also why the table view doesn't update, because the data is not actually being deleted.
You're thinking of NSBatchDeleteRequest, which was added in iOS 9. Create one like this:
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Employee")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
You can also add a predicate if you only wanted to delete instances that match the predicate. To run the request:
let result = try managedObjectContext.executeRequest(request)
Note that batch requests don't update any of your current app state. If you have managed objects in memory that would be affected by the delete, you need to stop using them immediately.
To flesh out Tom's reply, this is what I added to have a complete routine:
func deleteAllRecords() {
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "CurrentCourse")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try context.execute(deleteRequest)
try context.save()
} catch {
print ("There was an error")
}
}
Declare the Method for getting the Context in your CoreDataManager
Class
class func getContext() -> NSManagedObjectContext {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
}
if #available(iOS 10.0, *) {
return appDelegate.persistentContainer.viewContext
} else {
return appDelegate.managedObjectContext
}
}
Call the above method from your NSManagedObject subClass:
class func deleteAllRecords() {
//getting context from your Core Data Manager Class
let managedContext = CoreDataManager.getContext()
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Your entity name")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try managedContext.execute(deleteRequest)
try managedContext.save()
} catch {
print ("There is an error in deleting records")
}
}

NSFetchedResultsController and to-many relationship not working

Ok, I was searching and trying in that case for the last 1-2 weeks and I didn't get it work. I would be able to achieve what I want without NSFRC but for performance reasons and convienience I would like to do it with the NSFRC.
So, I have a DataModel with 2 Entities - see the picture
There is one Account and one account can have many accountchanges - which is quite obvious.
So I want to be able to choose an Account and then show all AccountChanges for that specific Account.
So far I was able to get the Account and also accessing the NSSet in cellForRow Function but I am not getting the correct sections and numberOfRowsInSection - this is the main issue.
Here is some code:
func numberOfSections(in tableView: UITableView) -> Int {
print("Sections : \(self.fetchedResultsController.sections?.count)")
if (self.fetchedResultsController.sections?.count)! <= 0 {
print("There are no objects in the core data - do something else !!!")
}
return self.fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Section Name")
print(self.fetchedResultsController.sections![section].name)
let sectionInfo = self.fetchedResultsController.sections![section]
print("Section: \(sectionInfo) - Sections Objects: \(sectionInfo.numberOfObjects)")
return sectionInfo.numberOfObjects
}
There are some print statements which are only for information!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = myTable.dequeueReusableCell(withIdentifier: "myCell")! as UITableViewCell
let accountBalanceChanges = self.fetchedResultsController.object(at: indexPath)
print("AccountBalanceChanges from cell....")
print(accountBalanceChanges)
let details = accountBalanceChanges.accountchanges! as NSSet
print("Print out the details:")
print(details)
let detailSet = details.allObjects
let detailSetItem = detailSet.count // Just for information!
let myPrint = detailSet[indexPath.row] as! AccountChanges
let myVal = myPrint.category
myCell.textLabel?.text = myVal
return myCell
}
So, I am able to get the data but always only one item and not the whole set - I guess due to the fact that the sections/ numberOfRows are wrong.
Here is my NSFRC
var fetchedResultsController: NSFetchedResultsController<Accounts> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Accounts> = Accounts.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "(ANY accountchanges.accounts = %#)", newAccount!)
fetchRequest.predicate = predicate
// 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.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
I am assuming it is the SortDescriptor or the predicate - or maybe both?
Any help or at least directions are well appreciated.
I already tried many different approaches but none was giving me the correct results.
I would do the opposite, I mean using the FRC to fetch all the changes for an account with a certain Id, and use the following predicate:
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
or
let predicate = NSPredicate(format: "accounts = %#", account.objectID)
I would rename Accounts entity to Account and same for the relationship since it's a to-one relationship.
That's assuming you have a table view with all the accounts and when you click on one it gives you back its changes.
var fetchedResultsController: NSFetchedResultsController<AccountChanges> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<AccountChanges> = AccountChanges.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
fetchRequest.predicate = predicate
// 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.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
Cheers

NSFetchRequest with NSPredicate returning correct results, but whose properties aren't updated

My managedObjectContext hierarchy is as follows: (PSC)<-(writerMOC -- private)<-(mainMOC -- main)<-(backgroundMOC -- private)
I have an NSManagedObject who "name" property is "Banana".
In the backgroundMOC, I get a reference to the object with backgroundMOC.objectWithID, change the NSManagedObject's "name" property to "Apple", and subsequently set it's "syncStatus" property to 1 (flagged for synchronization), then recursively save the moc's with the following routine:
func saveManagedContext(moc: NSManagedObjectContext, shouldSync: Bool = true, completion: (() -> Void)? = nil)
{
print("\nSaving managed object context...")
do {
try moc.save()
if let parentContext = moc.parentContext {
parentContext.performBlock {
self.saveManagedContext(parentContext, shouldSync: shouldSync, completion: completion)
}
}
else {
if shouldSync { SyncEngine.sharedInstance.synchronize(shouldPushUpdates: true) }
completion?()
}
print("Finished saving managed object context...")
} catch {
logger.error("\(error)")
}
}
Once the last moc is saved, a sync routine is called which does its work on the backgroundMOC, which queries the local store for all objects whose syncStatus is 1, again this fetch is called on the backgroundMOC.
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
This correctly returns the updated object in the array, however, that object's syncStatus property equals 0, and its "name" property is still set to "Banana".
This is really causing me headaches, I felt like i had totally understood how managedObjectContext blocks should work, but this has proven to be quite a puzzle.
UPDATE
Here's the code that prompts the update. This is called from the main thread when the cell is tapped.
func updateNameForCell(cell: UITableViewCell)
{
///gets the object id from the fetchedResultsController
guard let fruitMetaID = tableController.objectIDForCell(cell) else { return }
let backgroundMOC = CoreDataController.sharedInstance.newBackgroundManagedObjectContext()
backgroundMOC.performBlock {
do {
guard let fruit = (backgroundMOC.objectWithID(fruitMetaID) as? FruitMetaData)?.fruit else {
throw //Error
}
print(fruit.name) // "Banana"
fruit.name = "Apple"
fruit.needsSynchronization() //Sets syncStatus to 1
CoreDataController.sharedInstance.saveManagedContext(backgroundMOC)
}
catch {
//handle error
}
}
}
UPDATE AGAIN
Maybe I'm not creating the contexts right. Enlighten me please!
/// The parent to all other NSManagedObjectContexts. Responsible for writting to the store.
lazy var writerManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
}
return managedObjectContext
}()
lazy var mainManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.parentContext = self.writerManagedObjectContext
}
return managedObjectContext
}()
/// The context associated with background syncing..
func newBackgroundManagedObjectContext() -> NSManagedObjectContext
{
let backgroundManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundManagedObjectContext.performBlockAndWait {
backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext
}
return backgroundManagedObjectContext
}
Holding onto child MOCs (children of the main context) is fraught with issues. I would recommend creating a new child (aka backgroundMOC) for each operation that you do.
Without seeing all of your code this looks like an issue with the child context getting out of sync.
Update
Assuming that your creation of the backgroundMOC sets the mainMOC as its parent then I wonder about the -objectWithID: and what it is returning.
I also wonder about your -performBlock: calls. In my head the threading looks fine but better to test. Try changing to -performBlockAndWait: just to test and see if there is a threading race condition. Not a permanent change but eliminates that part of the code as a source of the issue.
Before fetchRequest is called, you should reset context.
backgroundMOC.reset() // add this line
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
The reason is FruitMetaData is an object(or class) so changing one of its properties/Core Data attributes does not register as a change to the results array ... the object references in the array remain the same.
And NSFetchRequest still returns the same result(by using cache). When use context.reset().This tells the context in the extension to fetch new data every time and ignore the cache.

Store Integers in Core Data using Swift and XCode

While Strings appears to be fine I'm having some trouble storing Integers into Core Data. Following tutorials and reading available information out there doesn't seem to be helping me who has no Objective-C background. (Swift seemed like a straight forward language like the languages I'm fluent with PHP/OOPHP/JavaScript/VBScript/... thus I started playing with it and so far have been able to do everything I wanted, almost)
What I want to do now is, to receive the JSON data and store it into Core Data
Here's my Core Data
Entity name: Category
Its Attributes:
id Int16
title String
description String
My Swift model? file: Category.swift
import CoreData
class Category: NSManagedObject {
#NSManaged var id: Int //should I declare this as Int16?
#NSManaged var title: String
#NSManaged var description: String
}
I'm using SwiftyJASON extension? and NSURLSession protocol? to get the data and to parse it as follow:
import UIKit
import CoreData
class ViewController: UIViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
var url = NSURL.URLWithString("http://domain.com/index.php?r=appsync/read&id=category")
var session = NSURLSession.sharedSession()
session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
// parse data into json
let json = JSONValue(data)
let entityDescription = NSEntityDescription.entityForName("Category", inManagedObjectContext: self.managedObjectContext)
let category = Category(entity: entityDescription, insertIntoManagedObjectContext: self.managedObjectContext)
for item in json.array! {
category.id = item["id"].string!.toInt()! //goes KABOOM!
category.title = item["title"].string!
category.description = item["description"].string!
managedObjectContext?.save(nil)
}
dispatch_async(dispatch_get_main_queue()) {
// do something
}
}).resume()
}
}
Let's assume the JASON data is:
[{"id":"1","title":"cat1","description":"blabala one"},{"id":"2","title":"cat2","description":"blabala two"}]
At line where it says category.id = item["id"].string!.toInt()! xCode goes KABOOM, what am I doing wrong here?
Notes/More questions:
I tried changing id type within Core Data to Int32 and then declaring it as just Int
in the model (and not Int16 or Int32) which reduced some errors but
xCode still crashes
Probably the way I'm looping stuff is not the best way to do this,
what's the better way of storing array of data into core data at
once?
Most of the tutorials I've seen there's no id's for Entities(tables), am I missing something here?
References:
SiftyJSON: https://github.com/lingoer/SwiftyJSON
Core Data tutorial:
http://rshankar.com/coredata-tutoiral-in-swift-using-nsfetchedresultcontroller/
EDIT > Working code:
Category.swift model file which can be auto generated using File>New>File>iOS>Core Data>NSManagedObject subclass [swift, no need for bridging header but you need to manually add #objc line as below]
import CoreData
#objc(Category) //Wouldn't work without this
class Category: NSManagedObject {
#NSManaged var id: NSNumber //has to be NSNumber
#NSManaged var title: String
#NSManaged var mydescription: String //"description" is reserved so is "class"
}
ViewController.swift
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
var url = NSURL.URLWithString("http://domain.com/index.php?r=appsync/read&id=category")
var session = NSURLSession.sharedSession()
session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
let json = JSONValue(data)
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext //this line had to be moved here
let entityDescription = NSEntityDescription.entityForName("Category", inManagedObjectContext: managedObjectContext)
for item in json.array! {
let category = Category(entity: entityDescription, insertIntoManagedObjectContext: managedObjectContext) //this line has to be in inside for loop
category.id = item["id"].string!.toInt()!
category.title = item["title"].string!
category.mydescription = item["description"].string!
managedObjectContext?.save(nil)
}
dispatch_async(dispatch_get_main_queue()) {
// do something
}
}).resume()
}
}
Sample fetching data code:
func requestData() {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Category")
request.returnsObjectsAsFaults = false
var results:NSArray = context.executeFetchRequest(request, error: nil)
//println(results)
for category in results {
var cat = category as Category
println("\(cat.id),\(cat.title),\(cat.mydescription)")
}
}
P.S. Make sure to Clean your project and delete the app from simulator after changing Model
Scalar types (integers, floats, booleans) in core data are broken in the current Swift beta (5). Use NSNumber for the properties, and file a radar.
object.intProperty = NSNumber(int:Int(item["id"] as String))
(Typed on the phone, so sorry if that's wrong, and I know it's disgusting code - hence, file a radar!)
Or, in your specific case, looking at the JSON, use String. Those IDs are coming in as strings anyway.
Updated for Swift 2
If your JSON data is really of type [[String:String]], you can use the following code in order to set category.id:
if let unwrappedString = item["id"], unwrappedInt = Int(unwrappedString) {
category.id = unwrappedInt
}

Can't Unwrap Optional.None when using CoreData in Swift

Ok so the problem actually occurs once the code bit var context: NSManagedObjectContext = appDel.managedObjectContext is run I commented it out to confirm that it was that line and it was please note this is my first time learning iOS programming so please try to be as specific as possible in your answer thank you :)
import UIKit
import CoreData
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var txtName : UITextField
#IBOutlet var txtDesc : UITextField
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
self.view.endEditing(true)
}
#IBAction func hitAdd(sender : UIButton) {
glTask.newTask(txtName.text, desc: txtDesc.text)
txtName.text = ""
txtDesc.text = ""
self.view.endEditing(true)
self.tabBarController.selectedIndex = 0
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
Right here V
var context: NSManagedObjectContext = appDel.managedObjectContext
This crashes the app once button is pressed ^
The code error message is fatal error Cant unwrap Optional.None
var newTask = NSEntityDescription.insertNewObjectForEntityForName("Tasks", inManagedObjectContext: context) as NSManagedObject
newTask.setValue("test task", forKey: "myTask")
newTask.setValue("test Description", forKey: "myDesc")
context.save(nil)
//println(newTask)
println("Task was saved.")
}
// UITextField Delegate
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
}
Looking at the Core Data stack in Swift, managedObjectContext is implemented like this:
var managedObjectContext: NSManagedObjectContext {
if !_managedObjectContext {
let coordinator = self.persistentStoreCoordinator
if coordinator != nil {
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
}
}
return _managedObjectContext!
}
var _managedObjectContext: NSManagedObjectContext? = nil
As you can see it is backed by an Optional.
The place where this can go wrong is here:
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
if NSManagedObjectContext() returns a nil, then the backing _managedObjectContext will be nil and you will get this crash at the line where you unwrap it return _managedObjectContext!
To debug this, dig deeper down the stack, its most likely failing to initialize the object model or persistant store, and thus returning nil to you.
Edit:
In the definiton of the getter for var persistentStoreCoordinator: NSPersistentStoreCoordinator
They provide a location (the wall of comments) where you should debug this exact type of issue.
Not sure if OP ever figured this out, but I had a similar issue and realized that the code I copied from another app's AppDelegate was using the project name of that app and that I had forgot to change this line: let modelURL = NSBundle.mainBundle().URLForResource("CoreData", withExtension: "momd") to use "CoreData" instead of the "test" it had from another project.

Resources