Parse object crashes while there is a key in the database - swift

So I am trying to change the background color for all rows in the chat, that a user has written himself. I do this by checking if the facebookID from the user, matches the facebookID on the message from the database.
But for some reason the .valueForKey on my Messages database keeps crashing.
var myUser:PFUser = PFUser.currentUser()!
var queryId = PFQuery(className: "Messages")
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.messageTableView.dequeueReusableCellWithIdentifier("messageCell") as! ChatTableViewCell
cell.textLabel?.text = messagesArray[indexPath.row]
let getUser = myUser.valueForKey("facebookID")
print(getUser)
let getIDUser = queryId.valueForKey("facebookID")
print(getIDUser)
//if getUser == getIDUser {
// make cell red
//}
return cell
}
This gives back the following in the console for getIDUser
reason: '[<PFQuery 0x133dadf10> valueForUndefinedKey:]: this class is not key value coding-compliant for the key facebookID.'
So the getIDUser returns an error, but I don't have a clue why since the database and key do exist.

The problem is that this
var messageDB:PFObject = PFObject(className:"Messages")
creates just new message object. And you indeed can add it to the database.
But to get you objects from store you have to perform query.
Here are more info.

Related

How do I retrieve the unique ID in swift to delete it(Firebase)

How would I be able to access the unique ID in swift for Firebase? I have tried almost everything this forum has, I'm trying to delete that certain node within a table cell in swift:
I tried creating a reference to from the Users email, and retrieving the IDs in a dictionary, however when I try to reference it from "indexPath", it returns the ID.
I went as far as trying an Array with a type int, string, any; and those yell at me.
I have included some sample code:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
var currentEmail = Auth.auth().currentUser?.email as String?
if(editingStyle == .delete) {
let ref = Database.database().reference()
ref.child(currentEmail!).observe(.value, with: { snapshot in
if !snapshot.exists() {return} //may want to add better error handling here.
let info = snapshot.value as! NSDictionary
let reff = info.index(ofAccessibilityElement: indexPath)
print("info keys are:", reff)
})
}
did you try snapshot.key? Example:
self.uid = snapshot.key
The email address of a user will contain a . character, which is not allowed in a key in Firebase. So that is probably the error message you get on ref.child(currentEmail!) (in the future, include the exact error message in your question please).
In your database, you have used the email address minus the . as the email address: nice#nicecom, which is probably the email address nice#nice.com.
So you'll also need to strip the . from currentEmail before passing it to the database.
So something like:
currentEmail = currentEmail!.replacingOccurrences(of: ".", with: "")

Can't view Firebase data in tableview Swift 4

Im having a issue where I can't view anything that I write to my Firebase database in my table view. I previously had some working code to view my database entries but had to revise how I write data to the database so I can save the unique ID generated by childByAutoID(), so I may delete an entry later. Here's my code:
Heres how I write to Firebase:
ref = Database.database().reference() // sets the variable "ref" to connect to our Firebase database
let key = ref?.child("task").childByAutoId().key
let post = ["uid": key, // Gets auto generated ID for database entry
"title": input.text, // Saves the title field to Firebase
"description": inputField.text] // Saves the description field to Firebase
ref?.child("task").child(key!).setValue(post) // Saves the task for Firebase, ties each post with a unique ID
var arr : [(String, String)] = [];
for (key, value) in post {
arr.append((key, value!));
Heres my TableViewController:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return (arr.count)
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style:
UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
let (key, value) = arr[indexPath.row]; //read element for the desired cell
cell.textLabel?.text = key
cell.detailTextLabel?.text = value
return (cell)
}
override func viewDidAppear(_ animated: Bool) {//change "viewDidLoad()" back to "viewDidAppear()"
ref = Database.database().reference() // sets the variable "ref" to connect to our Firebase database
list.removeAll() // Deletes all older data, so only data thats on the Firebase Database will be added to the "list" array
desc.removeAll() // Deletes all older data, so only data thats on the Firebase Database will be added to the "desc" array
handle = ref?.child("task").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
arr.append(item)
list.append(item)
//desc.removeAll()
self.myTableView.reloadData()
}
})
}
Firebase returns the items in form of NSArray or NSDictionary or NSString. (Refer here)
Since your post object is NSDictionary, parse the snapshot value as a Array of Dictionary. try the following:
if let itemArray = snapshot.value as? [[String : Any]] {
// Looping logic on 'itemArray' to get your sent dictionaries on the firebase
// After looping logic add Ui logic out of loop
}

NSInternalInconsistencyException: Invalid Update using tableview CoreData

I am using a tableView to display a list of people. I am trying to add an alert to confirm that the user actually wants to delete the person and to prevent mistakes. However, when I try to delete the person that is stored with CoreData, there seems to be a problem reloading the view. I get this exception:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Editing and Delete Function:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
var deleteRow = indexPath.row
indexPathforDelete = indexPath
let entityDescription = NSEntityDescription.entityForName("People", inManagedObjectContext: managedObjectContext!)
let request = NSFetchRequest()
request.entity = entityDescription
var error: NSError?
var objects = managedObjectContext?.executeFetchRequest(request, error: &error)
if let results = objects {
let personToDelete = results[deleteRow] as! NSManagedObject
let firstName = personToDelete.valueForKey("firstName") as! String
let lastName = personToDelete.valueForKey("lastName") as! String
var message = "Are you sure you would like to delete \(firstName) \(lastName)?\nThis will permanentaly remove all records of "
if(personToDelete.valueForKey("gender") as! String == "Male"){
message = "\(message)him."
}
else{
println(personToDelete.valueForKey("gender") as! String)
message = "\(message)her."
}
var deleteAlert : UIAlertView = UIAlertView(title: "Delete \(firstName) \(lastName)", message: message, delegate: self, cancelButtonTitle: "Cancel")
deleteAlert.addButtonWithTitle("Delete")
deleteAlert.show()
}
save()
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
AlertView Response Function:
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int){
if(buttonIndex == 1){
managedObjectContext?.deleteObject(personToDelete)
tableView.deleteRowsAtIndexPaths([indexPathforDelete], withRowAnimation: .Fade)
save()
}
setEditing(false, animated: true)
self.navigationItem.leftBarButtonItem = nil
}
tableView number of rows function:
var personToDelete = NSManagedObject()
var indexPathforDelete = NSIndexPath()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
let entityDescription = NSEntityDescription.entityForName("People", inManagedObjectContext: managedObjectContext!)
let request = NSFetchRequest()
request.entity = entityDescription
var error: NSError?
var objects = managedObjectContext?.executeFetchRequest(request, error: &error)
let results = objects
println("Results Count: \(results!.count)")
return results!.count
}
I think the problem is that you have two variables with the name propertyToDelete: a property that you declare and initialise with a blank NSManagedObject:
var personToDelete = NSManagedObject()
and a local variable that you declare within your commitEditingStyle function:
let personToDelete = results[deleteRow] as! NSManagedObject
It is this local variable to which you assign the object from your results array. But this local variable is destroyed when the function completes, and the AlertView action is deleting the object to which the property points. (The reason I hesitate is that I would expect your context to throw an error when it tries to delete an object that has never been registered with it). Note that by contrast you have only the one variable named indexPathforDelete. This holds the correct value when the AlertView action runs, and consequently the tableView deletes the correct row. That's why you get the error: it has deleted a row, but then finds (because no object has been deleted) it still has the same number of rows as before.
The immediate solution is to use the property within your function, rather than a local variable: just delete let:
personToDelete = results[deleteRow] as! NSManagedObject
But I would also recommend rethinking your approach: you are repeating the same fetch. If all the datasource methods do the same, it will be repeated numerous times when the table view is first built, whenever a cell is scrolled into view, whenever a cell is tapped, etc. This will be costly in terms of performance. You should instead undertake the fetch once (perhaps in viewDidLoad), store the results in an array property, and use that for the table view datasource methods. Alternatively, and perhaps preferably, use an NSFetchedResultsController: it is very efficient and there is boilerplate code for updating the table view when objects are added or deleted.
The documentations of tableView:commitEditingStyle:forRowAtIndexPath: says: "You should not call setEditing:animated: within an implementation of this method. If for some reason you must, invoke it after a delay by using the performSelector:withObject:afterDelay: method."

No returns for Parse query

I'm trying to pull locations near a user via Parse for tableview, except I only get either a white screen or an empty table.
Edited: I neglected to add the call for location before query near geopoint is submitted. In testing, I found that location was determined under 'geoPontForCurrentLocationInBackground'. However, once the geopoint is established and the query is submitted, the user location returns no latitude or longitude within the query. Moreover, the query doesn't return any objects and I'm not sure why:
override func queryForTable() -> PFQuery! {
var query = PFQuery(className: "User")
PFGeoPoint.geoPointForCurrentLocationInBackground {
(userLocation: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
}
}
let point: PFGeoPoint = PFGeoPoint(latitude: self.userLoc.latitude, longitude: self.userLoc.longitude)
query.whereKey("location", nearGeoPoint: point, withinMiles: 50.0)
query.limit = 10
return query
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as Shops
if let object = object {
if let shopName = object.valueForKey("shopName") as? String {
cell.shopList.text = shopName
}
}
return cell as Shops
}
I believe the issue is that Parse treats the User class as a special class. You may have difficulty querying User class if you treat it as if it was a Custom Class.
The correct and tested way to do this is to the the query function of PFUser.
var query = PFUser.query()
You are setting up the query, but never actually executing it. Per the Parse documents, you need to add:
// Final list of objects
queryResults = query.findObjects()
Before your queryForTable() return statement in order to get the query objects.

iOS swift tableview cell for parse query data

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
var query = PFQuery(className:"category")
let object = objects[indexPath.row] as String
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
NSLog("%#", object.objectId)
let abc = object["link"]
println("the web is \(abc)")
cell.textLabel!.text = "\(abc)"
}
} else {
NSLog("Error: %# %#", error, error.userInfo!)
}
}
return cell
}
after add the let object = objects[indexPath.row] as String can't load the view, delete the line show only one row successfully.
First I advise you to get your cell data outside cellForRowAtIndexPath. This function is not a good place to receive data from parse. Make another function and create a class variable and put handle getting data from there.
let object = objects[indexPath.row] as String
for object in objects
Try not to use same variable names for different stuff, as they will confuse you.
This line is not contributing to anything at the moment it seems. Try deleting it:
let object = objects[indexPath.row] as String
First lets have principles in mind. Don't ever update UI from a separate thread, its behavior is unexpected or undefined. It works or works weird.
Second, the problem you have is the when the VC gets loaded the tableView's datasource is called there and then on the main thread. Now you tried to add something on the cell by doing a Async call in separate thread which will take time and main thread is not waiting when the call to parse is being done. If you have difficulty in Async please take a look at the documentation its really important to get a good grasp of the few terms and the principles.
The thing is your main thread runs top to bottom without waiting each call to server thats async in the cell generation. So the result of that call will post later on and you are not posting on main thread too.
Moreover, i would suggest you don't do this approach for big projects or manageable code base. I generally do is:
when the view loads call the Parse with the needed information
Wait for that on a computed variable which i will observe to reload table views once I'm conformed i have the data.
Initially table view will have 0 rows and thats fine. Ill make a spinner dance during that time.
I hope i made some issues clear. Hope it helps you. Cheers!
//a computed var that is initialized to empty array of string or anything you like
//we are observing the value of datas. Observer Pattern.
var datas = [String](){
didSet{
dispatch_async(dispatch_get_main_queue(), {
//we might be called from the parse block which executes in seperate thread
tableView.reloadData()
})
}
}
func viewDidLoad(){
super.viewDidLoad()
//call the parse to fetch the data and store in the above variable
//when this succeeds then the table will be reloaded automatically
getDataFromParse()
}
//get the data: make it specific to your needs
func getDataFromParse(){
var query = PFQuery(className:"category")
//let object = objects[indexPath.row] as String //where do you use this in this block
var tempHolder = [String]()
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
for object in objects!{
//dont forget to cast it to PFObject
let abc = (object as! PFObject).objectForKey("link") as? String ?? "" //or as! String
println("the web is \(abc)")
tempHolder.append(abc)
}
} else {
print("error") //do some checks here
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = datas[indexPath.row]
return cell
}