So I have this function in class Functions :
struct Prices {
var standardPrice: Int!
}
// FUNC PRICING
class Functions {
private var PricingRef: CollectionReference!
var price = Prices()
func getPrice() -> Prices {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
}
}
}
return price
}
}
Then I want to pass the standardPrice value to this class, called PriceList :
class PriceList: UITableViewController {
var price = Prices()
var newStandardPrice = 0
func Price() {
price = Functions().getPrice()
newStandardPrice = price.standardPrice // always error with value nil
}
I always have that error where newStandardPrice is nil.
but the print(self.price.standardPrice!) shows number of result I want.
So as far as I know, the problem here is because it takes time for the firebase firestore to get the data from database.
How do I get the value of standardPrice after its assigned with the new price from firebase database?
Any help will be appreciated
Thankyou
you need to use completion handler because its async function
func getPrice(completion:#escaping (Prices?,Error?)-> Void) {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
completion(nil,err)
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
completion(self.price.standardPrice,nil)
}
}
}
}
How to use
Functions().getPrice { (price, error) in
if let err = error {
// do something if you get error
} else if let getPrice = price {
// use price
self.price = getPriice
}
I am using the below code to fetch the data from the firestore database in swift iOS. But when I scroll the new data loaded is replacing the previously loaded data in the tableview. I am trying to fix this issue but as of now no good.
The outcome required is that adding new documents to the previously list of documents in the tableview
Below is the code I am implementing. If any more information is required please let me know
CODE
fileprivate func observeQuery() {
fetchMoreIngredients = true
//guard let query = query else { return }
var query1 = query
stopObserving()
if posts.isEmpty{
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).limit(to: 5)
}
else {
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).start(afterDocument: lastDocumentSnapshot).limit(to: 2)
// query = db.collection("rides").order(by: "price").start(afterDocument: lastDocumentSnapshot).limit(to: 4)
print("Next 4 rides loaded")
print("hello")
}
// Display data from Firestore, part one
listener = query1!.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Post in
if let model = Post(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
}
self.posts = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
self.fetchMoreIngredients = false
self.lastDocumentSnapshot = snapshot.documents.last
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let off = scrollView.contentOffset.y
let off1 = scrollView.contentSize.height
if off > off1 - scrollView.frame.height * leadingScreensForBatching{
if !fetchMoreIngredients && !reachEnd{
print(fetchMoreIngredients)
// beginBatchFetch()
// query = baseQuery()
observeQuery()
}
}
}
Instead of calling snapshot.documents, call snapshot.documentChanges. This returns a list of document changes (either .added, .modified, or .removed, and allows you to add, remove, or modify them in your local array as needed... Not tested code just an idea what you ca do ...
snapshot.documentChanges.forEach() { diff in
switch diff.type {
case .added:
if let model = Post(dictionary: diff.document.data()){
self.posts.append(model)
}else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
case .removed:
// add remove case
case .modified:
// add modify case
}
}
I am beginner in programming. I actually have my own answer of this questions and the app worked as I am expected, but I am not sure if this is the correct way to to this.
This check out action will be triggered after the user click chechoutButton. but before before this chechoutButton.isEnabled , I have to make sure 3 parameters are available (not nil). before doing this check out action, I need 3 parameters :
get user's coordinate from GPS.
get user's location address from Google Place
API
Get current date time from server for verification.
method to get user location address from Google Place API will be triggered only if I get the coordinate from GPS, and as we know, fetching data from the internet (to take date and time) also takes time, it should be done asynchronously.
how do I manage this checkoutButton only enabled if those 3 parameters are not nil ? Is there a better way according to apple guideline to do this
the simplified code are below
class CheckoutTVC: UITableViewController {
#IBOutlet weak var checkOutButton: DesignableButton!
var checkinAndCheckoutData : [String:Any]? // from MainMenuVC
var dateTimeNowFromServer : String?
var userLocationAddress : String?
let locationManager = LocationManager()
var coordinateUser : Coordinate? {
didSet {
getLocationAddress()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// initial state
checkOutButton.alpha = 0.4
checkOutButton.isEnabled = false
getDateTimeFromServer()
getCoordinate()
}
#IBAction func CheckoutButtonDidPressed(_ sender: Any) {
}
}
extension CheckoutTVC {
func getDateTimeFromServer() {
activityIndicator.startAnimating()
NetworkingService.getDateTimeFromServer { (result) in
switch result {
case .failure(let error) :
self.activityIndicator.stopAnimating()
// show alert
case .success(let timeFromServer) :
let stringDateTimeServer = timeFromServer as! String
self.dateTimeNowFromServer = stringDateTimeServer
self.activityIndicator.stopAnimating()
}
}
}
func getCoordinate() {
locationManager.getPermission()
locationManager.didGetLocation = { [weak self] userCoordinate in
self?.coordinateUser = userCoordinate
self?.activateCheckOutButton()
}
}
func getLocationAddress() {
guard let coordinateTheUser = coordinateUser else {return}
let latlng = "\(coordinateTheUser.latitude),\(coordinateTheUser.longitude)"
let request = URLRequest(url: url!)
Alamofire.request(request).responseJSON { (response) in
switch response.result {
case .failure(let error) :// show alert
case .success(let value) :
let json = JSON(value)
let locationOfUser = json["results"][0]["formatted_address"].string
self.userLocationAddress = locationOfUser
self.locationAddressLabel.text = locationOfUser
self.activateNextStepButton()
}
}
}
func activateCheckoutButton() {
if dateTimeNowFromServer != nil && userLocationAddress != nil {
checkOutButton.alpha = 1
checkOutButton.isEnabled = true
}
}
}
I manage this by using this method, but I don't know if this is the correct way or not
func activateCheckoutButton() {
if dateTimeNowFromServer != nil && userLocationAddress != nil {
checkOutButton.alpha = 1
checkOutButton.isEnabled = true
}
}
You can use DispatchGroup to know when all of your asynchronous calls are complete.
func notifyMeAfter3Calls() {
let dispatch = DispatchGroup()
dispatch.enter()
API.call1() { (data1)
API.call2(data1) { (data2)
//DO SOMETHING WITH RESPONSE
dispatch.leave()
}
}
dispatch.enter()
API.call3() { (data)
//DO SOMETHING WITH RESPONSE
dispatch.leave()
}
dispatch.notify(queue: DispatchQueue.main) {
finished?(dispatchSuccess)
}
}
You must have an equal amount of enter() and leave() calls. Once all of the leave() calls are made, the code in DispatchGroupd.notify will be called.
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.
I am wanting to update the child values after editing inside the textfields.
At the moment I have this action:
#IBAction func updateAction(_ sender: Any) {
guard let itemNameText = itemName.text, let itemDateText = itemDate.text else { return }
guard itemNameText.characters.count > 0, itemDateText.characters.count > 0 else {
print("Complete all fields")
return
}
let uid = FIRAuth.auth()?.currentUser?.uid
let key = item.ref!.key
let itemList = Item(itemName: itemNameText, itemDate: itemDateText, uid: uid!)
let editItemRef = databaseRef.child("/usersList/\(key)")
editItemRef.updateChildValues(itemList.toAnyObject())
print("edited")
}
I was following this tutorial but he seems to use the username, and as I only have the email or uid (userID) as authentication I thought I'd use the uid.
This is my toAnyObject function inside my class:
func toAnyObject() -> [String: AnyObject] {
return ["itemName": itemName as AnyObject, "itemDate": itemDate as AnyObject, "userID": userID as AnyObject]
}
When I run the breakpoint it does show the edited value of the item however the update doesn't appear to be performing.
Just to be extra safe, try dropping the leading slash from your path:
databaseRef.child("usersList/\(key)")
…and try printing the Error returned by Firebase, if any:
editItemRef.updateChildValues(itemList.toAnyObject()) {
(error, _) in
if let error = error {
print("ERROR: \(error)")
} else {
print("SUCCESS")
}
Edit. We found out he was using the wrong database path. The right one is:
databaseRef.child("users").child(uid!).child("usersList/\(key)")