Error upon core data change processing - ios

I am working on a coin collection app, and I have recently started integrating core data into my app. I have a core data database which stores custom CoinCategory objects and each CoinCategory object has an array of Coin objects. This way, my app stores everything in terms of categories.
When I integrated the core data, I can add my first category without any errors and delete it without any problems, but when I add in a second coin category, I experience the following error:
2017-06-26 09:55:37.218 CoinCollection[18889:12839563] -[CoinCollection.CoinCategory compare:]: unrecognized selector sent to instance 0x608000236cc0
2017-06-26 09:55:37.219215-0400 CoinCollection[18889:12839563] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[CoinCollection.CoinCategory compare:]: unrecognized selector sent to instance 0x608000236cc0 with userInfo (null)
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[CoinCollection.CoinCategory compare:]: unrecognized selector sent to instance 0x608000236cc0 with userInfo (null)
Could anyone please advise me on how to fix this? Thank you very much!
I am attaching my code below for my viewcontroller and the app delegate that is running the core data.
AppDelegate.swift
// We customize the app, system-wide
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CoinCollection")
container.loadPersistentStores(completionHandler: { (storeDescription,
error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext ()
{
let context = persistentContainer.viewContext
if context.hasChanges
{
do
{
try context.save()
}
catch
{
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
And then for my view controller. Notice that I get a Coin object from a sub-viewcontroller that invokes this method, and that we decide whether the Coin object fits into the existing categories. If not, then we add the new Coin.
// Controls the table view controller showing the general coins (one per each category)
import UIKit
import CoreData
class CoinTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
//this is an array of all the coins in the collection
//each row of this two-dimensional array represents a new category
var coinsByCategory: [CoinCategoryMO] = []
var fetchResultController: NSFetchedResultsController<CoinCategoryMO>!
//other attributes....
////////////////////////////////////////////////////////////////////////
override func viewDidLoad()
{
super.viewDidLoad()
//////////////////////////////////////////////////////////////////////////////////
//we now fetch the data
let fetchRequest : NSFetchRequest<CoinCategoryMO> = CoinCategoryMO.fetchRequest()
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
let context = appDelegate.persistentContainer.viewContext
let sortDescriptor = NSSortDescriptor(key: "coinCategory", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do
{
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects
{
self.coinsByCategory = fetchedObjects
}
}
catch
{
print(error)
}
}
//configure even more....
}
// MARK: - Table view data soure
func deleteCoinCategory(rowPath: IndexPath)
{
if 0 <= rowPath.row && rowPath.row < self.coinsByCategory.count
{
//we have just tested that the rowPath index is valid
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
let context = appDelegate.persistentContainer.viewContext
let coinCategoryToDelete = self.fetchResultController.object(at: rowPath)
context.delete(coinCategoryToDelete)
appDelegate.saveContext()
}
}
}
func deleteCoin(c: Coin, indexOfSelectedCategory: IndexPath) -> Bool
{
//we have a coin that we want to delete from this viewcontroller
//and the data contained in it.
//
//the parameter indexOfSelectedCategory refers to the IndexPath of the
//row in the TableView contained in THIS viewcontroller whose category
//of coins we are modifying in this method
//
//Return value: a boolean that indicates whether a single coin has
//been deleted - meaning that the user should return to the parentviewcontroller
if 0 < indexOfSelectedCategory.row && indexOfSelectedCategory.row < self.coinsByCategory.count && self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.hasCoin(c: c) == true
{
//the index is valid as it refers to a category in the coinsByCategory array
//and the examined category has the coin in question
if self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.count == 1
{
//the coin "c" that we are going to delete is the only coin in the entire category
self.deleteCoinCategory(rowPath: indexOfSelectedCategory)
return true
}
else
{
//more than one coin in the category
self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.removeCoin(c: c)
//we save the changes in the database...
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
appDelegate.saveContext()
}
return false
}
}
return false
}
func addCoin(coinToAdd: Coin)
{
//we check over each category to see if the coin can be added
for category in self.coinsByCategory
{
if category.coinCategory?.coinFitsCategory(aCoin: coinToAdd) == true
{
//we can add the coin to the category
category.coinCategory?.addCoin(newCoin: coinToAdd)
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
//we save changes to the database
appDelegate.saveContext()
//we are DONE with this function
return
}
}
}
//since the coinToAdd does not fall in the existing categories, we create a new one
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
let newCategory = CoinCategoryMO(context: appDelegate.persistentContainer.viewContext)
newCategory.coinCategory = CoinCategory(coins: [coinToAdd], categoryType: CoinCategory.CategoryTypes.COUNTRY_VALUE_AND_CURRENCY)
print("Saving data to context ...")
appDelegate.saveContext()
}
}
//delegate methods control the core data database
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
switch type
{
case .insert :
if let newIndexPath = newIndexPath
{
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath
{
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath
{
tableView.reloadRows(at: [indexPath], with: .fade)
}
default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects
{
self.coinsByCategory = fetchedObjects as! [CoinCategoryMO]
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
tableView.endUpdates()
}
I am looking forward to hearing your thoughts and thanks in advance for the help!
EDIT:
My code for the CoinCategory class:
// This class is supposed to represent a category of coin objects in CoreData
import Foundation
import CoreData
public class CoinCategory: NSObject, NSCoding
{
//These are the various types of categories that a user can create out of their coin collection
enum CategoryTypes : NSString
{
case COUNTRY_VALUE_AND_CURRENCY = //...
case COUNTRY = //...
case YEAR = //...
case CURRENCY = //...
case NO_CATEGORY = //...
}
//this struct is used to encode data in Key-Value pairs per the NSCoding protocol
struct Keys
{
static let Current_Category_Type = "current_category_type"
static let Coins_In_Category = "coins_in_category"
}
//this is the collection of the coins in the category
var coinsInCategory: [Coin] = [] //initially we have no coins in the collection
var currentCategoryType : CategoryTypes.RawValue = ""
public var count : NSNumber
{
get
{
//..number of coins in category
}
}
public var array : [Coin]
{
get
{
//read-only copy of the Coins array..
}
}
public required init?(coder aDecoder: NSCoder)
{
//we decode this object's information
if let categoryTypeObject = aDecoder.decodeObject(forKey: Keys.Current_Category_Type) as? CategoryTypes.RawValue
{
self.currentCategoryType = categoryTypeObject
}
if let coinsInCategoryArrayObject = aDecoder.decodeObject(forKey: Keys.Coins_In_Category) as? [Coin]
{
self.coinsInCategory = coinsInCategoryArrayObject
}
}
public func encode(with aCoder: NSCoder)
{
//we encode this object's information
aCoder.encode(currentCategoryType, forKey: Keys.Current_Category_Type)
aCoder.encode(self.coinsInCategory, forKey: Keys.Coins_In_Category)
}
override init()
{
super.init()
self.currentCategoryType = CategoryTypes.COUNTRY_VALUE_AND_CURRENCY.rawValue
self.coinsInCategory = []
}
convenience init(coins: [Coin], categoryType: CategoryTypes.RawValue)
{
self.init()
self.coinsInCategory = coins
if isACategoryType(categoryType: categoryType) == true
{
self.currentCategoryType = categoryType
}
else
{
self.currentCategoryType = CategoryTypes.NO_CATEGORY.rawValue
}
}
func isACategoryType(categoryType: NSString) -> Bool
{
switch categoryType
{
case CategoryTypes.COUNTRY_VALUE_AND_CURRENCY.rawValue:
return true
case CategoryTypes.COUNTRY.rawValue:
return true
case CategoryTypes.YEAR.rawValue:
return true
case CategoryTypes.CURRENCY.rawValue:
return true
default:
return false
}
}
func addCoin(newCoin: Coin)
{
//we are adding a new Coin object to this category
//if it falls into the category's type
if self.coinFitsCategory(aCoin: newCoin) == true
{
self.coinsInCategory.append(newCoin)
}
}
func coinFitsCategory(aCoin: Coin) -> Bool
{
//this function tests if aCoin fits into the category type
//but that all varies depending on which category the coin is
if self.coinsInCategory.count == 0
{
//this category is currently empty, so any addition goes!
return true
}
//otherwise, this category is not empty... so we are now going to
//examine the situation more critically
let testCoin = self.coinsInCategory[0]
switch self.currentCategoryType
{
case CategoryTypes.COUNTRY_VALUE_AND_CURRENCY.rawValue:
return (testCoin.getCountry().lowercased == aCoin.getCountry().lowercased) && (testCoin.getValue() == aCoin.getValue()) && (testCoin.getDenomination().lowercased == aCoin.getDenomination().lowercased)
case CategoryTypes.COUNTRY.rawValue:
return testCoin.getCountry().lowercased == aCoin.getCountry().lowercased
case CategoryTypes.CURRENCY.rawValue:
return testCoin.getDenomination().lowercased == aCoin.getDenomination().lowercased
case CategoryTypes.YEAR.rawValue:
return testCoin.getYear() == aCoin.getYear()
default:
return false
}
}
func getIndexOfCoinInCollection(coin: Coin) -> Int
{
//we are going to return -1 if the coin does not exist in the collection
//and are going to return the index otherwise if yes
for i in 0..<self.coinsInCategory.count
{
if coinsInCategory[i] == coin
{
return i
}
}
//have not found anything
return -1
}
func removeCoin(at: Int)
{
//we remove the coin at the index if it is in a valid range of the coinInCategory array
if isValidArrayIndex(index: at)
{
self.coinsInCategory.remove(at: at)
}
}
func getCoin(at: Int) -> Coin?
{
//we return nil if there is an issue in accessing the coin
if isValidArrayIndex(index: at)
{
return self.coinsInCategory[at]
}
else
{
return nil
}
}
func assignCoin(at: Int,c: Coin)
{
if isValidArrayIndex(index: at)
{
self.coinsInCategory[at].assign(right: c)
}
}
func deleteAllCoins()
{
//we delete all the coin in this category
self.coinsInCategory.removeAll()
}
func removeCoin(c: Coin)
{
//we delete a coin from the category
for i in 0..<self.coinsInCategory.count
{
if self.coinsInCategory[i] == c
{
//the coin at index "i" is equal to the coin "c" that we want to delete from the category
self.coinsInCategory.remove(at: i)
return
}
}
}
func hasCoin(c: Coin) -> Bool
{
return getIndexOfCoinInCollection(coin: c) != -1
}
func swapValuesWithAnotherCategory(other: CoinCategory)
{
swap(&self.currentCategoryType, &other.currentCategoryType)
swap(&self.coinsInCategory,&other.coinsInCategory)
}
func swapCoins(indexOne: Int, indexTwo: Int)
{
if isValidArrayIndex(index: indexOne) && isValidArrayIndex(index: indexTwo)
{
swap(&self.coinsInCategory[indexOne],&self.coinsInCategory[indexTwo])
}
}
private func isValidArrayIndex(index: Int) -> Bool
{
return (0 <= index && index < coinsInCategory.count)
}
}
And then the coin category class:
// This provides the class definition of Class Coin for the project
import UIKit
import CoreData
enum TimePeriods: String
{
//this enumeration represents the different time periods that a
//coin was minted in, for the sake of this programn
case BCE = "BCE"
case CE = "CE"
}
public class Coin : NSObject, NSCoding
{
//this struct represents all the keys used in encoding and decoding this object
struct Keys
{
static let Country = "country"
static let Mint = "mint"
static let Year = "year"
static let Currency = "currency"
static let Value = "value"
static let Grade = "grade"
static let Comments = "comments"
static let NumInstances = "numberInstances"
static let Description = "description"
static let Obverse = "obverse"
static let Reverse = "reverse"
}
//this represents a coin in the table view
static let GRADING_LOWER_LIMIT: NSNumber = 1
static let GRADING_UPPER_LIMIT: NSNumber = 70
//this represents the default strings returned if a field does not have the needed information
static let DEFAULT_DESCRIPTIVE_NAME: NSString = "(Description?)"
static let DEFAULT_COMMENTS: NSString = "(Comments?)"
static let DEFAULT_DENOMINATION: NSString = "(Denomination?)"
static let DEFAULT_MINT: NSString = "(Mint?)"
static let DEFAULT_COUNTRY: NSString = "(Country?)"
static let DEFAULT_YEAR: NSString = "(Year?)"
static let DEFAULT_GRADE: NSString = "(Grade?)"
static let DEFAULT_VALUE_AND_DENOMINATION: NSString = "(Value?) (Currency?)"
static let OBVERSE_IMAGE_STRING : NSString = "Obverse"
static let REVERSE_IMAGE_STRING : NSString = "Reverse"
static private let BULLET = "➣ " //represents the kind of bullet to be used to build a complete summary of the coin
//declare members with setters and getters
private var country: NSString = "" //what country/empire/etc. used in?
private var mint: NSString = "" //where minted? EX: US Mint, St. Petersburg
private var year: NSNumber? = nil //what year minted? per gregorian calendar
//the year can be negative to represent the BCE period
//positive to represent the CE periods
private var typeCurrency: NSString = "" //what is the unit of value? EX: Cents, dollars, centavos, etc
private var theValue: NSNumber = 0 //how many? EX: how many dollars, cents, centavos, etc.?
//additional information about the coin
private var grade: NSNumber? //on the american grading scale for coins. 1-70
private var comments: NSString = "" //extra comments stored by the user for himself
private var numberOfInstances: NSNumber = 0 //number of coins exactly like this. EX: 1,2,3,4...etc? For each instance, it must be >= 1.
//This describes the type of the coin
//EX: Walking Liberty Quarter, Barber Quarter, Standing Liberty Quarter... etc
private var descriptiveName: NSString = ""
private var obverseImage: UIImage? = nil
private var reverseImage: UIImage? = nil
public var valueAndDenomination: NSString
{
get
{
//need to check four cases
//case 1: we have the right values for value and denomination
//case 2: we do not have a value but do have denomination
//case 3: we have a value but do not have denomination
//case 4: we do not have both
//
//the reason why we consider 0 to be an empty value is because a coin that was worth
//nothing would not have been minted in the first place!!!
if (self.theValue != 0 && self.typeCurrency != "")
{
//have value and denomination
return "\(self.theValue) \(self.typeCurrency)" as NSString //like "20 Cents"
}
else if (self.theValue == 0 && self.typeCurrency != "" )
{
//do not have value, but have denomination
return "(Value?) \(self.typeCurrency)" as NSString
}
else if (self.theValue != 0 && self.typeCurrency == "")
{
//we have value, but do not have denomination
return "\(self.theValue) (Currency?)" as NSString
}
else
{
//we do not have both
return Coin.DEFAULT_VALUE_AND_DENOMINATION as NSString
}
}
}
public required init?(coder aDecoder: NSCoder)
{
//we decode this object's information
if let countryObject = aDecoder.decodeObject(forKey: Keys.Country) as? NSString
{
self.country = countryObject
}
if let mintObject = aDecoder.decodeObject(forKey: Keys.Country) as? NSString
{
self.mint = mintObject
}
if let yearObject = aDecoder.decodeObject(forKey: Keys.Year) as? NSNumber
{
self.year = yearObject
}
if let currencyObject = aDecoder.decodeObject(forKey: Keys.Currency) as? NSString
{
self.typeCurrency = currencyObject
}
if let valueObject = aDecoder.decodeObject(forKey: Keys.Value) as? NSNumber
{
self.theValue = valueObject
}
if let gradeObject = aDecoder.decodeObject(forKey: Keys.Grade) as? NSNumber
{
self.grade = gradeObject
}
if let commentObject = aDecoder.decodeObject(forKey: Keys.Comments) as? NSString
{
self.comments = commentObject
}
if let numInstancesObject = aDecoder.decodeObject(forKey: Keys.NumInstances) as? NSNumber
{
self.numberOfInstances = numInstancesObject
}
if let descriptiveNameObject = aDecoder.decodeObject(forKey: Keys.Description) as? NSString
{
self.descriptiveName = descriptiveNameObject
}
if let obverseImageObject = aDecoder.decodeObject(forKey: Keys.Obverse) as? UIImage
{
self.obverseImage = obverseImageObject
}
if let reverseImageObject = aDecoder.decodeObject(forKey: Keys.Reverse) as? UIImage
{
self.reverseImage = reverseImageObject
}
}
override init()
{
//default initializer
super.init()
self.country = ""
self.mint = ""
self.year = nil
self.typeCurrency = ""
self.theValue = 0
self.comments = ""
self.numberOfInstances = 1
self.descriptiveName = ""
self.obverseImage = nil
self.reverseImage = nil
}
init(country: NSString,year: Int?,typeCurrency: NSString, theValue: NSNumber,mint: NSString,grade: Int?,numInstances: NSNumber = 1,description: NSString, comments: NSString)
{
super.init()
self.country = country
self.mint = mint
self.year = year! as NSNumber
self.typeCurrency = typeCurrency
self.theValue = theValue
self.comments = comments
self.numberOfInstances = numInstances
self.descriptiveName = description
self.obverseImage = nil
self.reverseImage = nil
}
init(country: NSString,year: NSNumber?,typeCurrency: NSString, theValue: NSNumber,mint: NSString,grade: NSNumber?,numInstances: NSNumber = 1,description: NSString, comments: NSString,obverseImage: UIImage, reverseImage: UIImage)
{
super.init()
self.country = country
self.mint = mint
self.year = year
self.typeCurrency = typeCurrency
self.theValue = theValue
self.comments = comments
self.numberOfInstances = numInstances
self.descriptiveName = description
}
public func encode(with aCoder: NSCoder)
{
//we encode the coin's information
aCoder.encode(self.country, forKey: Keys.Country)
aCoder.encode(self.mint, forKey: Keys.Mint)
aCoder.encode(self.year, forKey: Keys.Year)
aCoder.encode(self.typeCurrency, forKey: Keys.Currency)
aCoder.encode(self.theValue, forKey: Keys.Value)
aCoder.encode(self.grade, forKey: Keys.Grade)
aCoder.encode(self.comments, forKey: Keys.Comments)
aCoder.encode(self.numberOfInstances, forKey: Keys.NumInstances)
aCoder.encode(self.descriptiveName, forKey: Keys.Description)
aCoder.encode(self.obverseImage, forKey: Keys.Obverse)
aCoder.encode(self.reverseImage, forKey: Keys.Reverse)
}
//setter and getter functions for class members...
func getCompleteSummary() -> NSString
{
//returns a bulleted list that represents the coin
//and it describes every single detail...
}
func getIncompleteSummary() -> String
{
//returns a bulleted list string that represents the coin
//and it describes every single detail...
}
/////////////////////////////////////////////////////////////////////////////////
func ofSameType(rhs: Coin) -> Bool
{
return (self.getCountry().lowercased == rhs.getCountry().lowercased) && (self.getValue() == rhs.getValue()) && (self.getDenomination().lowercased == rhs.getDenomination().lowercased)
}
public static func==(lhs: Coin, rhs: Coin) -> Bool
{
//we compare two coin objects for equality in ALL Categories
return lhs.country.lowercased == rhs.country.lowercased &&
lhs.theValue == rhs.theValue &&
lhs.typeCurrency.lowercased == rhs.typeCurrency.lowercased &&
lhs.mint.lowercased == rhs.mint.lowercased &&
lhs.year == rhs.year &&
lhs.grade == rhs.grade &&
lhs.comments == rhs.comments &&
lhs.numberOfInstances == rhs.numberOfInstances &&
lhs.descriptiveName == rhs.descriptiveName &&
lhs.obverseImage == rhs.obverseImage &&
lhs.reverseImage == rhs.reverseImage
}
func assign(right: Coin)
{
//we implement this instead of overloading the assignment "=" operator
//as it is not possible to overload the "=" operator
//we assign the right-hand-coin's field values
//to the left-hand coin's side
self.country = right.country
self.theValue = right.theValue
self.typeCurrency = right.typeCurrency
self.mint = right.mint
self.year = right.year
self.grade = right.grade
self.comments = right.comments
self.numberOfInstances = right.numberOfInstances
self.descriptiveName = right.descriptiveName
self.obverseImage = right.obverseImage
self.reverseImage = right.reverseImage
}
}

It looks like the problem is that your CoinCategory class does not implement the compare: method:
[CoinCollection.CoinCategory compare:]: unrecognized selector sent to instance 0x608000236cc0
My guess is that the FRC is trying to compare the coinCategory attribute of the second CoinCategoryMO to the coinCategory attribute of the first CoinCategoryMO in order to get them in the correct order (since your FRC is sorted using the coinCategory attribute).
So the direct answer is that you need to implement a compare: method for your CoinCategory class. But I think this is just an indicator that you need to change your approach. Rather than having an Entity with attributes that are custom objects (or collections thereof), you should probably aim to have multiple Entities (eg. a CoinMO entity, a CoinCategoryMO entity, a CoinCategoryTypeMO entity, etc) with to-many relationships between them. If you show the code for your custom objects (Coin and CoinCategory) it will be easier to advise how best to model them in CoreData.

Related

how to update realm object based on the certain property in Realm?

I have products in my Realm database like this
I want to update my realm database based on productID, so I don't need to add another product over and over again. let say I want to update quantity of product that has productID = "a" to be 5.
I have tried to write something like this.
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let productIndex = productsInRealmDatabase.index(where: {$0.productID == selectedProductID})
if let productIndex = productIndex {
do {
try realm.write {
var productRealm = productsInRealmDatabase[productIndex]
productRealm.quantity = 5
productsInRealmDatabase[productIndex] = productRealm // ERROR HERE
}
} catch {
// error Handling
}
}
but I got error in : productsInRealmDatabase[productIndex] = productRealm
Error Message: Cannot assign through subscript: subscript is get-only
so how to update realm object based on the certain property in Realm?
You should use Realm's own filter method which accepts an NSPredicate and returns an auto-updating Results instance rather than Swift's filter when operating on Realm collections. Than either update the properties of the fetched prouduct or create a new one and save that to Realm.
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let matchingProduct = productsInRealmDatabase.filter("productID == %#", selectedProductID).first
if let matchingProduct = matchingProduct {
do {
try realm.write {
matchingProduct.quantity = 5
}
} catch {
// error Handling
}
} else {
let newProduct = Product()
newProduct.productID = selectedProductID
newProduct.quantity = 5
do {
try realm.write {
realm.add(newProduct)
}
} catch {
// error Handling
}
}
If you want your Products to be unique based on their productID property, you use also set productID as the primaryKey of your Object subclass.
class Product:Object {
#objc dynamic var productID = ""
...
override static func primaryKey() -> String? {
return "productID"
}
}
Try this -
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let filteredProducts = productsInRealmDatabase.filter("productID = \(selectedProductID)")
do {
try realm.write {
filteredProducts.forEach { product in
product.quantity = 5
}
}
} catch {
// error Handling
}
While inserting data to your database inside the insert function mark update key as true and then try updating the value. eg:
static func insertData() {
//Your insertion code//
try! realm.write {
realm.add(request, update: true)
}
}
static func updateData(productId: String, quantity: Int) {
let product = self.getProductData(prodId: productId)
let realm = try! Realm()
try! realm.write {
product?.quantity = quantity
}
}
Hope this helps you out.

Use Core Data to save a changing integer in swift

Hi just wondering if this is possible, i have a simple counter app where a counter variable is incremented by 1 every time a button is pushed. is it possible to save the counter when the user exits the app using core data? i know NSUserdefaults would work here but im exploring core data and was wondering if it could be used in cases like this
class ViewController: UIViewController {
var counter = Int()
#IBOutlet var label: UILabel!
#IBAction func button(sender: AnyObject) {
counter += 1
label.text = "\(counter)"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
NSManagedObject subclass Error
class IntegerEntity: NSManagedObject {
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?, value: Int) {
self.init(entity: entity, insertIntoManagedObjectContext: context)
self.value
// ERROR value of type IntegerEntity has no member value
}
}
ViewController.swift errors
import UIKit
import CoreData
class ViewController: UIViewController {
var counter = Int()
#IBOutlet var label: UILabel!
#IBAction func button(sender: AnyObject) {
counter += 1
label.text = "\(counter)"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
if integer != nil && !integer!.isEmpty {
(integer?.first! as! IntegerEntity).value = counter
// ERROR value of type IntegerEntity has no member value
} else {
let newInt = IntegerEntity(entity: entity, insertIntoManagedObjectContext: context, value: counter)
// ERROR Use of unresolved identifier context
dataContext.saveData()
}
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
if integer != nil && !integer!.isEmpty {
counter = (integer!.first! as! IntegerEntity).value
// ERROR value of type IntegerEntity has no member value
} else {
counter = 0
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NSManagedObjectContext {
func saveData() -> Bool {
do {
try self.save()
return true
} catch let error as NSError {
print(error)
return false;
}
}
}
This is possible.
First, create a Core Data model and add an entity called IntegerEntity. In the entity, add a property called value whose type is Integer 64. Now generate the NSManagedObject subclass.
In IntegerEntity,swift, add the following initializer:
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?, value: Int) {
self.init(entity: entity, insertIntoManagedObjectContext: context)
self.value = value
}
This is because later on, we can use this directly to create an entity with a value.
I suppose your app has only one view controller, so you have two options of when to save the data:
Use an NSTimer to save the data every 60 seconds or some other time interval
override viewDidDisappear in the view controller or applicationWillResignActive in the app delegate.
To save the data, you first need to get the saved integer back (if present).
// Just some boilerplate code here
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
// If some integer has already been saved...
if integer != nil && !integer.isEmpty {
(integer?.first! as! IntegerEntity).value = counter
} else {
// If no integer has been saved before...
let newInt = IntegerEntity(entity: entity, insertIntoManagedObjectContext: context, value: counter)
dataContext.saveData()
}
}
You may be wondering, what's saveData? I think my version of Xcode has a bug. Xcode always insists that save() method throws an error and returns Void. So I created this extension:
extension NSManagedObjectContext {
func saveData() -> Bool {
do {
try self.save()
return true
} catch let error as NSError {
print(error)
return false;
}
}
}
And BOOM! that's it!
"But how do I fetch the data back?" you asked. Actually, you did it before! Here's the code:
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var integerEntity: IntegerEntity!
if dataContext != nil {
let entity = NSEntityDescription.entityForName("IntegerEntity", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let integer = try? dataContext.executeFetchRequest(request)
// If some integer has already been saved...
if integer != nil && !integer.isEmpty {
counter = (integer!.first! as! IntegerEntity).value.integerValue
} else {
// If no integer has been saved before...
counter = 0
}
}
Looks familiar? It's almost the same code as before!
WARNING
IMO, it's not worth it to write so much code just to save one single integer, just so you know. Use user defaults instead. If you want to explore Core Data, you can try saving lists of words in Core Data. And with the press of a button , AVSpeechSynthesizer read them all aloud.

Is there any way of sharing getter and setter through properties in Swift?

I'm building a helper to enable typed access to NSUserDefaults properties. Something like this:
struct UserDefaults {
private static var standardUserDefaults: NSUserDefaults = {
return NSUserDefaults.standardUserDefaults()
}()
private static let propKey = "PROP"
static var prop: Bool {
get {
return standardUserDefaults.boolForKey(propKey)
}
set {
standardUserDefaults.setBool(newValue, forKey: propKey)
standardUserDefaults.synchronize()
}
}
}
This way I can have a nice syntax for reading and writing to NSUserDefaults:
UserDefaults.prop // read
UserDefaults.prop = false // write
The problem is that there's a lot of boilerplate code for this, I need 10 lines for each aditional property.
Is there any way of reducing the amount of lines needed for each new property? Reusing getter and setter? Any kind of run time generator?
You can try wrapping the actual value in a class that handles all the dirty work for you:
class WrappedUserDefault<T> {
let key : String
let defaultValue : T
var value : T {
get {
if let value = UserDefaults.standardUserDefaults.objectForKey(key) as? T {
return value
} else {
return defaultValue
}
}
set {
if let value = newValue as? AnyObject {
UserDefaults.standardUserDefaults.setValue(value, forKey: key)
} else {
UserDefaults.standardUserDefaults.removeObjectForKey(key)
}
UserDefaults.standardUserDefaults.synchronize()
}
}
init(key:String, defaultValue:T) {
self.key = key
self.defaultValue = defaultValue
}
}
struct UserDefaults {
static let standardUserDefaults = NSUserDefaults.standardUserDefaults()
static let ready = WrappedUserDefault<Bool>(key:"ready", defaultValue: true)
static let count = WrappedUserDefault<Int>(key: "count", defaultValue: 0)
}
Then with just a little bit more code you wind up with:
UserDefaults.count.value++
UserDefaults.ready.value = true
UserDefaults.ready.value
If the verbosity of ready.value bothers you, you can somewhat hide that, although then you're back to you're back to having a fair amount of copy/paste code:
struct UserDefaults {
static let standardUserDefaults = NSUserDefaults.standardUserDefaults()
private static let readyWrapper = WrappedUserDefault<Bool>(key:"ready", defaultValue: true)
static var ready : Bool {
get { return readyWrapper.value }
set { readyWrapper.value = newValue }
}
}
At least in this case though, the copy/paste code is fairly trivial, so unlikely to need to be altered in the future.
I like David's answer much better, but here's another option. Drops your 10 lines per variable down to 5 (mainly because of new line removal...)
struct UserDefaults {
private static var standardUserDefaults: NSUserDefaults = {
return NSUserDefaults.standardUserDefaults()
}()
//Repeate these 5 lines for all new variables,
//changing the as? to the proper variable type
//Adding in a default value for the return in
//case the as? cast fails for any reason
private static let propKey = "PROP"
static var prop: Bool {
get { return (getVar(propKey) as? Bool) ?? false }
set { setVar(newValue, key:propKey) }
}
//The generic set/get
private static func getVar(key : String) -> AnyObject?
{
return standardUserDefaults.objectForKey(key)
}
private static func setVar(newValue : AnyObject, key : String)
{
if(newValue is Bool)
{
standardUserDefaults.setBool((newValue as? Bool)!, forKey: key)
}
//... More cases here
else if(newValue == nil)
{
standardUserDefaults.removeObjectForKey(key)
}
else
{
standardUserDefaults.setObject(newValue, forKey: key)
}
standardUserDefaults.synchronize()
}
}

Loop through Realm Object fields in Swift

I have a Realm Object
class CoursesModel: Object {
dynamic var courseName = ""
dynamic var par3Field = 0
dynamic var par4Field = 0
dynamic var par5Field = 0
}
When somebody enters the course name I want to check whether it already exists before writing it to Realm.
Can you please tell me what I'm doing wrong because it doesn't seem to loop through.
class func compareCourse(name : String) -> Bool {
let c = name
do
{
let realm = try Realm()
let course = realm.objects(CoursesModel)
for course in course {
if course == c {
print("course = \(course)")
print("c = \(c)")
return true
}
else {
return false
}
}
}
catch
{
// return nil
}
return false
}
Any help will be greatly appreciated.
EDIT - WORKING CODE HERE
class func compareCourse(name : String) -> Bool {
let c = name
do
{
let realm = try Realm()
let course = realm.objects(CoursesModel)
for course in course {
let a = course.courseName
print("Model Course = \(a)")
print("Passed Course = \(c)")
if a == c {
return true
}
}
}
catch
{
// return nil
}
return false
}
You are returning in both branches of the loop, which immediately exits out of the function. You do not want to return false on the first failure, but only after all have failed (I think).

CoreData occurred error on device but fine on simulator

I just tried using core data to do something, my goal is create a object with a child context, assign the values for an object, and then save it to storage after doing some operations.
It works fine on simulator, but an error occurred when running on a device.
This is the error :
CoreData: error: Mutating a managed object 0x174033540
(0x1740cb600) after it has been removed from its context.
my code :
Extestion
extension NSManagedObject {
class func GV_New() -> NSManagedObject {
let entity = self.MR_entityDescription();
let parentContext = NSManagedObjectContext.MR_defaultContext();
let childContext = NSManagedObjectContext.MR_contextWithParent(parentContext);
let managedObject = NSManagedObject(entity: entity, insertIntoManagedObjectContext: childContext);
return managedObject;
}
class func GV_Insert(object: NSManagedObject) -> Bool {
var result: Bool = false;
let keyPath = "userInfo.primaryKey";
let primaryKey = self.MR_entityDescription().valueForKeyPath(keyPath) as String!;
if primaryKey != nil {
if (self.MR_findFirstByAttribute(primaryKey, withValue: object.valueForKey(primaryKey)) != nil) {
result = false;
} else {
let managedObjectId = object.objectID;
let managedObjectContext = object.managedObjectContext;
let managedObject = managedObjectContext!.existingObjectWithID(managedObjectId, error: nil);
managedObject?.managedObjectContext?.MR_saveOnlySelfAndWait();
NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreAndWait();
result = true;
}
}
return result;
}
}
ViewController
class ViewController: UIViewController {
var camera: Camera!;
override func viewDidLoad() {
super.viewDidLoad();
println("\(Camera.MR_countOfEntities())");
camera = Camera.GV_New() as Camera;
camera.camera_id = 4567;
Camera.GV_Insert(camera);
println("\(Camera.MR_countOfEntities())");
for (index, value) in enumerate(Camera.MR_findAll()) {
let result = value as Camera;
println("result: \(value.camera_id)");
}
}
}
just resolved this problem,
I haven't retain the child context, since swift not supported class variables yet,
just set managedContext on the file.
let managedContext = NSManagedObjectContext.MR_context();
let managedContext = NSManagedObjectContext.MR_context();
extension NSManagedObject {
class func GV_New() -> NSManagedObject {
return self.MR_createInContext(managedContext) as NSManagedObject;
}
class func GV_Insert(object: NSManagedObject) -> Bool {
var result: Bool = false;
let keyPath = "userInfo.primaryKey";
let primaryKey = self.MR_entityDescription().valueForKeyPath(keyPath) as String!;
if primaryKey != nil {
if (self.MR_findFirstByAttribute(primaryKey, withValue: object.valueForKey(primaryKey)) != nil) {
result = false;
}
else {
object.managedObjectContext?.MR_saveOnlySelfAndWait();
NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreAndWait();
result = true;
}
}
return result;
}
}

Resources