Swift 3 How to increment value in parse - swift

I created an app that has a forum and in that forum users can like a post.
When i try to increment the number of likes in parse it seems to increment it because when i print out the value, it prints correctly, but when i refresh parse, it stays at 0.
Here is my likeButton function:
//This gives me the index of the cell in which the like button was tapped
#IBAction func likeButton(_ sender: AnyObject) {
let buttonRow = (sender.tag)!
let query = PFQuery(className: "Posts")
query.whereKey("body", equalTo: messages[buttonRow])
query.whereKey("title", equalTo: titles[buttonRow])
query.findObjectsInBackground { (object, error) in
if error != nil {
print(error)
}else{
if let post = object {
for objects in post {
if let posts = objects as? PFObject {
//I would think this line is the only thing I'd need to execute but it isn't working
posts.incrementKey("Like", byAmount: 1)
let pre = [posts["Like"]!]
//this prints 1 --> meaning it worked
print(pre[0])
//This next line should update posts["Like"] but it doesn't
posts["Like"] = pre[0]
posts.saveInBackground()
}
}
}
}
}
}
It seems like i'm incrementing it, but it is not saving. Please, any help would be greatly appreciated!
Thanks!

Related

Download single Object of Firestore and save it into an struct/class object

I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not

SearchBar problem while trying to search Firestore and reload the tableview

I have a tableView and I use infinite scroll to populate firestore data with batches. Also I have a searched bar and I am trying to query firestore with the text from the text bar and then populate it in the tableview. I have 3 main problems.
When I click search thee first time I get an empty array and an empty tableview, but when I click search the second time everything seems fine.
When I finally populate the searched content I want to stop fetching new content while I am scrolling.
If I text a wrong word and press search then I get the previous search and then the "No Ingredients found" printed twice.
This is my code for searchBar:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else {return}
searchIngredients(text: text)
self.searchBarIngredient.endEditing(true)
print("\(searchIngredients(text: text))")
}
The code for function when I click search
func searchIngredients(text: String) -> Array<Any>{
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
if let err = err {
print("\(err.localizedDescription)")
print("Test Error")
} else {
if (querySnapshot!.isEmpty == false){
self.searchedIngredientsArray = querySnapshot!.documents.compactMap({Ingredients(dictionary: $0.data())})
}else{
print("No Ingredients found")
}
}
}
self.tableView.reloadData()
ingredientsArray = searchedIngredientsArray
return ingredientsArray
}
Finally the code for scrolling
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let off = scrollView.contentOffset.y
let off1 = scrollView.contentSize.height
if off > off1 - scrollView.frame.height * leadingScreensForBatching{
if !fetchMoreIngredients && !reachEnd{
beginBatchFetch()
}
}
}
I don't write the beginBatchFetch() cause its working fine and I don't think is relevant.
Thanks in advance.
The issue in your question is that Firestore is asynchronous.
It takes time for Firestore to return documents you've requested and that data will only be valid within the closure calling the function. The code outside the closure will execute way before the data is available within the closure.
So here's what's going on.
func searchIngredients(text: String) -> Array<Any>{
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
}
//the code below here will execute *before* the code in the above closure
self.tableView.reloadData()
ingredientsArray = searchedIngredientsArray
return ingredientsArray
}
what's happening is the tableView is being refreshed before there's any data in the array.
You're also returning the ingredientsArray before it's populated. More importantly, attempting to return a value from an asynchronous function can (and should) generally be avoided.
The fix is to handle the data within the closure
class ViewController: NSViewController {
var ingredientArray = [String]()
func searchIngredients(text: String) {
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
//populate the class var array with data from firebase
// self.ingredientArray.append(some string)
//refresh the tableview
}
}
Note that the searchIngredients function should not return a value - nor does it need to

Parse retrieve PFObject with Pointer

I have two classes Place and BeenHere. BeenHere has pointers called "toPlace" and "fromUser"with target to Class Place and User accordingly. Place, in its turn, has title and image (PFFile) that I want to retrieve and show in ViewController. In the code below I have reached that pointer with objectId, but don't know how I can now retrieve title and image related to specific place this pointer leads to. Appreciate your help and suggestions.
class UserBeenHereViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let user = PFUser.currentUser()?.username
if user != nil {
let query = PFQuery(className: "BeenHere")
query.includeKey("toPlace")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
for object in objects! {
print(object["toPlace"].objectId)
}
}
else {
print("There is error")
}
}
}
}
First you need to get the "toPlace" into a PFObject and then access it. So in your case it should look something like:
var toPlace = comment["toPlace"] as? PFObject
print (toPlace["title"])

Can't see the messages I'm posting in Parse

I'm creating a yik yak clone and I can't seem to see the messages I post in the textField(string) on Parse. Is there something wrong I'm doing in my code that's not letting it show up on Parse?
#IBAction func postPressed(sender: AnyObject) {
if(currLocation != nil){
let testObj = PFObject(className: "BubbleTest")
testObj["userName"] = PFUser.currentUser()?.username
//testObj["profileName"] = PFUser.valueForKey("profileName") as! String
//testObj["photo"] = PFUser.currentUser()?.valueForKey("photo") as! PFFile
testObj["textField"] = self.textField.text
testObj["location"] = PFGeoPoint(latitude: currLocation!.latitude , longitude: currLocation!.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackground()
self.dismissViewControllerAnimated(true, completion: nil)
}
else {
alert()
}
The reason you are not seeing anything because you post it into the wrong class. According to the picture BubbleTest is the name of the class not YikYakTest
replace this line
let testObj = PFObject(className: "YikYakTest")
by
let testObj = PFObject(className: "BubbleTest")
your code should look like :
Note use saveInBackgroundWithBlock method so you could check if there is an error while saving
let testObj = PFObject(className: "BubbleTest")
let username = PFUser.currentUser()?.username
testObj["userName"] = username
testObj["textField"] = self.textField.text
testObj["Location"] = PFGeoPoint(latitude:currLocation.latitude , longitude: currLocation.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackgroundWithBlock { (success:Bool, error :NSError?) -> Void in
if error == nil
{
print("detail is saved")
self.dismissViewControllerAnimated(true, completion: nil)
}
else
{
print("error")
}
}
when you are saving PFGeopoint coordinates save it into Location column not location
I know many developer friends of mine who ran into a similar issue. I myself had this problem as well, now resolved. So hopefully I can provide some insight from what I learned querying data from Parse:
Try changing the numberOfSectionsInTableView method to return 1 instead of 0 like so:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
You may need to also have some data structure to hold the users' posts (messages):
var userPosts:NSMutableArray! = NSMutableArray()
Also, your table view could then have as many rows as you will have posts stored in userPosts:
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return userPosts.count
}
In cellForRowAtIndexPath, replace this:
let object = PFObject(className: "BubbleTest")
WITH THIS:
let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject
...
cell.message.text = userPost.objectForKey("message") as! String
return cell
}
This will set the text of your custom cell's message property to whatever the user's message is (i.e.: "Testing 1 2").
Note: These steps aren't intended to be the only steps needed to solve your problem. It is meant to guide you in the right direction with some basic steps.
Hope that helps! :)

Swift - tableView.reloadData() not working even though it seems in the right thread

I am working on a social media app where I have implemented basic like/unlike/follow/unfollow features. I am using Parse as a backend. Here is what happens.
I am getting the posts liked by the user from my backend with this function in my ViewDidLoad, this seems to be working:
func getLikes() {
var query = PFQuery(className: "Likes")
query.whereKey("Liker", equalTo: PFUser.currentUser()["displayName"])
query.findObjectsInBackgroundWithBlock { (results:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
for result in results {
self.likedPosts.append(result["likedItem"] as String)
}
println(self.likedPosts)
self.homeTableView.reloadData()
}
}
}
Then, in my cellForRowAtIndexPath, I set the title and the function of the likeButton according to whether or not the id of the post for that cell is contained in the array of liked posts:
if contains(self.likedPosts, self.id[indexPath.row]) {
cell.thankButton.setTitle("Unlike", forState: UIControlState.Normal)
cell.thankButton.addTarget(self, action: "unlike:", forControlEvents: UIControlEvents.TouchUpInside)
}
else {
cell.thankButton.setTitle("Like", forState: UIControlState.Normal)
cell.thankButton.addTarget(self, action: "like:", forControlEvents: UIControlEvents.TouchUpInside)
}
This works fine, as the buttons in each cell display the right title in accordance with the backend. They also have the right function, which code is as follows:
func like(sender:UIButton) {
var id = sender.tag
var postId = self.id[id]
var likeAction = PFObject(className: "Likes")
likeAction["Liker"] = PFUser.currentUser()["displayName"]
likeAction["likedItem"] = postId
likeAction.saveInBackgroundWithBlock { (success:Bool!, error:NSError!) -> Void in
if error == nil {
if success == true {
self.homeTableView.reloadData()
println("liked")
}
}
}
}
func unlike(sender:UIButton) {
var id = sender.tag
var postId = self.id[id]
var query = PFQuery(className: "Likes")
query.whereKey("Liker", equalTo: PFUser.currentUser()["displayName"])
query.whereKey("likedItem", equalTo: postId)
var targetId:String!
query.findObjectsInBackgroundWithBlock { (results:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
targetId = results[0].objectId
var likeObject = PFObject(withoutDataWithClassName: "Likes", objectId: targetId)
likeObject.deleteInBackgroundWithBlock({ (success:Bool!, error:NSError!) -> Void in
if error == nil {
if success == true {
self.homeTableView.reloadData()
println("liked")
}
}
})
}
}
}
However, reloadData never works, and the buttons retain their title and function, even though it should change (as the backend registers the change). I am aware that a recurring reason for reloadData() not to work is that it is not in the right thread, but as far as I can tell, it is here. The "println()" in both functions actually works every time, the backend registers the change every time, but reloadData() never works.
Any idea is greatly appreciated.Thank you!
It seems not in the main thread. The println works well in any thread, but when you update your UI you should do that on the main thread. Try wrapping your code in a block like this:
NSOperationQueue.mainQueue.addOperationWithBlock {
self.homeTableView.reloadData()
}
OK the problem was actually dumb, but I'm posting in case anyone who is a noob like me gets the issue: basically I was calling reloadData without updating anything, since the array of liked posts is refreshed in getLikes(). I put self.getLikes() instead of reloadData() and it works fine.