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.
Related
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.
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!
I have a Swift app that uses CoreData. I created List entity with class MyAppTarget.List. Everything is configured properly in .xcdatamodeld file. In order to fetch my entities from persistent store, I am using NSFetchedResultsController:
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName("List", inManagedObjectContext: managedObjectContext)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "ListFetchedResultsControllerCache")
and it works like expected, returning array of MyAppTarget.List objects when fetching.
However, I would like to use it inside another target, for unit testing. I added List class to MyUnitTestTarget, so I can access it inside the unit test target. The problem is that the fetched results controller returns MyAppTarget.List objects, not the MyUnitTestTarget.List objects. In order to make the List entity testable, I have to make it public along with all methods that I need to use and I would like to avoid this.
I tried to change the managedObjectClassName property on NSEntityDescription:
fetchRequest.entity.managedObjectClassName = "MyUnitTestTarget.List"
but it generates exception:
failed: caught "NSInternalInconsistencyException", "Can't modify an immutable model."
The documentation states that
[...] once a description is used (when the managed object model to which it belongs is associated with a persistent store coordinator), it must not (indeed cannot) be changed. [...] If you need to modify a model that is in use, create a copy, modify the copy, and then discard the objects with the old model.
Unfortunately, I don't know how to implement this flow. I wonder if there is a way to change the managed object class name in runtime, before fetching the entities with NSFetchedResultsController?
It occurs that solution to my problem was pretty simple. In order to make it working, I had to create a copy of managedObjectModel, edit its entities and create NSPersistentStoreCoordinator with the new model. Changing the managedObjectClassName property on NSEntityDescription instance is possible only before model to which it belongs is associated with NSPersistentStoreCoordinator.
let testManagedObjectModel = managedObjectModel.copy() as NSManagedObjectModel
for entity in testManagedObjectModel.entities as [NSEntityDescription] {
if entity.name == "List" {
entity.managedObjectClassName = "CheckListsTests.List"
}
}
This also solves my other problem with unit testing CoreData model entities in Swift.
You can dynamically alter the class name of the NSManagedObject subclass with something like:
let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!
// Check if it is within the test environment
let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
let isTestEnvironment = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
// Create the module name based on product name
let productName:String = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as! String
let moduleName = (isTestEnvironment) ? productName + "Tests" : productName
let newManagedObjectModel:NSManagedObjectModel = managedObjectModel.copy() as! NSManagedObjectModel
for entity in newManagedObjectModel.entities as! [NSEntityDescription] {
entity.managedObjectClassName = "\(moduleName).\(entity.name!)"
}
i know that this has been asked a couple of times but the usual solution does not seem to work for me. I created a CoreData entity and a subclass for it using <ProjectName>.<SubclassName> syntax as class name. Creating a new object like this:
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as User
println("-->\(object)<--")
object.setValue(12, forKey: "userID")
object.setValue("username", forKey: "username")
the console output:
although the object does not get printed in the console it seems to have been in some way created and setting a value on that object refers to a Core Data Object
when i use it without a subclass it works as expected (setting Class name back to default):
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as NSManagedObject
println("-->\(object)<--")
output:
Here's my subclass declaration:
import Foundation
import CoreData
class User: NSManagedObject {
#NSManaged var userID: NSNumber
#NSManaged var username: String
}
and here's the core data model form:
What's wrong with the code? Or do i miss anything?
For printing out the value of an NSManagedObject, do not use:
println(...)
But rather use:
NSLog(...)
Please find an example of using NSLog and an NSManagedObject below:
/**
* Called when the user clicks on the save button.
*/
#IBAction func saveTapped(sender: AnyObject) {
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference MOC
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("List", inManagedObjectContext: context)
// Create instance of put data model and initialize
var newItem: List = List(entity: en!, insertIntoManagedObjectContext: context)
// Map our properties
newItem.item = textFieldItemName.text
newItem.quantity = textFieldQt.text
newItem.info = textFieldMoreInfo.text
// Save our context
var error: NSError? = nil;
if (context.hasChanges) {
if (!context.save(&error)) { // save failed
println("Save Failed: \(error!.localizedDescription)")
} else {
println("Save Succeeded")
}
}
NSLog("newItem: %#", newItem)
// Navigate back to root ViewController
self.navigationController?.popToRootViewControllerAnimated(true)
}
Note: I do not know the exact reason (bug, or implementation maybe ...) but it turns out that it does not print out a value when we use println(...) function, instead of that it returns an empty String.
Consequently I recommend to all of you guys to use NSLog(...) function instead of println(...) when you want to print out the value of an NSManagedObject.
If you print the expression CoreDataManager.sharedInstance.managedObjectContext twice, do you get a different pointer each time?
It sounds like the managed object context is getting deallocated right after you use it, which indicates that your CoreDataManager.sharedInstance.managedObjectContext property is returning a new managed object context every time, not the same one, or your sharedInstance property is returning a new instance every time.
A managed object's in-memory state is stored as a weak reference to a managed object context. When the context drops out from underneath you (e.g. it's no longer referenced and so is deallocated), your managed object's storage disappears.
A few good indicators that this is happening:
You haven't saved your managed object context yet but a newly created managed object prints as fault
You get weird errors when you try to set a property
You get weird errors when you try to retrieve a property you just set
Include the following at the top of your .swift file
import CoreData
The reason for not working is it doesn't know which library to reference.
Hope it helps
try creating it with this method :
let entityDescripition = NSEntityDescription.entityForName(“User”, inManagedObjectContext: managedObjectContext)
let user = User(entity: entityDescripition, insertIntoManagedObjectContext: managedObjectContext)
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?