Swift - Core Data Attribute EXC Bad Access - ios

I have a Swift app that I'm trying to use NSFetchedResultsController to populate four separate tableviews, which are in containers inside a main VC, using inheritance from a parent custom tableview controller class. I have a parent class with this method defined that all of my subclasses are inheriting:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
var task = fetchedResultsController.objectAtIndexPath(indexPath) as Task
if task.isKindOfClass(Task) == true {
cell.detailTextLabel?.text = task.name//EXC_BAD_ACCESS code=1
}
return cell
}
I commented the line where it is giving me the EXC_BAD_ACCESS compile error.
I know that the Task class object that inherits from NSManagedObject is successfully storing in that variable but it seems that whenever I try to access the name property (even println will cause the error), or any property on the object I get this compile error.
My hunch is that it has to do with four different tableviews trying to all access at once. I'm new to Core Data and not really sure.
EDIT
More code that might help with the problem:
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
var fetchedResultsController: NSFetchedResultsController = NSFetchedResultsController()
func getFetchedResultsController() -> NSFetchedResultsController {
fetchedResultsController = NSFetchedResultsController(fetchRequest: taskFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultsController
}
func taskFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Task")
let sortDescriptor = NSSortDescriptor(key: "priority", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController = getFetchedResultsController()
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
Screenshot of the entity inspector:
http://i.stack.imgur.com/Htyne.png

Got it working! A few things: my name property was nil because I didn't set a default value in the model inspector and needed a default value for good measure, but more importantly: my managedObjectContext wasn't unwrapped with ! in its declaration and so I didn't unwrap it on every reference to it and so sometimes I was getting the optional(managedObjectContext?) when I needed the unwrapped value. It was let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
but what it needed was let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!. Optional problems and not being aware enough of when to unwrap were my downfall. I also deleted my initial app.sqlite because I ran into an issue there after I changed the model assigning the default value to name.

Related

Swift - Unexpected rows added to CoreData

I have a CoreData base with 6 rows in it.
I na ViewController, the data is displayed in a UITable, when I select a row in the table, the didSelectRow
lists 6 rows. That are all the rows.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
caches = CoreData.getCaches()
print ("Amount \(caches.count)") // gives 6
performSegue(withIdentifier: "Select", sender: nil)
}
When the Segue is executed the prepareForSegue is executed. Now the same command results with the value 7.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
caches = CoreData.getCaches()
print ("Amount \(caches.count)") // gives 7
}
I suspect that something in the background is happening, but i can't find out what.
Below is the static method for reference:
static func getCaches() -> [Caches] {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var resultArray: [Caches] = []
let request = NSFetchRequest<Caches>(entityName: "Caches")
request.returnsObjectsAsFaults = false
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let sortDescriptors = [sortDescriptor]
request.sortDescriptors = sortDescriptors
do {
resultArray = try context.fetch(request)
} catch {
print("Error - \(error)")
}
return resultArray
}
After a lot of searching I found it.
I execute a performSegueWithIdentifier. Which calls the prepareForSegue in the calling ViewController. But apparently before that, the variables/properties from the called VC are created. (Which is logical if you give it some thought)
In the called VC, a variable was initialised with the following code
(copied from somewhere on the net)
var cache = Caches((context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext))
This line of ode was causing the trouble. Cause it creates an entity in the persistentContainer (not written to the actual CoreData). I replaced it with a plain old:
var cache = Caches()
And everything is working okay now. Thanks for the support.

NSFetchResultsController throws error during update two elements simultaneosly

I am trying two update elements in collection view. When I pass just one message it works nicely
func simulate() {
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
FriendsController.createMessageWithText(text: "Here's a text message that was sent a few minutes ago...", friend: friend!, minutesAgo: 1, context: context)
//second message
FriendsController.createMessageWithText(text: "Another message to give you hard time.", friend: friend!, minutesAgo: 1, context: context)
do {
try context.save()
}
catch let error as NSError {
print("Error \(error.localizedDescription)")
}
}
but when I do same steps except passing two messages it throws error:
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification.
Invalid update: invalid number of items in section 0. The number of
items contained in an existing section after the update (8) must be
equal to the number of items contained in that section before the
update (6), plus or minus the number of items inserted or deleted from
that section (1 inserted, 0 deleted) and plus or minus the number of
items moved into or out of that section (0 moved in, 0 moved out).
with userInfo (null)
Maybe someone have any clue?
There are: NSFetchController and delegate function I implemented:
lazy var fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<Message> in
let fetchRequest = NSFetchRequest<Message>(entityName: "Message")
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"date",ascending: true)]
fetchRequest.predicate = NSPredicate(format: "friend.name = %#", (self.friend?.name!)!)
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if type == .insert {
collectionView?.insertItems(at: [newIndexPath!])
collectionView?.scrollToItem(at: newIndexPath!, at: .bottom, animated: true)
}
}
If you want to insert(or delete, reload) more than one cell, than you need to make it in performBatchUpdates method of UICollectionView instance. You need to call it in func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>).
Look at this example: https://github.com/NoFearJoe/Timmee/blob/master/Timmee/Timmee/Sources/Core%20layer/Database/StorageObservable.swift
or:
https://gist.github.com/tempire/debbabb2cccafa90320e

How do you load attribute data from the entity into a single (1) UIViewController?

I'm new to coding with Swift, and I've been using the tutorial from Udemy by Aaron Caines, which has been great. The tutorial he did was using a UITableViewController.
I have an app that uses a single UIViewController (ONLY ONE VIEW and it's not a UITableViewController). I've already loaded CoreData into the build phases. I've been able to verify that the data is saved in the attributes, but for some reason, I can't load the data back into the two text boxes and one image view that I have in the view controller.
I've placed a couple of questions as comments within the code.
It should be as easy as setting up the variables:
#IBOutlet var textName: UITextField!
#IBOutlet var descriptionName: UITextField!
#IBOutlet var imageView: UIImageView!
calling the entity and getting the persistent container ready to load and receive data:
let pc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
fetching the data
var frc : NSFetchedResultsController = NSFetchedResultsController<NSFetchRequestResult>()
func fetchRequest() -> NSFetchRequest<NSFetchRequestResult> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NamedEntity")
// DO I NEED A SORTER IF I'M NOT USING A TABLEVIEW?
//let sorter = NSSortDescriptor(key: "accounttext", ascending: false)
//fetchRequest.sortDescriptors = [sorter]
return fetchRequest
}
func getFRC() -> NSFetchedResultsController<NSFetchRequestResult> {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: pc, sectionNameKeyPath: nil, cacheName: nil)
//OCCASIONALLY THERE'S AN ISSUE WITH THE sectionNameKeyPath.
//THE ERROR INVOLVES TRYING TO "UNWRAP A NIL VALUE".
//IS THERE ANOTHER VALUE I SHOULD BE CONSIDERING?
return frc
}
fetching the data whenever the view loads or appears:
override func viewDidLoad() {
super.viewDidLoad()
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
}
catch {
print(error)
return
}
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
self.tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
}
catch {
print(error)
return
}
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
self.tableView.reloadData()
}
and then loading it into the appropriate boxes:
//THIS IS WHERE THINGS GET STUCK
// HOW DO I CALL THE ATTRIBUTES OF MY ENTITY AND UPDATE MY VARIABLES IF I'M NOT USING A TABLEVIEW?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! cellAccountTableViewCell
let item = frc.object(at: indexPath) as! Entity
cell.nameText.text = item.accounttext
cell.descriptionText.text = item.amounttext
cell.imageView.image = UIImage(data: (item.image)! as Data)
return cell
}
I only have 2 text boxes and 1 image view. I've spent the last three days scouring dozens of useless forum topics and countless youtube videos for this answer, but it seems that everyone gives a tutorial on using a table view controller.
The most useful thing I've found was a video by Electronic Armory. This helped me understand the structure of the Entity(ies), attrubutes, and the persistentContainer. It also deals with the relational aspect the database.
https://www.youtube.com/watch?v=da6W7wDh0Dw
Can I use core data on ONE (1) single UIViewController, and if so, how do I call the data and load it into the appropriate fields? Let me know if there's any more info needed.
I'm really trying to understand the Core Data process. What am I missing, or what am I not understanding about the loading process? Any help would be appreciated!
Thanks,
Luke
I have an app that uses a single UIViewController (ONLY ONE VIEW and it's not a UITableViewController).
I only have 2 text boxes and 1 image view.
Assuming there's only one entity returned from the fetch (otherwise how are you going to show them all?), probably something like this:
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
//self.tableView.reloadData()
if let fetchResult = frc.fetchedObjects{
if let item = fetchResult.first as? Entity{
textName.text = item.accounttext
descriptionName.text = item.amounttext
imageView.image = UIImage(data: (item.image)! as Data)
}
}

Refresh Core Data in ViewController when Modal View (2nd view) is Dismissed - Swift

I'm trying to figure out how to reload my UIViewController after I dismiss a Modal View. What's happening is I segue from View 1 (my UIVIewController) to a Modal View where I make an update to Core Data. Upon completion, I save the Core Data and dismiss the Modal View, sending the user back to View 1 (the UIViewController). Problem is the UIViewController is not pulling the updated change to the Core Data (but instead is presenting the old information, because it has not been refreshed).
This was the closest answer I think that could work, but I'm having trouble translating from Objective-C to Swift.
Any ideas? Thanks in advance for the help.
Here is quick NSFetchedResultsController example
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
}
private lazy var fetchedResultsController: NSFetchedResultsController = {
let animalsFetchRequest = NSFetchRequest(entityName: "Animal")
let sortDescriptor = NSSortDescriptor(key: "classification.order", ascending: true)
animalsFetchRequest.sortDescriptors = [sortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: animalsFetchRequest,
managedObjectContext: self.context,
sectionNameKeyPath: nil,
cacheName: nil)
frc.delegate = self
return frc
}()
// delegate method
func controllerDidChangeContent(controller: NSFetchedResultsController) {
// update UI
}
My suggestion for this issue is to create delegate that will notify View 1.
For instance:
in presented view controller create delegate:
protocol NotifyReloadCoreData
func notifyDelegate()
end
create property of view controller:
var delegate: NotifyReloadCoreData?
when press save or something like that :
delegate.notifyDelegate()
in your View 1
class UIViewController1: UIViewController, NotifyReloadCoreData
and implement function
func notifyDelegate(){
// reload core data here
}

NSObjectInaccessibleException - Deleting all entity objects - NSFetchedResultsController

I have a function responsible for deleting all the items of an entity:
func removeItems() {
if let managedContext = managedObjectContext {
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Ent", inManagedObjectContext: managedContext)
fetchRequest.entity = entity
fetchRequest.includesPropertyValues = false
var error: NSError?
var results = managedContext.executeFetchRequest(fetchRequest, error: &error)
for result in results as [NSManagedObject] {
managedContext.deleteObject(result)
}
if !managedContext.save(&error) {
println("could not save \(error), \(error?.userInfo)")
}
}
}
My application consists of a TabBar with 3 screens:
The first tab presents a list of cities, and when one is selected, a segue is executed and goes to a product listing page, in which the user can "tag" products.
The second tab has a screen that shows the listing of these branded products, and also has a badge showing the amount of products
However, I need to delete all objects of this entity whenever the user selects a different city or when he starts the application after terminated.
For the first case, I delete all the objects in "prepareForSegue" function when the user selects a city, and it works perfectly.
The problem comes when I try to run the second case.
If I try to call the remove function in the "application didFinishLaunchingWithOptions" of the AppDelegate or "viewDidLoad" in the first tab, the bank is corrupted, and I get the following message when I try to enter in the second tab:
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd000000000140000 ''
But if I remove the function of "application didFinishLaunchingWithOptions" or "viewDidLoad" the first tab, the application works perfectly.
Looking more closely, the error is occurring in the second tab (the product listing).
I have a variable in which I use to keep up the items in the table (in the second tab):
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest()
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Entidade", inManagedObjectContext: managedContext)
fetchRequest.entity = entity
let sortDescriptor1 = NSSortDescriptor(key: "nome", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1]
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedContext,
sectionNameKeyPath: "nome",
cacheName: "Entidade")
fetchedResultsController.delegate = self
return fetchedResultsController
}()
And the error is occurring exactly this line of the second tab:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = editButtonItem()
var error: NSError?
if !fetchedResultsController.performFetch(&error) { // <----- HERE
fatalCoreDataError(error)
}
}
Would anyone have any suggestions of what I'm doing wrong?
The problem was entirely in the second tab.
The answer to the problem was to remove the variable:
lazy var fetchedResultsController: NSFetchedResultsController = {
.
.
.
}()
Now the "viewDidLoad" the second tab was as follows (the fetch was removed):
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = editButtonItem()
}
Was added the following variable:
var entities = [Entidade]()
And added the following methods:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
fetchLog()
}
And
func fetchLog() {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Entidade")
var error: NSError? = nil
if let results = managedContext.executeFetchRequest(request, error: &error) as? [Entidade] {
self.entities = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
With these changes, I can finally remove the objects when the application is started by placing the following code in the cities list screen:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.removeItens()
}
Or choose to call the "removeItems()" method in AppDelegate when the application starts or ends.
If anyone needs, I can post the entire source code of the screens.
Updated
I found out what really happened, I have a method in AppDelegate in which is responsible for updating the "badgeValue" tab of the list whenever a user marks a product.
He was as follows (and was called every time a change occurred in managedObjectContext):
func updateUI() {
let tabBarController = window!.rootViewController as UITabBarController
if let tabBarViewControllers = tabBarController.viewControllers {
let navigationController = tabBarViewControllers[3] as UINavigationController
let listViewController = navigationController.viewControllers[0] as ListViewController
listViewController.managedObjectContext = managedObjectContext // <--- Here's the problem
let fetchRequest = NSFetchRequest(entityName: "Entidade")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) {
navigationController.tabBarItem.badgeValue = String(fetchResults.count)
}
}
}
I can not set the managedObjectContext to a screen this way, I need to assign it only once in the "application didFinishLaunchingWithOptions", so I got to keep the old code to take advantage of NSFetchedResultsController

Resources