over writing mangedObjectContext - data dissapearing - ios

The setup is this:
I have a table view that populates with two different sets of data from two different methods located in the same class.
When I run the first set of data by itself, it works great.
When I instantiate the second method that is suppose to grab data based on an ID within the first set of data which is pulled with each loop of the "cellForRowAtIndexPath" , it breaks as if I tried to call on data that no longer exists.
If feels like somewhere in the process the first data set is being replaced by the second data set.
So, the below works.
import Foundation
import UIKit
import CoreData
class LendersViewController: UIViewController {
var lenders = Array<Lenders>()
let lendersModel = LendersModel()
//MARK: VC Delegate Methods
override func viewDidLoad() {
// Get the data from CoreData and Put it into a variable that I can get to later.
self.lenders = lendersModel.readLenderData() as Array
}
}
extension LendersViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return lenderData.count!
return self.lenders.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
// Set the cell to reference the LendersViewCell class below.
// This contains all the connections to the cell.
let cell: LendersViewCell = tableView.dequeueReusableCellWithIdentifier("LenderCell") as LendersViewCell
let lendersNSArray = lenders as NSArray
cell.cellLenderName.text = lendersNSArray[indexPath.row].valueForKey("corpName") as String?
return cell
}
}
class LendersViewCell: UITableViewCell {
#IBOutlet weak var cellLenderLogo: UIImageView!
#IBOutlet weak var cellAprValue: UILabel!
#IBOutlet weak var cellDownValue: UILabel!
#IBOutlet weak var cellMonthlyValue: UILabel!
#IBOutlet weak var cellFavButtonLabel: UIButton!
#IBOutlet weak var cellLenderName: UILabel!
}
But as soon as I modify the "cellForRowAtIndexPath" with the below, which calls on the second method for its data set, into cellForRowAtIndexPath ...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
// Set the cell to reference the LendersViewCell class below.
// This contains all the connections to the cell.
let cell: LendersViewCell = tableView.dequeueReusableCellWithIdentifier("LenderCell") as LendersViewCell
let lendersNSArray = lenders as NSArray
cell.cellLenderName.text = lendersNSArray[indexPath.row].valueForKey("corpName") as String?
//Adding the below two lines causes an error.
var getLenderId = lendersNSArray[indexPath.row].valueForKey("lenderId") as? String
self.lenderUserComments = lendersModel.readUserCommentsData(getLenderId!)
return cell
}
The first iteration works but the second iteration throws this error.
"fatal error: unexpectedly found nil while unwrapping an Optional value"
The error here isn't the problem though. I get that the value "getLenderId" is nil and unwrapping it as nil is throwing the error.
The problem is that the data set from the first method disappears after I instantiate the second method.
Couple points.
Both methods are in the same class but read data from two different Core data tables.
I am using NSFetchedResultsControllerDelegate in that class and that is how the data is manages prior to sending it as an array to the processes making the request.
I searched the site but came across lots of unrelated topics. I am sure that someone has run into this before so if this has been asked with greater clarity a link to the better question would be appreciated.
Thanks all,
p.s. Here is the model class with the methods that are being called.
import Foundation
import CoreData
import UIKit
class LendersModel:NSObject, NSFetchedResultsControllerDelegate{
//MARK: - Variables
// *** Setup the params used to work with the database.
private let entityName:String = "LenderUserComments"
private let entityName2:String = "Lenders"
private let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
//Reads ALL lender data
func readLenderData()-> Array<Lenders>{
// Setup database connection and save to context.
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: entityName2)
request.returnsObjectsAsFaults = false;
// Get the data and put it into a variable
var results = context.executeFetchRequest(request, error: nil)
return results as Array<Lenders>
}
// Reads User's comments on given data.
func readUserCommentsData(lenderId:String)-> Array<LenderUserComments>{
// Setup database connection and save to context.
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: entityName)
request.returnsObjectsAsFaults = false;
request.predicate = NSPredicate(format: "lenderId = %#", lenderId)
// Get the data and put it into a variable
var results = context.executeFetchRequest(request, error: nil)
return results as Array<LenderUserComments>
}
....
}
Added at the request of the a commenter.
var managedObjectContext: NSManagedObjectContext {
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}
Solution :I replaced the managedObjectContext variable in AppDelegate.swift. Credits to Hashmat Khalil for providing the core of the solution. The below works like a charm.
var managedObjectContext: NSManagedObjectContext {
if (_managedObjectContext != nil)
{
return _managedObjectContext!; // making sure you don't reinitialise
}
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}

so now looking at your code every time you access appDel.managedObjectContext you are creating a new instance. when the old instance is gone you won't be able to access the data as well and you have to refetch everything. so make sure that you use only one instance. and it would be better for GUI context to have main concurrency.
var managedObjectContext: NSManagedObjectContext {
if (_managedObjectContext)
{
return _managedObjectContext; // making sure you don't reinitialise
}
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}
that's it.

Related

How to take Core Data and Filter it with NSPredicate Swift

I am currently learn and creating a basic decision app. The basics of the app is to take user input for a category they would like to do and then all the things that want to fill that category with.
Now I am wanting to display the results on a table view which works but I also what to click on each individual category that they recently used and be able to see the things that they placed under ever category. I was getting everything that was being save to the Core Data but now I am trying to use NSPredicate to filter out what I need. When I run the App there is nothing in the table view.
mainName I have passed in from a different view controller to capture and set what the name of the category was to help filter the data. I was trying to use it in the predicate as a filter.
I don't know if what I am doing is right but help would be great. This is independent study project I am doing to help finish my degree and everything I know is self taught so far. If what I have is completely wrong please tell me. This is just one of the hundreds of different ways I have tried to get this right.
#IBOutlet weak var tableview: UITableView!
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return whats.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
let Doing = whats[indexPath.row]
cell.textLabel!.text = Doing.valueForKey("what") as? String
return cell
}
func loadData(){
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
if let context = appDelegate?.managedObjectContext{
let fetchRequest = NSFetchRequest(entityName: "Doing")
let namePredicate = NSPredicate(format: "namesR.noun = '\(mainName)'")
(whats as NSArray).filteredArrayUsingPredicate(namePredicate)
fetchRequest.predicate = namePredicate
do {
let results =
try context.executeFetchRequest(fetchRequest)
whats = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
If you want use Core Data in your tableView based app? You can use NSFetchedResultsController class and their delegate protocol methods. This class specially designed for a tableView!
Official Documentation for NSFetchedResultsController class
From my understanding, you're saying you have a Category entity and an Item entity with many Item for each Category. In that case, in the view controller where you want to display the Items, you need to have a Category variable to use in your predicate so that you only get back the Items associated with that Category.
class ItemsTableVC {
var category: Category!
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Item")
fetchRequest.sortDescriptors = []
fetchRequest.predicate = NSPredicate(format: "category == %#", self.category)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.sharedContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
}

Update Core Data from a UISwitch inside a TableViewCell

I have a static table that is bound to some Core Data values, I'm not sure how I would use NSFetchedResultsController in this instance, though I have seen discussions about how much more recommended it is.
I grab my Core Data object which is passed via Segue.
I also have a model that is setup to contain questions, with one of the properties containing the Core Data value (this is why I don't think I can use NSFetchedResultsController, as even though my Core Data entity contains some of the values I need, I'm not sure I would need a full data set)
self.surveyQuestion.append(SurveyQuestion(question: "Does the customer have a 'Proof of ownership'?", answer: coreDataEntity.isProofOfOwnership as Bool))
The questions are Survey related such as "Is your property X?" with a UiSwitch which is mapped to a Core Data value:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell : SurveyQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier("SurveyQuestionCell") as! SurveyQuestionTableViewCell
cell.lblQuestion.textColor = UIColor.grayColor()
let surveyQuestion = self.surveyQuestion[indexPath.row]
cell.lblQuestion.text = surveyQuestion.question
cell.toggQuestion.on = surveyQuestion.answer
if cell.toggQuestion.on {
cell.lblQuestion.textColor = UIColor.blackColor()
cell.accessoryType = .DetailDisclosureButton
}
return cell
}
Now, when I tap on the UISwitch I need it to update the Core Data value, and reload the table, its hooked up to a CustomTableViewCell like so:
*edit - Nearly got this thing working! heres my UITableViewCell class
class SurveyQuestionTableViewCell: UITableViewCell {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestion?
var tableViewReference : UITableView?
#IBAction func toggledQuestion(sender: AnyObject) {
let tempContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
tempContext.parentContext = self.managedObjectContext
tempContext.performBlock({
let entityName = "CoreDataEntity"
let request = NSFetchRequest(entityName: entityName)
request.predicate = NSPredicate(format: "id = %#", self.surveyQuestionReference!.id)
do {
let results = try tempContext.executeFetchRequest(request) as? [NSManagedObject]
if results!.count > 0{
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
for result in results!{
result.setValue(self.toggQuestion.on, forKey: (self.surveyQuestionReference?.property)!)
}
})
}
}
do {
try tempContext.save()
//completion(finished: true)
} catch let error {
print(error)
}
}catch{
print("error")
}
})
print(sender)
dispatch_async(dispatch_get_main_queue()) {
self.tableViewReference!.reloadData()
}
}
I can obviously access the bit where the toggle is triggered, but this class doesn't know anything about the Core Data bit, I was thinking about using notifications but that just seems kind of messy...
when you create your cell, pass in a reference to the coredata object, and the tableView itself and store them as attributes of SurveyQuestionTableViewCell, then you can do everything you need to in setSelected()
in your custom cell class, add an attribute for the question
class SurveyQuestionTableViewCell: UITableViewCell {
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestionType
vat tableViewReference : UITableView
...
and then in cellForRowAtIndexPath after you create the cell
cell.surveyQuestionReference = surveyQuestion
cell.tableViewReference = tableView
where SurveyQuestionType is whatever you have previously defined
in setSelected, you can use those stored attributes
surveyQuestionReference = self.toggQuestion.on
tableViewReference.reloadData()
Here's another option, using a shared Instance
import Foundation
import MapKit
import CoreData
class DataModelInstance : NSObject, NSCoding
{
var appDelegate : AppDelegate?
var managedContext : NSManagedObjectContext?
var persistentStoreCoordinator : NSPersistentStoreCoordinator?
// plus whatever else you need
class var sharedInstance : DataModelInstance
{
struct Singleton
{
static let instance = DataModelInstance()
}
return Singleton.instance
}
and then in any class which needs access to this data model
var dataModel = DataModelInstance.sharedInstance
I know there are those who just won't ever use singletons, but it can be a much more elegant solution to making these attributes available where they are needed
With a shared data model, you can simply move all of your data attributes out of the class they are currently in, and reference them through the data model - then if you have the same data model in your custom cell class, you can do whatever you can do in the main view. To keep your GUI and processing logic separate, you can put everything in the data model
dataModel.refreshTable()
and then define a function in the data model that takes care of your table view - you could save all current edits to the data, and reload, without having to put any of that logic in individual cell classes
for updating any record in core data try to use this code:
let managedObjectContext:NSManagedObjectContext=(UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let req=NSFetchRequest(entityName: "Entity Name")
req.returnsObjectsAsFaults=false
let result:NSArray=try! managedObjectContext.executeFetchRequest(req)
if result.count>0{
let res=result[Int(indexPath.row!]as! NSManagedObject
res.setValue("The Value", forKey: "Key Name")
do {
try managedObjectContext.save()
} catch _ { print("Update Unsuccessful") }
You must use [unowned self] in within the closure. See Apple's docs. This is how it's done. See also CoreDataKit, a 28-star github repo Core Data stack. It's available on cocoapods and honestly, why not just drop something like this into your app and not worry about "unowned selves" and other philosophical brain twisters, eh?
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
/* In here we are in a closure (Swift version of a block), so "self" is problematic. Use unowned self instead (in objective c you'd have to do weak self). */
[unowned self] in
for result in results!{
result.setValue(self.toggQuestion.on,
forKey: (self.surveyQuestionReference?.property)!)
}
})
}

Core Data to create TableViewCells Swift

So I am trying to create a TableViewCell with Core Data, but when defining the cells, they all turn in the last input at the Core Data. The app is taking the user textfield input and turning into the table view cell label, and the zipInStr to the TableViewCell detail.
This is the function that add the values to the CoreData:
#IBAction func createFav(sender: AnyObject) {
//Defining variables to save at core data
var newTextInput = textInput.text
var trimmNewTextInput = newTextInput.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
var zipInStr: String = zipCode.text!
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newFav = NSEntityDescription.insertNewObjectForEntityForName("Cells", inManagedObjectContext: context) as NSManagedObject
newFav.setValue(zipInStr, forKey: "favsDictKey")
newFav.setValue(trimmNewTextInput, forKey: "favsDictValues")
context.save(nil)
println(newFav)
textInput.text = String()
}
}
And this is the function that is creating the TableViewCells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var results: NSArray = context.executeFetchRequest(request, error: nil)!
println(results)
if (results.count == 0){
println("Error")
} else {
for res in results{
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
}
}
return cell
}
I am pretty sure the error have something to do with the loop, since when I print the results I can see all the inputs with its respective values
Your setup is not correct. You should employ a NSFetchedResultsController (see Xcode template for easy code to copy) - this is the best way to populate a table view with Core Data.
In cellForRowAtIndexPath you simply retrieve the correct item for the index path. Don't put fetch request into this method, as it is potentially called many times (e.g. while scrolling).
let cell = self.fetchedResultsController.objectAtIndexPath(indexPath) as Cells
BTW, "Cells" seems to be an inappropriate name for an entity. You need a singular that describes the data you are displaying. Perhaps, "Favorite"?
As per my comment, to get your current setup working, make results into a stored property. Add
var results : NSArray = NSArray()
at the beginning of your class definition. Then move the code to populate the array from your cellForRowAtIndexPath to viewDidLoad:
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
results = context.executeFetchRequest(request, error: nil)!
println("\(results)")
Now, the tableView will call cellForRowAtIndexPath multiple times, once for each row that it wants to display. So the code in that method needs to configure it with the data for the relevant row. The indexPath provided as an argument to the call indicates which row (and section, if you break your table view into sections) the tableView is requesting. So your code does not need to loop through all the values in results - it just needs to reference the correct element of the array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let res = results[indexPath.row] as NSManagedObject
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
return cell
}
That should get your tableView working (unless I've messed up some optionals or casts - if so, sorry, I'm still getting up to speed with Swift).
This approach will work fine provided your data is relatively static (i.e. doesn't change whilst the tableView is being displayed) and provided you don't want to break the table into sections. In either of these situations (and, some would say, in any case), you should use an NSFetchedResultsController. That will automatically keep track of how many (and which) items appear in each section, etc, and can also be configured to automatically update the tableView if new items are added to the data whilst it is being displayed.

How do I use NSManagedObject and get all the data back?

I'm having trouble getting my NSManagedObject to work.
In one file I have:
import UIKit
import CoreData
#objc(Location)
class Location: NSManagedObject {
#NSManaged var title:String
#NSManaged var lat:NSDecimalNumber
#NSManaged var lon:NSDecimalNumber
#NSManaged var course:NSDecimalNumber
#NSManaged var alt:NSDecimalNumber
}
In a TableView class I have:
...
var locations:NSArray = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = self.getAllLocations()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var logCell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("loggedCell") as TableViewCell
logCell.loggedTitle.text = locations[indexPath.row].title
if let lat = locations[indexPath.row].lat {
println(lat)
}
return logCell
}
func getAllLocations() -> [Location] {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Locations")
request.returnsObjectsAsFaults = false
if let results = context.executeFetchRequest(request, error: nil) {
return results as [Location]
} else {
// Failed, return empty error. Alternatively, report error.
return []
}
}
...
I am able to retrieve the values from CoreData. If I println( locations[indexPath.row] ) it's all there. However in func tableView I am only able to get the title. lat, lon, everything but title errors out with 'AnyObject' does not have a member named 'lat', as in the lines:
if let lat = locations[indexPath.row].lat {
println(lat)
}
I'm sure it's something basic as I am new to Swift and iOS development. I would appreciate a point in the right direction please.
It's partly about Swift's strict typing and partly about the interplay between Swift types and Cocoa (Objective-C) types.
The problem here is that you have typed locations as an NSArray. That's a Cocoa collection, not a Swift collection. Swift doesn't know what kind of thing is inside an NSArray.
You may know (or believe) that what's at locations[indexPath.row] is a Location, therefore, but Swift doesn't know that. You have to tell it, by casting (with as).
Alternatively, if there is no reason not to do so, type locations as a Swift array of Location, a [Location]. Now Swift knows what's in it. I should also point out that as things stand, your initializer is pointless:
var locations:NSArray = [Location]()
You start by making a Swift array of Location, yes, but then by typing locations as an NSArray and assigning into it, you throw away all knowledge that this thing is supposed to contain only Location objects.

Passing an object from one viewcontroller to another viewcontroller got nil in swift

Recently, I'm learning about CoreData in Swift. My purpose is about to send the value of one object "editContact" in class "AllContactTableViewController" as code below.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedObjectContext:NSManagedObjectContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: managedObjectContext)
var request = NSFetchRequest(entityName: "Contact")
request.returnsObjectsAsFaults = false
var results: NSArray = managedObjectContext.executeFetchRequest(request, error: nil)!
let editContactView : EditContactTableViewController = EditContactTableViewController()
var editContact : Contact = results.objectAtIndex(indexPath.row) as Contact
editContactView.editContact = editContact
println("\(editContactView.editContact)")
}
to another viewcontroller called "EditContactTableViewController" (as code below)
class EditContactTableViewController: UITableViewController {
var editContact : Contact!
override func viewDidLoad() {
super.viewDidLoad()
firstNameField.text = self.editContact.firstName
lastNameField.text = self.editContact.lastName
phoneField.text = self.editContact.phone
emailField.text = self.editContact.email
companyField.text = self.editContact.company
addressField.text = self.editContact.address
}
}
then it caused the error as "fatal error: unexpectedly found nil while unwrapping an Optional value"
in the log. It seems the value of object called "editContact" in this class has changed to nil.
Do you have idea how to fix this problem?
First, use a NSFetchedResultsController. There are many advantages, and you also will safely get the object you need with
self.fetchedResultsController.objectAtIndexPath(indexPath)
If your contact is displayed in the cell you must already have fetched it. So it does not make any sense to fetch it again. Also you have an unused variable (entity) in your very verbose but unnecessary code.
The best way is to use a segue in storyboard from the cell to the edit controller. You can then set up the object in prepareForSegue.
One nice setup is to subclass the table view cell and give it a property of type Contact. The displaying of the attributes in the cell can be handled by the subclass, helping you to uncluttered the table view controller. You can then easily retrieve the object in prepareForSegue from the sender parameter.

Resources