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
Related
Other people have asked a similar question, but the answers given did not help me. I am trying to create a table view with core data and I keep getting an error message that my fetchRequest and/or managedObjectContext are nil
class CustomTableViewController: UITableViewController,
NSFetchedResultsControllerDelegate {
var coreDataContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#available(iOS 10.0, *)
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Movie> = {
// Initiate the query
let fetchRequest: NSFetchRequest<Movie> = Movie.fetchRequest() as! NSFetchRequest<Movie>
// Sort the data
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"title", ascending: true)]
NSLog("Request: %#, Context: %#", fetchRequest, self.coreDataContext);
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setToolbarHidden(false, animated: false)
do {
// when view loads, run the fetch (query)
if #available(iOS 10.0, *) {
try self.fetchedResultsController.performFetch()
} else {
// Fallback on earlier versions
}
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError)")
}
}
Does anyone have any ideas what's wrong? Also, not sure if it matters, but my project is a mix of both Objective-C and Swift
Also, the NSLog("Request: %#, Context: %#", fetchRequest, self.coreDataContext) from my above code prints out:
Request: (null), Context: <NSManagedObjectContext: 0x1701db4e0>
I have an entity called Movie in my xcdatamodeld with three String attributes. I also created a class for Movie like this in its own Movie.swift file:
import UIKit
import CoreData
class Movie: NSManagedObject {
}
The problem is that your Movie class has no code for generating a fetch request.
#nonobjc public class func fetchRequest() -> NSFetchRequest<Movie> {
return NSFetchRequest<Movie>(entityName: "Movie");
}
The solution: Delete your Movie class file entirely.
In the data model, configure your Movie entity to do automatic code generation:
That will solve the problem. Xcode knows how to make the class files for an entity. So just permit it to do so, and all will be well.
My view controller is loading before Core Location data is available from the model.
I've a Master View controller that modally pushes a new view controller with two NSManagedObject subclasses - records and locations - which are instantiated in a prepareForSegue method.
if segue.identifier == "newRecord"
{
let controller = (segue.destinationViewController as! NewRecordVC)
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate // instantiate the delegate methods in AppDelegate
let managedContext = appDelegate!.managedObjectContext // create context from delegate methods
let recordEntity = NSEntityDescription.entityForName("RecordData", inManagedObjectContext: managedContext)
let locationEntity = NSEntityDescription.entityForName("Location", inManagedObjectContext: managedContext)
controller.location = Location(entity: locationEntity!, insertIntoManagedObjectContext: managedContext)
controller.record = Record(entity: recordEntity!, insertIntoManagedObjectContext: managedContext)
controller.managedContext = appDelegate?.managedObjectContext
print("segueing")
}
Both managed objects have inits for various values. Records have simple pre-assigned values assigned to properties that show up on the new view. Location properties, however, mainly require location services to assign property values. Printing property values shows that while a location is instantiated in viewDidLoad, the location properties assigned by location services are still nil. Location services are working - the properties print from within the model, BUT this is after viewDidLoad. On load, I need at least the geoPlacemark property, which provides text for the view.
override func viewDidLoad()
{
super.viewDidLoad()
...
if let recordNotNil = record
{
...
iD.text = "ID: \(recordNotNil.iD)"
}
if let locationNotNil = location
{
...
print("bing")
print(locationNotNil.temp)
record!.location = location!
print(location.geoPlacemark)
print(location.timestamp)
if let geoPlacemarkNotNil = location.geoPlacemark
{
print("bong")
locationText(geoPlacemarkNotNil)
}
}
}
Do I have to run the location services from within every view controller, not the model? Or is there a way to get the view to wait for the location delegate methods?
In prepareForSegue() the destinationViewController is already instantiated, and viewDidLoad() was already called.
If you want to set a label you should really do it in viewWillAppear()
override func viewWillAppear() {
super.viewWillAppear()
// Set up your views here
}
The other benefit of viewWillAppear() over viewDidLoad() is that it's called every time the ViewController is about to show up, which can be multiple times during the VCs lifecycle - as opposed to a single call ad the beginning of the VC's lifecycle of viewDidLoad().
The problem here came from using the normal override init method for the NSManagedObject.
class Record: NSManagedObject
{
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
{
super.init(entity: entity, insertIntoManagedObjectContext: context)
let randomBit = String(arc4random_uniform(100))
iD = NSDate().toIDTimeDateString() + "-" + randomBit + "OtR"
believers = 0
deviceTime = NSDate()
doubters = 0
eventTimeStarted = NSDate()
eventTimeEnded = NSDate()
featureImageIndex = 0
coreLocationUsed = 0
photoTaken = 0
tableSection = "My Records"
timeRecorded = deviceTime
validationScore = 0
}
}
As per the apple doc.s, custom NSManagedObjects should be initiated using awakeFromInsert or awakeFromFetch.
I am trying to create a favorites page. How can I let a user click on an image in one table view and then the data from the table view they clicked on is transferred to another tableview in another page?
#IBAction func favoritesSelected(sender: AnyObject)
{
if toggleState == 1
{
sender.setImage(UIImage(named:"Star Filled-32.png"),forState:UIControlState.Normal)
isFav = true
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var newFave = NSEntityDescription.insertNewObjectForEntityForName("Favorites", inManagedObjectContext: context) as NSManagedObject
newFave.setValue("" + nameLabel.text!, forKey: "favorite")
do
{
try context.save()
}
catch _
{
print("error")
}
//print("\(newFave)")
print("Object saved")
toggleState = 2
}
From the code above, you can see what happens when a user clicks on the favorites button. The image changes and it uploads the name to the core data.
I'm trying to get it to go to another table view cell class so that when it gets to the favorites page, the names that were favorited will already be there.
I will show what I have in that class but I'm sure it's wrong.
if (result == 2)
{
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: "Favorites")
request.returnsObjectsAsFaults = false
do
{
var results:NSArray = try context.executeFetchRequest(request)
if (results.count <= 0)
{
print("Either all object deleted or error")
}
}
catch _
{
print("error")
}
}
else
{
print("no show")
}
Option 1: NSNotification triggers tableView reload:
Register the UITableView tableView with the notification:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
func contextDidSave(sender: NSNotification) {
tableView.reloadData()
}
After the user clicks on the star in the first example and the context was saved properly, the contextDidSave callback will be executed and the tableView will load with the latest state in the DB
Option 2: Setup UITableView with NSFetchedResultsController
With this option, once the user clicks on the star and the context saves, iOS will trigger the update to selected cells automatically. See this article for reference:
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/
In my applications with a favourite function I have done the following:
1) On my model objects have a BOOL property called favourite, with a default value of false.
2) In the table view which lists all objects, when a user taps the favourite button, set the favourite property on the corresponding model object to true and save your managed object context.
3) In the table view in which you wish to display the favourited objects, query core data for all of your model objects with a favourite property that is true and display those results. As mentioned in Christopher Harris' answer, this is trivial if you are a using an NSFetchedResultController.
I have started my first core data application. I am working with one entity right now called 'Folder'.
The first view controller displays all the Folders in a tableview, which I can add to and it reloads the data. This works fine because It uses the fetch request to populate the table.
override func viewWillAppear(animated: Bool) {
var error: NSError?
let request = NSFetchRequest(entityName: "Folder")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
self.events = moc?.executeFetchRequest(request, error: &error) as! [Folder]
self.UITable.reloadData()
}
However when segueing to another view controller via the table cell I pass on the selected Folder data to the controller using the index path. e.g.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetails" {
let destinationVC = segue.destinationViewController as! FolderDetailsViewController
let indexPath = UITable.indexPathForSelectedRow()
let selectedFolder = folders[indexPath!.row]
destinationVC.selectedFolder = selectedFolder
}
}
My second view controller uses the data passed from the first table view to display in textfields:
var selectedFolder: Folder!
folderNameLabel.text = selectedFolder?.title
folderDetailsLabel.text = selectedFolder?.details
folderDateLabel.text = displayDate
I then have a modal to edit/save the folder data in a modal appearing from the second controller:
//Edit and save event
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
//Error
var error: NSError?
//Storing Data from fields
SelectedFolder!.title = FolderName.text
SelectedFolder!.details = FolderDetails.text
SelectedFolder!.date = FolderDate.date
context?.save(&error)
self.dismissViewControllerAnimated(true, completion: {});
When dismissing the modulate data is not updated, I have to go back to the first controller to reload the data and segue again.
I think this is because I have no NSFetchRequest (or NSFetchResultsController) to get the most recent changes.
What is the best method to reload the data of the selectedFolder when I make the changes in the modal ?
You can refresh your second view in viewWillAppera() if your modal view is presented in full screen.
override func viewWillAppear(animated: Bool) {
{
folderNameLabel.text = selectedFolder?.title
folderDetailsLabel.text = selectedFolder?.details
folderDateLabel.text = displayDate
}
It seems like you would want to call moc.refreshObject(folder, mergeChanges:true)
See the documentation here.
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.