Issue Reloading Table View After Adding Parse Object - ios

I have a UITableViewController that displays data from a Parse query. It get the data and displays it fine except when I create a new object and run the query again to get the new data. When I create a new object the table view keeps the existing data in my array and displays it but it appends all the data from the query to the array so the objects that already existed prior to creating the new object get displayed twice. I tried emptying the arrays at the start of the query function but since I have the skip property set on the query I can't do that because my array will only get everything after the skip if the limit is reached. So, how can I just add the new object to my array?
I should also mention that I can't simply add the new object name to the array in addCollection() because I have to add the objectId to my objectID array.
func getCollections() {
activityIndicator?.startAnimating()
// collections = [] - Can't do this because of the skip (if the skip is used)
// objectID = []
let query = PFQuery(className: "Collections")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.orderByAscending("collectionName")
query.limit = limit
query.skip = skip
query.findObjectsInBackgroundWithBlock( {
(objects, error) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let collectionName = object["collectionName"] as! String
let id = object.objectId
self.collections.append(collectionName)
self.objectID.append(id!)
}
}
if objects!.count == self.limit {
self.skip += self.limit
self.getCollections()
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
self.activityIndicator!.stopAnimating()
}
} else {
var errorString = String()
if let message = error!.userInfo["error"] {
errorString = message as! String
}
print(errorString)
}
})
}
func addCollection(name: String) {
let collection = PFObject(className: "Collections")
collection["user"] = PFUser.currentUser()
collection["collectionName"] = name
collection.saveInBackground()
getCollections()
}

This code is logically flawed and can be simplified:
func addCollection(name: String) {
let collection = PFObject(className: "Collections")
collection["user"] = PFUser.currentUser()
collection["collectionName"] = name
collection.saveInBackground()
getCollections()
}
problems include:
your save runs in the background and isn't complete before you try to reload
your reload doesn't update or reset the skip and limit values
Unless you need to check for updates from other users then you shouldn't make a new request to the server to get new details. Instead you should add a completion block on the save and in there:
get the name and id and add those values to your data source arrays
update the skip value by adding one

Related

Swift - Firebase - order collection

I am trying to retrieve some documents but I need them to be ordered by some data ("ListIDX") inside my "Wishlists" - collection.
I tried this but that's not allowed:
db.collection("users").document(userID).collection("wishlists").order(by: "ListIDX").document(list.name).collection("wünsche").getDocuments()
This is my function:
func getWishes (){
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
var counter = 0
for list in self.dataSourceArray {
print(list.name) // -> right order
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments() { ( querySnapshot, error) in
print(list.name) // wrong order
if let error = error {
print(error.localizedDescription)
}else{
// DMAG - create a new Wish array
var wList: [Wish] = [Wish]()
for document in querySnapshot!.documents {
let documentData = document.data()
let wishName = documentData["name"]
wList.append(Wish(withWishName: wishName as! String, checked: false))
}
// DMAG - set the array of wishes to the userWishListData
self.dataSourceArray[counter].wishData = wList
counter += 1
}
}
}
}
This is what I actually would like to achieve in the end:
self.dataSourceArray[ListIDX].wishData = wList
Update
I also have a function that retrieves my wishlists in the right order. Maybe I can add getWishesin there so it is in the right order as well.
func retrieveUserDataFromDB() -> Void {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
db.collection("users").document(userID).collection("wishlists").order(by: "listIDX").getDocuments() { ( querySnapshot, error) in
if let error = error {
print(error.localizedDescription)
}else {
// get all documents from "wishlists"-collection and save attributes
for document in querySnapshot!.documents {
let documentData = document.data()
let listName = documentData["name"]
let listImageIDX = documentData["imageIDX"]
// if-case for Main Wishlist
if listImageIDX as? Int == nil {
self.dataSourceArray.append(Wishlist(name: listName as! String, image: UIImage(named: "iconRoundedImage")!, wishData: [Wish]()))
// set the drop down menu's options
self.dropDownButton.dropView.dropDownOptions.append(listName as! String)
self.dropDownButton.dropView.dropDownListImages.append(UIImage(named: "iconRoundedImage")!)
}else {
self.dataSourceArray.append(Wishlist(name: listName as! String, image: self.images[listImageIDX as! Int], wishData: [Wish]()))
self.dropDownButton.dropView.dropDownOptions.append(listName as! String)
self.dropDownButton.dropView.dropDownListImages.append(self.images[listImageIDX as! Int])
}
// // create an empty wishlist
// wList = [Wish]()
// self.userWishListData.append(wList)
// reload collectionView and tableView
self.theCollectionView.reloadData()
self.dropDownButton.dropView.tableView.reloadData()
}
}
self.getWishes()
}
For a better understanding:
git repo
As #algrid says, there is no sense to order the collection using order() if you are going to get an specific element using list.name at the end, not the first or the last. I would suggest to change your code to:
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments()
I am trying to retrieve some documents but I need them to be ordered by some data ("ListIDX")
The following line of code will definitely help you achieve that:
db.collection("users").document(userID).collection("wishlists").order(by: "ListIDX").getDocuments() {/* ... */}
Adding another .document(list.name) call after .order(by: "ListIDX") is not allowed because this function returns a Firestore Query object and there is no way you can chain such a function since it does not exist in that class.
Furthermore, Firestore queries are shallow, meaning that they only get items from the collection that the query is run against. There is no way to get documents from a top-level collection and a sub-collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use the properties of documents in a single collection. So the most simple solution I can think of would be to use two different queries and merge the results client-side. The first one would be the above query which returns a list of "wishlists" and the second one would be a query that can help you get all wishes that exist within each wishlist object in wünsche subcollection.
I solved the problem. I added another attribute when saving a wish that tracks the index of the list it is being added to. Maybe not the smoothest way but it works. Thanks for all the help :)

iOS How to fetch data for each user parse Swift

How can I fetch data for user which I want.
For this I want use text field where I write username, and button which will load all information about user.
Now my code load all user from data base:
let query = PFUser.query()
query?.findObjectsInBackground(block: { (object, error) in
for objects in object! {
let user = objects["username"] as! String
print(user)
}
})
How I can make it for user which I want?
You could filter the users locally like this:
query?.findObjectsInBackground {(objects, error) in
let users = objects as? [PFUser]
for object in (users!.filter { $0.username == something }) {
// do something
}
}
But that's a pretty bad idea because your list of users might be huge and this is wildly inefficient. If you're using PFQuery, you might want to add a constraint:
let query = PFUser.query()
query.whereKey("username", equalTo: something)
Now, when you call findObjectsInBackground, you'll only get relevant results.
You are getting user list in object at:
query?.findObjectsInBackground(block: { (object, error) in
Put a where clause in for loop, to get a single user by matching your required username as:
for objects in object! where (objects["username"] as! String) == "Your_Text_Field.text" {
print(objects)
let userName = objects["username"] as! String
print(userName)
}
If you don't want to match exact name rather you want to get a list of user by searching with entered text, then you can apply filter:
query?.findObjectsInBackground {(object, error) in
if let users = object as? [PFUser] {
let filteredUsers = users.filter { $0.username.contains("Your_Text_Field.text") }
print(filteredUsers)
}
}

How do I fill these arrays in order to then populate a tableView?

I am trying to fetch some data from the backend and then assign it to 3 different arrays. These arrays I then want to use to populate my tableViewCells. The issue is, when I print my arrays outside of the fetch block, they return nil. When I print them in the fetch block, they return the object's variables which I intend it to do so.
I include the self.tableView.reloadData() line in the hope that the arrays get populated and subsequently fill the tableViewCells, but it doesn't seem to be working.
Any suggestions welcomed on how to get those arrays populated correctly so when I print them outside of the fetch request they return the appropriate data.
var capArray = [String]()
var imageDic = [String: [PFFile]]()
var priceArray = [Int]()
override func viewDidAppear(animated: Bool) {
capArray.removeAll(keepCapacity: true)
imageDic.removeAll(keepCapacity: true)
priceArray.removeAll(keepCapacity: true)
let query = PFQuery(className: "SellerObject")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for o in objects {
if o.objectForKey("caption") != nil && o.objectForKey("imageFile") != nil && o.objectForKey("price") != nil {
let cap = o.objectForKey("caption") as? String
self.capArray.append(cap!)
let imdic = o.objectForKey("imageFile") as? [PFFile]
self.imageDic[cap!] = imdic
let price = o.objectForKey("price") as? String
let priceInt = Int(price!)
self.priceArray.append(priceInt!)
}
}
}
}
self.tableView.reloadData()
}
Your issue is self.tableView.reloadData() is executing before the block has finished executing.
The simplest thing to see it working is to move self.tableView.reloadData() inside the block, but that is a short term hack just to see things in action.
But that is not the correct long-term approach. To do this properly your array population code should really be in a model.
Your table view would load initially with no data to use, your model would notify your view controller as data becomes available and the view controller then updates the table.
If the data comes from the device the table view could wait before it loads if the data can be retrieved quickly, if the data is coming over a network it needs to wait.

Swift Parse - local datastore and displaying objects in a tableview

I am building and app that saves an object in the local datastore with parse. I then run a query to retrieve the objects that are in the local datastore and it is working fine. however, I would like to grab the object, and the contents in it, and set some labels in a table view cell based on the items that are stored in the parse local data store object. for example, i make an object with attributes like "objectID", "name", "date", "location". what i'd like to do is to have a table view on the home screen that displays the name, date, location ...etc. of each item that was saved in local datastore in labels in each cell.
i know that im saving it correctly:
// parse location object
let parseLighthouse = PFObject(className: "ParseLighthouse")
parseLighthouse.setObject(PFUser.currentUser()!, forKey: "User")
parseLighthouse["Name"] = self.placeTitle.text
parseLighthouse["Note"] = self.placeNote.text
parseLighthouse["Locality"] = self.placeDisplay.text!
parseLighthouse["Latt"] = self.map.region.center.latitude
parseLighthouse["Longi"] = self.map.region.center.longitude
parseLighthouse["LattDelta"] = 0.5
parseLighthouse["LongiDelta"] = 0.5
parseLighthouse["Date"] = dateInFormat
parseLighthouse.pinInBackground()
parseLighthouse.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved. ID = \(parseLighthouse.objectId)")
}
and when i run the query, im able to access the attributes by running println(object.objectForKey("Name"))
func performQuery() {
let query = PFQuery(className: "ParseLighthouse")
query.fromLocalDatastore()
query.whereKey("User", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
println(object.objectForKey("Name"))
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
because when running the query, i get back the object id and name as expected.
Successfully retrieved 2 lighthouses.
Optional("A3OROVAMIj")
Optional(happy)
Optional("bbyqPZDg8W")
Optional(date test)
what I would like to do is grab the name field within the parse object local data store, and that be the name of the label on a cell in a table view controller.
i dont know how to access that info from the object, and set the label correctly.
does anyone know how this is possible?
It's always a good idea to avoid pointer lol ... so why not saving the userid or username with the specific object..
so change this line:
parseLighthouse.setObject(PFUser.currentUser()!, forKey: "User")
TO
parseLighthouse["username"] = PFUser.currentUser().username
Answer
NOW let's create a struct that contains the objectID and the Name outside of your Controller Class.
struct Data
{
var Name:String!
var id:String!
}
then inside of the Controller class, declare the following line of code globally
var ArrayToPopulateCells = [Data]()
Then your query function will look like :
func performQuery() {
let query = PFQuery(className: "ParseLighthouse")
query.fromLocalDatastore()
query.whereKey("User", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
print(object.objectId)
print(object.objectForKey("Name"))
var singleData = Data()
singleData.id = object.objectId
singleData.Name = object["Name"] as! String
self.ArrayToPopulateCells.append(singleData)
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
In the tableView numberOfRowinSection()
return ArrayToPopulateCells.count
In the cellForRowAtIndexPath()
var data = ArrayToPopulateCells[indexPath.row]
cell.textlabel.text = data.objectID
cell.detailLabel.text = data.Name
VOila that should be it

findObjectInBackgroundWithBlock nested ios

I am developing app using ios, swift and parse.com as backend.
My problem is I need one query object result in second query object like below code. but when i use below code GUI become unresponsive for some time because of findObjects() method. I have used findObjectsInBackgroundWithBlock() instead but than tableview self.posts display only one record in tableview. I have 10 record in post table.
Can you guide me proper way how to resolve below issue.Actually I does not want to use findObjects() method.
var query = PFQuery(className:"Post")
var fquery = PFQuery(className: "Friends")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
let user = PFUser.currentUser()
if let objects = objects as? [PFObject] {
for object in objects {
friendArray.removeAll(keepCapacity: false)
fquery.whereKey("whosefriend", equalTo: object["postusername"])
var fobjects = fquery.findObjects()
for fobject in fobjects {
friendArray.append(fobject["friendname"] as String)
}
if (contains(friendArray, user["fullname"] as String)) {
let post = Post(.......)
self.posts.append(post)
}
}
}
self.tableView.reloadData()
} else {
println("Error: \(error) \(error.userInfo!)")
}
}
One option is to make your "postusername" a pointer column in class Post that points to Friends class and then you would only need one query that would go something like:
var query = PFQuery(className:"Post")
query.includeKey("postusername") //this would include the object that it points to i.e. the Friends object you saved there
... then in your for loop ...
for object in objects! {
let friend = object["postusername"] // now friend is the Friends object
let friendName:String = friend["friendname"] as? String
friendArray.append(friendName)
}
Note: this requires you saving "postusername" as a PFObject of Class Friends. Parse iOS docs explain this well.
https://parse.com/docs/ios/guide
I have resolve the issue by using relational query.
var query = PFQuery(classWithName: "Post")
var fQuery = PFQuery(className:"Friends")
fQuery.whereKey("friendname", equalTo: cuser["fullname"])
query.whereKey("postusername", matchesKey:"whosefriend", inQuery:fQuery)

Resources