Issue with adding multiple objects to Core Data (Swift) - ios

OverView
I continue to run into issues with adding multiple values to my Core Data entity. All i need to do is simply add 6 string-value items from a text field into Core Data. Specific examples/critique of my code would be very appreciated, as i am nearing mental break down with this issue.
The Issue
The first time i ran this, i tested it by saving only the first line (the product name) to core data and then printing it off. It worked perfect. After that, i tried the same method for all of them, and then tried printing. My program would set a breakpoint next to the "entity1.setValue(three, forKey: "serialNo")."
I also get a message in the debugger area that says (lldb).
If i try to step through the breakpoint, everything just prints out as 'nil'.
CODE
#IBAction func saveButton(sender: AnyObject) {
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObjectForEntityForName("UsedInfo", inManagedObjectContext:context) as NSManagedObject
let one = pickerTextField.text
let two = modelName.text
let three = serialNo.text
let four = YOM.text
let five = engineHours.text
let six = locationOfMachine.text
entity1.setValue(one, forKey: "product")
entity1.setValue(two, forKey:"modelName")
entity1.setValue(three, forKey:"serialNo")
entity1.setValue(four, forKey:"yom")
entity1.setValue(five, forKey:"engineHours")
entity1.setValue(six, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
do {
try context.save()
}
catch {
print("error")
}
}

Related

iOS swift core data persistence problem: Lost and thrashing

I may have done a really stupid thing.
In writing an upgrade for my (first) shipped app, I changed the schema of my Core Data model, adding another entity (Issuer) and creating a to-many relationship with the existing entity (Coin). I thought the lightweight migration had successfully taken care of the change:
Before:
After:
Here's the Core Data stack, created automatically when I created the project:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Coin_Portfolio")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
In testing on my iPhone, I found that I could create Coins, like this:
func createNewCoin(){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
newCoin = NSEntityDescription.insertNewObject(forEntityName: "Coin", into: context) as! Coin
if currentOwner.name == nil {
print("In NewCoin, createNewCoin, currentOwner is nil")
}else{
print("In NewCoin, createNewCoin, currentOwner is named \(String(describing: currentOwner.name))")
}
// Problem with contexts could be here! Compare DB for this app to test bed
newCoin.denomination = nil
newCoin.grade = nil
newCoin.itemIdentifier = ""
newCoin.mintMark = nil
currentOwner.addToCoins(newCoin)
saveIt()
}
func saveIt(){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
appDelegate.saveContext()
}
And retrieve it like this:
let context = appDelegate.persistentContainer.viewContext
let ownerFetchRequest :
NSFetchRequest<Issuer> = Issuer.fetchRequest()
do {
try ownerFRC?.performFetch()
owners = try context.fetch(ownerFetchRequest)
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
for owner in owners{
if owner.name == currentOwner.name{
currentOwnerCoinsByDenom = ((owner.coins?.allObjects as! [Coin]).sorted(by: { $0.denomination! < $1.denomination! }))
currentOwnerCoinsByYear = ((currentOwnerCoinsByDenom as! [Coin]).sorted(by: { $0.denomination! < $1.denomination! }))
print ("currentOwnerCoinsByDenom.count == \(currentOwnerCoinsByDenom)")
print(currentOwnerCoinsByDenom.count)
print ("currentOwnerCoinsByYear.count == \(currentOwnerCoinsByYear)")
print(currentOwnerCoinsByYear.count)
}
}
}
Fine, as long as I don't quit the app and relaunch it.
Upon relaunch, my fetch attempts return no Coins, even though I know that there had been some 80+ records stored in Core Data before the schema change.
I then started what I thought was a rational approach to solving the problem. See iOS data saved in Core Data doesn't survive launch. The answers received, unfortunately, didn't solve the problem.
Accordingly, I panicked, knowing my users wouldn't take kindly to losing their own records.
Here's where I may have run off the rails: I created a duplicate project in order to try to resolve the issue without screwing up my original. But I continue to experience the same problem.
I have examined the dataBase(s) using DB Browser for SQLite, but am very confused as to which DB I'm downloading -- is it the original, or the one created by the duplicate project? They both have the same name, but one of them has the original records, the other only one record.
Any ideas how I can save myself? I'm flummoxed and desperate! Will gladly supply any needed info or code.
All help deeply appreciated!

Duplicating items already in array

Every time I run the app, and then re-run it, it saves the same items into the NSUserDefaults even if it is already there.
I tried to fix that with contains code, but it hasn't worked.
What am I missing?
for days in results! {
let nD = DayClass()
nD.dayOfTheWeek = days[ā€œDā€] as! String
let defaults = NSUserDefaults.standardUserDefaults()
if var existingArr = defaults.arrayForKey("D") as? [String] {
if existingArr.contains(days["D"] as! String) == false {
existingArr.append(nd.dayOfTheWeek)
}
} else {
defaults.setObject([nD.dayOfTheWeek], forKey: "D")
}
}
Every time I run the app, and then re-run it, it saves the same items into the NSUserDefaults even if it is already there.
Yes, because that's exactly what your code does:
defaults.setObject(existingArr, forKey: "D")
But what is existingArr? It's the defaults you've just loaded before:
if var existingArr = NSUserDefaults.standardUserDefaults().arrayForKey("D") as? [String]
So what happens is that when you enter the .contains branch, you always do the same operation: you save the existing array.
Contrary to what your comment in the code states, you're not appending anything to any array in that block, you're saving the existing one.

Why is UICollectionView reloadData crashing my app?

I am using uicollectionview to display some photos and I have a button that allows the user to delete the selected photo.
It works perfectly unless I try to delete the last photo in the array of photos being used to populate the uicollectionview. Ie if there are 5 photos then there will be a problem if a user removes the 5th photo but not the 1st, 2nd, 3rd or 4th. When I try to delete the 5th photo it crashes on reloadData() with the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 4 from section 0 which only contains 4 items before the update'
I don't understand why this would happen... The error even mentions "attempt to delete" but I never actually told it I was deleting anything. I just changed the source and then asked it to update. Also in the error message it says the section only contains 4 items before the update when their were actually 5 photos. Why?
A little bit more info about what I'm doing and how (Using Swift)...
I've got a ProgressPhotoSheetClass from which I have instantiated the object progressPhotoSheet
this progressPhotoSheet object has an array of photos and the photos can have priorities such as photo id, title etc
in my number of items in section I use
var numPics: Int = progressPhotoSheet.progressPhotos.count
return numPics
in my cellForItemAtIndexPath I use
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoHolder
cell.photoTitle.text = progressPhotoSheet.progressPhotos[indexPath.row].photoName
...etc..
When deleting the item I use
progressPhotoSheet.deletePhoto(progressPhotoSheet.progressPhotos[indexPath.row].photoId)
which deletes the photo from the device and from my core data and then reloads progressPhotoSheet.progressPhotos from using the modified core data so it is now exactly how it was before but without the deleted photo.
I then call
self.collectionView.reloadData()
Which should update the UICollectionView for the new data
I could understand if it felt there was a mismatch between what should be in the collection view and what is in the datasource if I were using
self.collectionView.deleteItemsAtIndexPaths(indexPaths)
because that would be saying ignored to get them to match we need to delete one item - here there is a possibility something could mismatch.. But surely using self.collectionView.reloadData() it doesn't matter what changes were made it should just look at what data is there now and update the UICollectionView accordingly....
So my question is... Why am I getting this error and what should I do to fix things so I don't get it?
Edit to include more info
Here is my telephoto Code
func deletePhoto(photoId: Int) {
// Set up Core Data Managed Object Context
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
// Fetch correct photo
let fetchRequest = NSFetchRequest(entityName: "CDProgressPhoto")
fetchRequest.predicate = NSPredicate(format: "photoId = %#", String(photoId))
// Save
if let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
if fetchResults.count != 0{
// Will only be one photo with this photo id
var photo = fetchResults[0]
photo.setValue(true, forKey: "toDelete")
// Save the object
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
}
}
// Reload from core data
self.loadPhotoSheetFromCoreData()
}
self.loadPhotoSheetFromCoreData() then empties progressPhotoSheet.progressPhotos before getting the new data from core data... Code below...
private func loadPhotoSheetFromCoreData() {
if(self.hasPhotoSheet()) {
// Clear existing photos
self.progressPhotos = []
// Set up Core Data Managed Object Context
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "CDProgressPhoto")
let predicate1 = NSPredicate(format: "photoSheetId == %#", String(self.sheetId))
let predicate2 = NSPredicate(format: "toDelete == %#", false)
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false) as NSSortDescriptor]
var predicatesArray: [NSPredicate] = [predicate1, predicate2]
//predicatesArray.append(predicate1)
request.predicate = NSCompoundPredicate.andPredicateWithSubpredicates(predicatesArray)
let existings = managedContext.executeFetchRequest(request, error: nil)
let existingPhotos: [CDProgressPhoto] = existings as! [CDProgressPhoto]
// for each photo make a ProgressPhoto object and add to progress photos array
for photo in existingPhotos {
var newPhoto: ProgressPhoto = ProgressPhoto()
newPhoto.photoSheetId = Int(photo.photoSheetId)
newPhoto.photoId = Int(photo.photoId)
newPhoto.photoName = photo.photoName
newPhoto.date = Int(photo.date)
newPhoto.url = photo.url
newPhoto.filename = photo.filename
newPhoto.height = Float(photo.height)
newPhoto.width = Float(photo.width)
newPhoto.selected = false
self.progressPhotos.append(newPhoto)
}
}
}
As you can see the photo isn't actually deleted at this point I just set a toDelete flag to true and then only re load items where toDelete is set to false. The photos are deleted later asynchronously depending on network connection etc because they are also stored on a server for use on the main website.
Have you tried calling invalidateLayout() on the collectionView? That might help incase your view is empty i.e. 0 elements are present.

Core Data array

I am using the code below to fetch the objects in an array. In my program, I have an array displayed in a tableview and when a cell is tapped, it leads to another array displayed in a tableview. The user can add cells in both of these tableviews. What is happening is that when I create new rows in my second tableview, go back, and tap the same cell that got me there, I notice that the objects I created are not there (they were reassigned to another cell). I believe that the problem lies in the line: routines = results. What I think is happening, is that when I tap back in my second view, the line routines = results is called again, and because results is by nature unordered, it messes up the order of my previously established routines array.
var routines = [NSManagedObject]()
override func viewWillAppear(animated: Bool) {
self.navigationItem.leftBarButtonItem = self.editButtonItem()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "Routine")
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [NSManagedObject]?
if let results = fetchedResults {
routines = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
I agree with the comment from Jonathan. You are just making work for yourself holding onto everything in arrays. Use the tools that are designed to work with both Core Data and UITableView controllers and you will have a much easier (and more maintainable) time.

CoreData doesn't save data to database

I am programming an iOS app using Swift, following a tutorial on Youtube. The app will have the same function as a to-do-list-app, but another use. However, when I expect the data to be saved (and printed in the debugger) nothing happens. Have I done something wrong?
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista", inManagedObjectContext: contxt)
// Create instance of our data model and initialize
var nyTankning = Model(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
contxt.save(nil)
println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
}
You need to fetch the data first in order to display. Now you only save it. See my additions in your code after saving the data.
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista",inIntoManagedObjectContext: contxt)
// Create instance of our data model and initialize
// Use your NSManagedModel to initialize not only the Model Keyword!
var nyTankning = Tankningslista(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
if contxt.save(nil) {
// Fetch the Data from Core Data first....
let fetchRequest = NSfetchRequest(entityName: "Tankningslista")
var error:NSError?
var result = contxt.executeFetchRequest(fetchRequest, error: &error) as [Tankningslista]
for res in result {
// Now you can display the data
println(res.liter)
......
......
}
// End of the fetching
} else {
println(error)
}
//println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
I hope that solution will solve your problem.
Puuh, there is a lot to improve, I really suggest you take a "real" iOS/ swift course on a platform like Udemy/ Bitfountain or Udacity. First, you need to fetch the ManagedObject somewhere either in a function (then store the results in an array) or with NSFetchedResultController (which is mostly used for CoreData with TableViews) then I am not sure what you have in your numberOfRowsInSection function, here you should also have the correct return values. As I said, to fix that here is really too much...

Resources