Why is my fetchRequest returning nil? - swift

My fetch request is returning nil from an entity that I think I've successfully put data into and after hours and hours of debugging, I haven't been able to figure it out. (Disclaimer: I've looked at the countless threads here on SO that talk about fetch requests and unwrapping options but every one I found is about how to deal with the unwrapping error. I'm asking for help figuring out why my fetch request isn't returning a record when I believe it should.)
In the view controller where I'm going to display the data, I have this in viewWillAppear:
class LiftLogTableViewController: UITableViewController {
var managedObjectContext: NSManagedObjectContext!
var liftEvents = [LiftEvent]()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let fetchRequest = NSFetchRequest(entityName: "LiftEvent")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [LiftEvent] {
liftEvents = results <---- nil error happening here
}
} catch {
fatalError("Error fetching data!")
}
}
But I believe there's a record in there because in AppDelegate.addData() I've done this:
// create a lift event in LiftEvent
let liftEvent = LiftEvent(entity: liftEventEntity, insertIntoManagedObjectContext: managedObjectContext)
let dateStr = "05-27-2016"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
let date: NSDate = dateFormatter.dateFromString(dateStr)!
liftEvent.date = date
liftEvent.liftEventUid = 1
liftEvent.liftUid = 1
liftEvent.formulaUid = 1
liftEvent.maxAmount = 250
print("Added lift ID: \(liftEvent.liftEventUid) lift ID: \(liftEvent.liftUid) weight: \(liftEvent.maxAmount) formula: \(liftEvent.formulaUid) ")
saveContext()
}
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() 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
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
I'm learning CoreData so at this point I have to assume there's something basic I'm just missing.
Thanks in advance.

Assuming you're using the standard Core Data template you need to assign a value to the managedObjectContext property in the LiftLogTableViewController class.
For example in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
managedObjectContext = appDelegate.managedObjectContext
}
However the recommended way is to pass the context via the segue.

Related

Why is this Label not showing the correct value? Swift

I wrote a code to take data from my CoreDate Entity to show the highest Integer as the value at a Highscore label. I don't understand why it is not working? I tried it with or without a extra function...
func loadHighscore() {
//Kontext identifizieren
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
//Anfrage stellen
let context = appDelegate.persistentContainer.viewContext
let entityName = "PushUps"
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
do {
let results = try context.fetch(request)
for result in results {
guard let count = (result as! NSManagedObject).value(forKey: "highScore") as? Int16 else {
return
}
}
if count > highScore {
highscoreLabel.text = "Highscore: \(count)"
highScoreChanged(newHighScore: Int16(count))
// Console statement:
print("New Highscore: \(count)")
}
} catch {
print("error")
}
}
func highScoreChanged(newHighScore: Int16) {
highscoreLabel.text = "Highscore: \(newHighScore)"
}
}
Your approach is a bit strange.
A better approach is to load the data sorted descending by highScore so the first item is the item with the highest value.
It's highly recommended to take advantage of the generic Core Data types and to use dot notation rather the KVC value(forKey
func loadHighscore() {
//Kontext identifizieren
// delegate can be forced unwrapped. The app doesn't even launch if AppDelegate doesn't exist
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//Anfrage stellen
let context = appDelegate.persistentContainer.viewContext
let entityName = "PushUps"
// Use a specific fetch request
let request = NSFetchRequest<PushUps>(entityName: entityName)
// add a sort descriptor to sort the items by highScore descending
request.sortDescriptors = [NSSortDescriptor(key: "highScore", ascending: false)]
do {
// results is an array of PushUps instances, no type cast needed
let results = try context.fetch(request)
if let result = results.first, result.highScore > highScore {
highScore = result.highScore
print("New Highscore: \(highScore)")
}
} catch {
print(error)
}
highscoreLabel.text = "Highscore: \(highScore)"
}
The function highScoreChanged is not needed either. If the saved highscore is higher than the current value (property highScore) the property is updated and at the end of the method the text field is updated with the value of the property highScore.
Be sure to execute the label update in main queue. In other way it may not be done.

How to set an entities attribute via relationship?

I have the following entities and relationship
I want to be able to set an exercise to have a nil result for its routine name relationship, if that makes sense? so that it can later be set as a routine name when the routine entity is formed.
My question is, how do you set this sort of attribute up? I am trying the following code but it causes a fatal crash:
userExercise.usersroutine?.name = nil
My logic being that i take the exercise and follow the relationship to the name property and set it to nil?
Thanks for any correction and clarification on my logic
EDIT: Added my existing exercise and routine save functions
func createExercise() {
guard let managedObjectContext = managedObjectContext else { return }
if let userExercise = userExercise {
userExercise.name = userExerciseName.text
userExercise.sets = Int64(userSetsCount)
userExercise.reps = Int64(userRepsCount)
userExercise.weight = Double(self.userExerciseWeight.text!)!
userExercise.id = UUID().uuidString
userExercise.routine = nil
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Routine Creation:
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Current Fetch Request:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExercise> = {
let fetchRequest: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
Please check the implementation below I'have created some exercises and routines. Also read comments in code, this will help you figure out how to go about it.
Function to add a new exercise
func createExercise(weight: Int16, respetitions: Int16, name: String, routine: Routine?)->Exercise? {
let context = getMainContext()
let exercise = NSEntityDescription.insertNewObject(forEntityName: "Exercise", into: context) as! Exercise
exercise.setValue(weight, forKey: "weight")
exercise.setValue(name, forKey: "name")
exercise.setValue(respetitions, forKey: "rep")
do {
try context.save()
return exercise
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to add a new routine
func createRoutine(name: String, exercises:[Exercise]) {
let context = getMainContext()
let routine = NSEntityDescription.insertNewObject(forEntityName: "Routine", into: context) as! Routine
routine.name = name
//Iterate over Exercise objects & check if routine is nil.
//Here if routine is not nil it menas your exercise is already assigned to a routine.
//If routine is nil assign routine.addToRelationship(<#T##value: Exercise##Exercise#>) and Also assign routine to the execise.
do {
try context.save()
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to get main NSManagedObjectContext on which we can perform core-data actions
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
Below, First I create few exercises without any relationship to routines
"The routine doesnt exist when the exercises are created, it is created afterward and its name is set"
and then create routines by passing some exercises (You can refer to other answer on how to fetch exercises with routine as nil values)
func initializer() {
//I'm adding exercises first without routines
let ex1 = self.createExercise(weight: 10, respetitions: 4, name: "Exercise1", routine: nil)
let ex2 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
let ex3 = self.createExercise(weight: 20, respetitions: 2, name: "Exercise3", routine: nil)
let ex4 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
self.createRoutine(name: "Routine 1", exercises: [ex1!, ex2!]) //You can pass all the exercises or use fetch request to query exercises with routine as nil
self.createRoutine(name: "Routine 2", exercises: [ex3!, ex4!])
self.createRoutine(name: "Routine 3", exercises: [ex1!, ex2!]) //This routine shall not be adding any execises as they are already added to othe routines
}
Updating create routine Function to query results of UserExercise which has usersroutine as nil
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
//Getting nil value User Exercises
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine == nil")
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let queryResults = try context.fetch(request)
//I like to check the size of the returned results!
print ("num of results = \(queryResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for exercise in queryResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("Exercise NAME: \(exercise.value(forKey: "name"))")
}
} catch {
print("Error with request: \(error)")
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
It doesn't seem that you should need to use the name attribute at all. This attribute should be used for storing the actual name of the UserRoutine and not for anything relationship based.
The relationships between entities in Core Data don't rely on a particular attribute of an entity, but between the entities themselves.
"I want the routine builder to look at the exercises and import all the exercises with nil in the relationship into it"
So...
Create a fetch request to fetch all the entities of UserExercise that don't have a related UserRoutine (i.e. where userroutine is nil).
let orphanedExerciseFetchRequest = NSFetchRequest(entityName: "UserExercises")
orphanedExerciseFetchRequest.predicate = NSPredicate(format: "userroutine == nil)
Execute this fetch request to get an array of UserExercises (with no related routine)
let orphanedExercises = managedObjectContext.executeFetchRequest(orphanedExerciseFetchRequest())
"creating a routine with attributed exercises"
Set the fetched UserExercise entitiy's property userRoutine to your routine (and don't forget to save the changes in your managed object context).
myRoutine.userexercises = orphanedExercises
Later, if you want to get the exercises for a particular routine:
let fetchRequest = NSFetchRequest(entityName: "UserExercises")
fetchRequest.predicate = NSPredicate(format: "userroutine == %#", someUserRoutine)

Swift Core Data Variable Issue

I am having a syntax issues I just cannot figure out. I do not have a strong Swift back ground, so the answer my be easy (I hope.) So, here is the snippet:
public func getLatestDate()-> NSDate? {
var request = NSFetchRequest()
var entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext)
request.entity = entity
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
request.sortDescriptors = sortDescriptors
var error: NSError? = nil
do {
let results = try self.managedObjectContext.executeFetchRequest(request)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
var date: NSDate?
if results != nil {
let managedObject: NSManagedObject = results![0] as NSManagedObject
date = managedObject.valueForKey("timeStamp") as? NSDate
}
return date
}
The problem is that if results != nil and the results on the following line are throwing an error stating:
Use of unresolved identifier 'results'
How do I resolve this issue?
Thank you.
-Matt
You're declaring results here:
do {
let results = try self.managedObjectContext.executeFetchRequest(request)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
So you can see that it's being done within a do-catch block. That means that where you try to use it is out of the scope where it was defined, so it can't see it at all. By the time you get to if results != nil, it's already gone out of scope and is gone.
in addition to Gavin: this will work cause of the reason, thar Gavin mentioned
do {
let results = try self.managedObjectContext.executeFetchRequest(request)
if results != nil {
let managedObject: NSManagedObject = results![0] as NSManagedObject
date = managedObject.valueForKey("timeStamp") as? NSDate
}
} catch {
fatalError("Failed to fetch employees: \(error)")
}

Reloading NSTableView after downloading data

I'm writing an app that will help me update my database on parse and I'm trying to load objects into an nstableview.
For whatever reason, I'm returning nil when I make the call in my viewcontroller so I moved the data into my appdelegate to get the objects.
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
let myVC = storyBoard.instantiateControllerWithIdentifier("teacher") as! ViewController
var teachers = [Teacher]()
let query = PFQuery(className: "TeacherList")
query.findObjectsInBackgroundWithBlock {
objects, error in
if let objects = objects {
for object in objects {
let teacher = Teacher()
teacher.name = object["name"] as! String
teacher.email = object["email"] as! String
teacher.subjectsTaught = object["subjectsTaught"] as! [String: String]
teacher.category = object["category"] as! String
teacher.uniqueID = object.objectId!
teachers.append(teacher)
}
}
print(teachers)
myVC.teacherList = teachers
}
As you see, I pass this along to my VC. So I understand that I need to reload the data as viewDidLoad() will be called before the data has been downloaded. I've tried putting tableView.reloadData() in didSet for teacherList and just in case that's set before the viewloads, I even throw it in viewdidload.
var teacherList: [Teacher]? {
didSet {
print("got set")
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.setDelegate(self)
tableView.setDataSource(self)
tableView.reloadData()
}
However, no matter what, my tableview is nil for anything that happens after viewdidload. I've also tried optional channing in my getSet.
I should also say that I'm brand new to OSX programming as I've done most of my programming for iOS.
I can put the query in my VC but whenever I run the query, I return nil.
PFQuery works asynchronously, the data is returned much later - in terms of computer speed – after viewDidLoad exits.
Reload the table view in the block right after the array has been populated on the main thread.
var teachers = [Teacher]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.setDelegate(self)
tableView.setDataSource(self)
let query = PFQuery(className: "TeacherList")
query.findObjectsInBackgroundWithBlock { [unowned self] (objects, error) in
if let objects = objects {
for object in objects {
let teacher = Teacher()
teacher.name = object["name"] as! String
teacher.email = object["email"] as! String
teacher.subjectsTaught = object["subjectsTaught"] as! [String: String]
teacher.category = object["category"] as! String
teacher.uniqueID = object.objectId!
self.teachers.append(teacher)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
print(self.teachers)
}
}

Core Data Update in swift while selecting any row in list table view not working?

I have got a scenario in which I have 2 VC which are -
VC1 - To enter detail & save the data.
VC2 - To display the datas in a table view.
Now I want that whenever I select any particular row I would update my Database at the particular row. For that I am passing the selected managed object at that particular row for which I use the following code.
VC2 class name - ViewController
Code to fetch request -
func fetchData()
{
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appdelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
do
{
people = try managedContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
print(people)
print("FETCHING DATA")
}
catch let error as NSError
{
print("could not fetch \(error), \(error.userInfo)")
}
}
Code to pass the data in selected row -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "segueupdate"
{
let update = segue.destinationViewController as! EnterDetailViewController
// var managedobject = NSManagedObject()
let indexpath = self.tableview.indexPathForSelectedRow
let row = indexpath?.row
let managedobject = people[row!]
update.managedobjectt = managedobject
}
}
VC2 class name - EnterDetailViewController
class EnterDetailViewController: UIViewController {
#IBOutlet weak var nametextfield: UITextField!
var managedobjectt = NSManagedObject()
override func viewDidLoad() {
super.viewDidLoad()
if let s = managedobjectt.valueForKey("name") as? String
{
nametextfield.text = s //here I show the user the existing name value
}
}
Now in my save function I do -
#IBAction func savedata(sender: AnyObject)
{
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedcontext = appdelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedcontext)
let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedcontext)
person.setValue(self.nametextfield.text, forKey: "name")
do
{
try managedcontext.save()
print("SAVED")
}
catch let error as NSError
{
print("could not save \(error), \(error.userInfo)")
}
self.navigationController?.popToRootViewControllerAnimated(true)
}
Here I want the compiler to check the received managed object and update any changes to the database which I am unable to do because SWIFT doesn't accept a managedobject type as condition but I was able to achieve update of database by this concept in ObjC.
And another problem is when I try to compile I get an error as -
failed to call designated initializer on NSManagedObject class
'NSManagedObject'
in the prepereforsegue() method. So how to solve the problem and perform update.
This line is wrong because you're trying to create an invalid managed object instance:
var managedobjectt = NSManagedObject()
It should be
var managedobjectt : NSManagedObject?
And when you update you aren't changing the current item if it exists, you're just always creating a new instance. You should
if let person = self.managedobjectt {
// update (if anything other than below)
} else {
// create new (insert and set any default values)
}
person.setValue(self.nametextfield.text, forKey: "name")
// save