Core Data Delete issue with Swift - core-data

I am using the following IB Action to delete data from my Core Data and TableView:
//Events
#IBAction func btnDelTask_Click(sender: UIButton){
let appDel: foodforteethAppDelegate = UIApplication.sharedApplication().delegate as foodforteethAppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
/*
let request = NSFetchRequest(entityName: "Food")
myList = context.executeFetchRequest(request, error: nil)
*/
if let tv = tblTasks {
var bas: NSManagedObject!
for bas: AnyObject in myList
{
context.deleteObject(bas as NSManagedObject)
}
myList.removeAll(keepCapacity: false)
tv.reloadData()
}
}
So when I run this in the simulator and click the button to delete then everything seems fine. The table clears. I can go back and forth through the app to different parts of it and return to the table and everything is still clear.
HOWEVER, if I then restart the simulator (without resetting the content and settings), the rows in the table that were previously deleted, suddenly reappear :/
Why is this happening? I don't think it is actually deleting everything from the Core Data which I've found out by commenting out 'myList.removeAll(keepCapacity:false)' and then it doesn't work even normally and nothing deletes.
Any help?

Related

Swift 3 tableview.reloadData on main thread, not displaying cells properly

In a simple TableViewController i am calling ClouKit deleteRecord in a UIAlertController.
// 2. When the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (_) in
LSCloudKit.shared.deleteRecord(record: login.toCKRecord(), completion: { (success) in
let index = self.logins.index(of: login);
self.logins.remove(at: index!);
self.tableView.reloadData(); // <-problem is here. Data not displayed properly
});
}));
When i call self.tableView.reloadData(), the table seems to update but it display the wrong list, ie: twice the same cell, sometimes its the one that got deleted and sometimes its another one.
I checked that the call is being made on the main thread.
Here is the deleteRecord function too:
func deleteRecord(record: CKRecord!, completion: #escaping (Bool) -> ()) {
let container = CKContainer.default();
let privateDatabase = container.privateCloudDatabase;
privateDatabase.delete(withRecordID: record.recordID) { (recordID, error) in
DispatchQueue.main.async {
completion(error == nil)
}
}
}
If i put a breakpoint in xCode anywhere either inside DispatchQueue.main.async or inside the completion block of "deleteRecord", then everything appears fine on the device once i hit "continue".
The problem happens on simulator and real devices as well.
Please help.
Thank you.
EDIT: Adding screenshot
Here is the screenshot before and after deleting "user1" from the list, with the output:
self.logins before delete: [admin, master, user1, user2, user3]
self.logins after delete: [admin, master, user2, user3]
Before delete
After delete
I was able to work around the problem by using this instead of reloadData()
self.tableView.beginUpdates()
self.tableView.deleteRows(at: [IndexPath.init(row: index!, section: 0)], with: .automatic)
self.tableView.endUpdates()
I will not mark this as the right answer for a while since it is a work around and i still dont understand why reloadData() did not work. Its annoying me.

swift 3 tableview.reloadData crashes the app

i have got a table view which gets data from core data.
this works fine, but if i send the command tbl.reloadData it will chrashes my app.
this is my code for get data and reload it:
#IBOutlet weak var tbl: NSTableView!
func requestData() {
let appdelegate = NSApplication.shared().delegate as! AppDelegate
let context = appdelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Test")
do {
data = try context.fetch(request) as! [Data]
} catch { }
print(data)
tbl.reloadData()
}
print(data) shows me successfully all my data of core data.
but on the line "tbl.reloadData()" the app will crash with this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
2017-05-03 14:24:47.750618+0200 Programm[36302:1990301] fatal error: unexpectedly found nil while unwrapping an Optional value
Only thing that can be nil after that print statement is tbl object. Make sure:
you have connected the IBOutlet properly.
removed old connections from storyboard. The ones you created previously and now renamed the variable name or deleted the variable entirely.
Also for some reason your code has NSTableView make sure you are looking for that and not the one column style UITableView.

Exporting Core Data structure to .csv file attached to mail XCODE 8.1 Swift 3

I'm making an app that needs to take a managed object array from core data and export it to a csv file that I plan to attach to an email being sent out using the mfMailComposer system. I have the data properly stored in the core data systems and the mail composer functionality seems to be working. I'm reaching a snag when I try to find the correct process by which to export the data.
I have already taken a long look at both of these posts attempting to determine a solution:
from 2012, seems very outdated:
how to export Core Data to CSV
from 2016, more recent, but swift 3 and Xcode 8 have since been released and I worry this has become outdated as well: How to create a CSV file from Core Data (swift)
I have been attempting to try the solutions proposed in the second link, but much of the code gets marked as incorrect when typing it, so I believe it is now obsolete with the upgrade.
The code below is based off of the second post and therefore, likely outdated, but in order to provide a reference of the process I am trying to accomplish...
// Called by the press of xcode UI button
#IBAction func ExportToCSV(_ sender: AnyObject)
{
// Make our mail composer controller and fill it with the proper information
let mailComposeViewController = configuredMailComposeViewController()
// If the composer is functioning properly ...
if MFMailComposeViewController.canSendMail()
{
// ... Present the generated mail composer controller
self.present(mailComposeViewController, animated: true, completion: nil)
}
else
{
// ... Otherwise, show why it is not working properly
self.showSendMailErrorAlert()
}
}
// Used to set up the body of the outgoing email
func configuredMailComposeViewController() -> MFMailComposeViewController
{
// Establish the controller from scratch
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
// Set preset information included in the email
mailComposerVC.setSubject("Generic email subject")
mailComposerVC.setMessageBody("Generic email body", isHTML: false)
// Turn core data for responses into a .csv file
// Pull core data in
var CoreDataResultsList = [NSManagedObject]()
// Register the proper delegate and managed context
let appDelegate =
UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
// Pull the data from core data
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ItemResponses")
do {
let results =
try managedContext!.fetch(fetchRequest)
CoreDataResultsList = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
// Take the managed object array and turn it into a .csv sring to write in the file
let csvString = writeCoreObjectsToCSV(objects: CoreDataResultsList, named: "Generic name")
let data = csvString.dataUsingEncoding(NSUTF8StringEncoding)
mailComposerVC.addAttachmentData(data, mimeType: "text/csv", fileName: "GenericFilename.csv")
return mailComposerVC
}
// Takes a managed object and writes it to the .csv file ..?
func writeCoreObjectsToCSV(objects: [NSManagedObject], named: String) -> String
{
// Make sure we have some data to export
guard objects.count > 0 else
{
return ""
}
let firstObject = objects[0]
let attribs = Array(firstObject.entity.attributesByName.keys)
// The attires.reduce function is throwing an error about originally using combine as in the second post, used auto fix, but noteworthy.
//Now gives an error that says "No '+' candidates produce the expected contextual result type NSString"
let csvHeaderString = (attribs.reduce("", {($0 as String) + "," + $1 }) as NSString).substringFromIndex(1) + "\n"
// This function says that substring from index has been renamed as well as a few other lines within it
let csvArray = objects.map({object in
(attribs.map({((object.valueForKey($0) ?? "NIL") as AnyObject).description}).reduce("",combine: {$0 + "," + $1}) as NSString).substringFromIndex(1) + "\n"
})
// Again with the reduce issue
let csvString = csvArray.reduce("", combine: +)
return csvHeaderString + csvString
}
New the bottom of the code I have commented in the multiple errors with the suggested code from the second post and the issues pertaining after I use xCode's auto-fix feature.
I would like to thank you in advance for helping me with this issue. I am merely looking for the most up-to-date way to export core data as a .csv file and send it out. Thanks!
I ended up working around it. At first I didn't understand how .csv files were written until I saw the acronym stood for "comma-separated values". Then it all clicked for me and I wrote my own, I did a more manual route for the head entry but the data on the following lines is still auto mated.
Below is the relevant functions in their new working form:
// Called by the press of xcode UI button
#IBAction func ExportToCSV(_ sender: AnyObject)
{
// Make our mail composer controller and fill it with the proper information
let mailComposeViewController = configuredMailComposeViewController()
// If the composer is functioning properly ...
if MFMailComposeViewController.canSendMail()
{
// ... Present the generated mail composer controller
self.present(mailComposeViewController, animated: true, completion: nil)
}
else
{
// ... Otherwise, show why it is not working properly
self.showSendMailErrorAlert()
}
}
// Used to set up the body of the outgoing email
func configuredMailComposeViewController() -> MFMailComposeViewController
{
// Establish the controller from scratch
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
// Set preset information included in the email
mailComposerVC.setSubject("Generic Subject")
mailComposerVC.setMessageBody("Generic Email Body", isHTML: false)
// Turn core data for responses into a .csv file
// Pull core data in
var CoreDataResultsList = [NSManagedObject]()
// Register the proper delegate and managed context
let appDelegate =
UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
// Pull the data from core data
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ItemResponses")
do {
let results =
try managedContext!.fetch(fetchRequest)
CoreDataResultsList = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
// Take the managed object array and turn it into a .csv sring to write in the file
// In doing this, we are writing just like we would to any string
let csvString = writeCoreObjectsToCSV(objects: CoreDataResultsList)
let data = csvString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)
mailComposerVC.addAttachmentData(data!, mimeType: "text/csv", fileName: "GenericFilename.csv")
return mailComposerVC
}
// Takes a managed object and writes it to the .csv file ..?
func writeCoreObjectsToCSV(objects: [NSManagedObject]) -> NSMutableString
{
// Make sure we have some data to export
guard objects.count > 0 else
{
return ""
}
var mailString = NSMutableString()
mailString.append("Generic Header 1, Generic Header 2, Generic Header 3")
for object in objects
{
// Put "\n" at the beginning so you don't have an extra row at the end
mailString.append("\n\(object.value(forKey: "Generic Core Data Key 1")!),\(object.value(forKey: "Generic Core Data Key 2")!), \(object.value(forKey: "Generic Core Data Key 3")!)")
}
return mailString
}
I'm having an issue where one of my keys is a string containing commas and need a proper way to escape from them. I've heard double quotes is how to do it but inserting them gave me no success.
Regardless, this is one current way to take core data into an array and write it to a string, save it to a .csv file and mail it out. This answers my question, but my next task is reading the data back in. I have no idea how to access the file to do that on an iPad. If you come across this and know how to do that, please let me know! I will probably be making another post on that topic if I can't find a solution and will then drop a new question and a link to that in the replies below this answer.
Thank you!

Swift 3 Fetch Request Error (warning: could not load any Objective-C class information)

I recently received this error when fetching data from Core Data:
warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
(lldb)
Here is my code:
// MARK: - Initialize Fetch Request
var fetchedResultsController = NSFetchedResultsController<Profile>()
func setFetchRequest() -> NSFetchRequest<Profile> {
let request = Profile.fetchRequest()
let sortDescriptor = SortDescriptor(key: "title", ascending: false)
do {
try moc?.fetch(request)
} catch {
print("Error With Request: \(error)")
}
request.sortDescriptors = [sortDescriptor]
return setFetchRequest()
}
// MARK: - Retrieve Fetch Request
func getFetchRequest() -> NSFetchedResultsController<Profile> {
fetchedResultsController = NSFetchedResultsController(fetchRequest: setFetchRequest(), managedObjectContext: moc!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultsController
}
I crashed with this error where I have "try moc?.fetch(request)":
Thread 1 EXC_BAD_ACCESS (code=2, address=0x16fc07feo)
Are these errors connected or is this a bug in Swift 3 / Xcode 8?
You shouldn't take results from the ManagedObjectContext. If you want to use a NSFetchedResultsController class in your app? You'll need to access their methods. And all of the required or optional methods are comes from the NSFetchedResultsControllerDelegate protocol.
Try this
class YourTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController:NSFetchedResultsController<Profile>!
}
And then create a custom helper function like this one:
`func frc() {
let request:NSFetchRequest<Profile> = Profile.fetchRequest()
let sorter = SortDescriptor(key: "title", ascending: true)
request.sortDescriptors = [sorter]
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// make sure the delegate is set to self
self.fetchedResultsController.delegate = self
do {
try self.fetchedResultsController.performFetch()
} catch {}
}
`
From this point you'll need a trigger to perform operations. So let's the system itself should be doing this when you call the viewDidLoad method or you can create a button instead. For example click the button to begin operations.
override func viewDidLoad() {
super.viewDidLoad()
self.frc()
self.tableView.reloadData()
}
It should be works.
Good luck!
Automatic Subclass Generation
Xcode 8 and Swift 3 comes with a new generation of subclassing called as Automatic Subclass Generation! How to create it? Well! So let's create a new Xcode 8 project, choose a Single View Application and then another window will appears called Choose options for your new project:. Give the name for your new project, make sure language is a Swift and Use Core Data check box is checked and then hit Create.
Go to the YourProjectName.xcdatamodeld file and click it. And then add an entity! So let's say your entity name is a Profile and create their Attributes respectively. It's important to know, because this is an Automatic Subclass Generation. Choose your entity and go to the Data Model Inspector ! Choose a Class Definition for the Codegen You can find a Codegen from here.
After selected the Class Definition, you can see Name text field automatically filled by your entity name like so. Again go to the your entity and click it. Click Command + S for save changes firstly and then click Command + B for rebuild, that's it. Automatic Subclass Generation is successfully created.
Remember
If you want to change your model? For example: If you want to add a new Attribute to your model? It's very easy, select a xcdatamodeld file and click your entity. Click the plus sign under Attributes and add your new Attribute. After your changes is completed? Don't forget to save changes. Again click Command + S and then click Command + B
Create A Managed Object
In the Swift 3 you can create a ManagedObject by using subclass initializer. It's very easy to implementing than ever before
let managedObject = Profile(context: self.managedObjectContext!)
You can see it's very easy! How to save values to the managedObject ? So let's say you have a title attribute of your model. Also title is a String.
managedObject.setValue("Well crafted API? Right?", forKey: "title")
or
managedObject.title = "Well crafted API? Right?"
Save values:
do {
try self.managedObjectContext.save()
print(managedObject)
} catch {}
It's works well on the Swift 3.0 beta and Xcode 8.0 beta.
Update
So, this is what I got working for Xcode 8 beta and Swift 3 beta Core Data
var fetchedResultsControler = NSFetchedResultsController<Profile>()
func frc() {
let request: NSFetchRequest<Profile> = Profile.fetchRequest()
let sortDescriptor = SortDescriptor(key: "title", ascending: true)
request.sortDescriptors = [sortDescriptor]
self.fetchedResultsControler = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.moc!, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsControler.delegate = self
do {
try self.fetchedResultsControler.performFetch()
} catch {
print("Error Fetching Data: \(error)")
}
}
and in viewDidLoad I have self.frc() at the top of the viewDidLoad.
So, in my Profile+CoreDataProperties.swift I copied a method Apple uses in their Master-Detail example when you create a new project:
extension Profile {
#nonobjc class func fetchRequest() -> NSFetchRequest<Profile> {
return NSFetchRequest<Profile>(entityName: "Profile");
}
#NSManaged var title: String?
#NSManaged var titleImage: Data
}
so that my fetch request is "native to my function." Pretty sure that's not the correct way to say that but it's helping me understand what is going on. The fetch request in Apple's example is green instead of blue. And it took me forever to notice that. I clicked on "Event" in Apple's example, and was conveniently taken to the created subclass, which was demonstrated in the Xcode 8 video at WWDC.
The files for e.g. Event.swift and Event+CoreDataProperties.swift are not exposed like they are in Xcode 7.x.x and earlier. You have to click on the entity in the code and you'll be taken to them. Maybe that was my problem? Anyway, I'm fetching data and images like a champ. Thanks a lot for your help #Mannopson!

Swift Core Data Issue

I have a very simple UITableView that loads core data records using a NSFetchedResultsController. I have re-written the Objective-C code to Swift. I seem to have an issue when running the Swift code on an iOS 7 simulator but ok on iOS 8. As far as I know Swift is supposed to be backward compatible for iOS 7.
The error I get when running iOS 7 but not 8 is:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSFetchRequest could not locate an NSEntityDescription for entity name 'Category''
For some reason when executing the NSFetchRequest it doesn't like my core data entity Category on iOS 7 but fine on iOS 8, any ideas?
Relevant code:
class CategoriesListTableViewController : CoreDataTableViewController, AddEditCategoryTableViewControllerDelegate {
let kCategoryEntityID = "Category"
let kCategoryCellID = "Category Cell"
let kCategoryEntityParentAttributeID = "parent"
let kCategoryEntityNameAttributeID = "name"
let kCategoriesCacheID = "Categories"
let kAddCategorySegue = "Add Category"
let kEditCategorySegue = "Edit Category"
var moc = NSManagedObjectContext()
override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.sharedApplication().delegate as AppDelegate
moc = app.cdh().context
//debugcode
let mom = moc.persistentStoreCoordinator.managedObjectModel
let entities = mom.entitiesByName
let entityNames = entities.description
println("All loaded entities are: \(entityNames)")
self.setupFetchedResultsController()
}
func setupFetchedResultsController() // attaches an NSFetchRequest to this UITableViewController
{
let request = NSFetchRequest(entityName: kCategoryEntityID)
let sortParent = NSSortDescriptor(key: kCategoryEntityParentAttributeID, ascending: true)
let sortName = NSSortDescriptor(key: kCategoryEntityNameAttributeID, ascending: true)
let sortDescriptors = [sortParent, sortName]
request.sortDescriptors = sortDescriptors
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: kCategoryEntityParentAttributeID, cacheName: kCategoriesCacheID)
}
It seems that you might not be creating your managed object context correctly. The fetch request created with an entity name string is not "complete" until it becomes associated with the actual managed object context. The managed object context in turn associates it with the managed object model which contains the necessary entity descriptions.
In this case this should happen during the initialization of the fetched results controller. Check if the correct managed object context is loaded as expected when you create the FRC.
As your different results appear in iOS7 and 8 respectively, consider deleting the app completely from the simulator first. Also, try to clean the project before building again.

Resources