Difference between generate and append when querying and adding data to array? - swift

var objectarray = [PFObject]()
func populateTable() {
query.findObjectsInBackgroundWithBlock { (objects, error) in
self.objectarray.removeAll(keepCapacity: true)
self.searchTableView.reloadData()
if error == nil {
Above is the query I am doing and the below 2 codes are what I can use to use the query to populate a array.
if let objects = objects as [PFObject]! {
self.objectarray = Array(objects.generate())
}
Is there any difference with running this code above to populate my array or running the code below?
for object in objects! {
self.objectarray.append(object)
}
Doing either works to load onto my tableView. Also another question regarding Parse. After doing the above, the user doesn't download PFFiles from the background until I run
getDataInBackgroundWithBlock
right? I want to know if it'd be beneficial to save smaller versions of images onto the server.

Related

Swift: Wait for firestore load before next load

I have a view controller that lists data from a firestore database. Inside a firestore collection, I have a bunch of documents with the information shown in the list, and one document called order which contains one field which is an array of strings in the order I want them displayed. My code grabs this:
self.db.collection("officers").document(school).collection(grade).document("order").getDocument {(document, error) in
if let document = document, document.exists {
self.officerNames = (document.data()!["order"] as! Array<String>)
and then is supposed to use the strings in the array order (officerNames) to query the documents in that same collection (all the documents have a different role so it's only getting one document in the snapshot) and display them in the same order as the one set in order (officerNames).
for item in 1...self.officerNames.count {
self.db.collection("officers").document(school).collection(grade).whereField("role", isEqualTo: self.officerNames[item-1]).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let officerMessage = document.data()["agenda"] as! String
let officerInfo = document.data()["short"] as! String
(a bunch of code here using that ^ ^ and due to the color I need item to be an integer)
}
}
}
}
I know that if I try printing item before the self.collection("officers")..... the numbers count by one but if I do that in the for document in querySnapshot..... they're all out of order meaning some documents are loaded faster than others. I have read about Async functions in Swift (although I do use those in JavaScript) but am really confused how to use them and hopefully, there is a simpler way to do this. Any way I can wait to make sure the previous document has been loaded and analyzed before iterating through the loop again?
Here's a screenshot of the database:
Honestly, you may want to examine your data structure and see if you can create one that doesn't require multiple queries like this. I can't quite tell what your data structure is, but if you update your question to include it, I can give some suggestions for how to refactor so you don't have to do 2 different get requests.
That being said, since Swift doesn't have promises like JS, it can be tough to keep data in order. For most cases, closures work well, as I wrote about in this blog. But they still won't preserve order in an array of async calls. Assuming you're using some array to store the officer's data, you can declare the size of the array up front by giving each one a default value. This would look something like this:
var officerArray = [Officer](repeating:Officer(), count: self.officerNames.count)
Of course, it'll be different depending on what kind of objects you're populating it with. I'm using some generic Officer object in this case.
Then, rather than appending the newly created Officer object (or whatever you're calling it) to the end of the array, add its value to its particular location in the array.
for item in 1...self.officerNames.count {
self.db.collection("officers").document(school).collection(grade).whereField("role", isEqualTo: self.officerNames[item-1]).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let officerMessage = document.data()["agenda"] as! String
let officerInfo = document.data()["short"] as! String
// etc
officerArray[self.officerNames.count-1] = Officer(officerMessage: officerMessage, officerInfo: officerInfo) // or however you're instantiating your objects
}
}
}
}
This preserves the order.

Parse display images in collectionView

I have a tableView with a CollectionView inside, in the TableView is the users details and I am trying to put the images belonging to the user in the collectionView.
So far I have everything working correctly apart from the images are not correct. Every image uploaded is displaying for each user, I need match the images to the user...I have been trying for a while now and have not found a solution so I'm hoping someone can help me here!
Here is my query for getting the user details and images:
func loadPosts() {
let followQuery = PFQuery(className: "Follows")
followQuery.whereKey("follower", equalTo: PFUser.currentUser()!.username!)
followQuery.findObjectsInBackgroundWithBlock ({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.followArray.removeAll(keepCapacity: false)
for object in objects! {
self.followArray.append(object.valueForKey("following") as! String)
}
self.followArray.append(PFUser.currentUser()!.username!)
let query = PFQuery(className: "Posts")
query.whereKey("usernamee", containedIn: self.followArray)
query.addDescendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.usernameArray.removeAll(keepCapacity: false)
self.imageArray.removeAll(keepCapacity: false)
self.uuidArray.removeAll(keepCapacity: false)
for object in objects! {
self.usernameArray.append(object.valueForKey("username") as! String)
self.imageArray.append(object.valueForKey("imageArray") as! PFFile)
self.uuidArray.append(object.valueForKey("uuid") as! String)
}
self.tableView.reloadData()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
Best regards.
There are three issues I see in your code. First of all, in your Posts query, you use the whereKey() method: query.whereKey("usernamee", containedIn: self.followArray). username is spelled incorrectly, and this may be causing some issues. In addition, you remove all objects from your array every time you issue a query. There must be some way to add and remove data as necessary. (This only applies if you are refreshing data. If you have not added data to the arrays, there is no need to empty them.) Finally, you have a value called imageArray that is stored as a PFFile. An array would be multiple objects. It is hard to tell whether or not each user has multiple images or not. (For the solution, I assume that there is only one image per user. I would suggest changing the name of the key to be more precise. If this is not the case, write me a comment.)
Now, in regards to your actual problem, I believe that this is because you have only one array for all the images, and when you display it for each user, the entire array is used. In order to correct for this, I would suggest building a data structure in the following format: [(String, Image)], where String is the username and Image is file for the image. You can thus access the users and their images as an array of tuples.
To create the array of tuples, I would suggest: var usersAndImagesArray = [(username: String, imageFile: PFFile)]()
To add to the array:
for object in objects!{
usersAndImagesArray.append(object.valueForKey("username") as! String, object.valueForKey("image") as! PFFile)
}
To access the values when setting up the cells:
let username = usersAndImagesArray[index.row].username
let imageFile = usersAndImagesArray[index.row].imageFile

PFQuery categorizing results into multilevel arrays

I am trying to do a multiple PFQuery and storing the data properly.
My plan is to query for group members in my Group
I am looking for the key member. It gives me an array of objectIds. Then I want to query in my _User class for their picture with key firstImage. Displaying the data is not the problem, just getting it the right way.
The data needs to be stored considering it's groups. This is what i tried so far:
let fetchGroupsQuery = PFQuery(className: "Group")
fetchGroupsQuery.whereKey("member", equalTo: objectID!)
fetchGroupsQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error != nil
{
}
else {
if let objects = objects {
self.memberPhotos.removeAll()
for object in objects {
if let member = object["member"] as? [String]
{
// returns array of objectIds
for user in member
{
// returns single ids
self.groupIds.append(user)
let photoQuery = PFUser.query()
photoQuery?.whereKey("objectId", equalTo: user)
photoQuery?.findObjectsInBackgroundWithBlock({ (results, error) -> Void in
if error != nil {
// ....
}
else {
self.memberPhotos.removeAll()
if let results = results
{
for result in results
{
if result["firstImage"] != nil
{
self.memberPhotos.append(result["firstImage"])
}
}
}
}})
}
}
My idea was to store the data within an array of arrays.
So that I can later go into array[indexPath.row] to get the array I need to loop through in order to get the right group pictures.
Any ideas how to solve this?
I would use two separate queries function:
1) to get their ID's
2) for their pictures.
For the first query: what you need to change?
Your objectID array should be of type of NSMutableArray because in order to fetch their info you should use the constraint whereKey containedIn not whereKey equalTo
and for that constraint you should downcast your NSMutableArray to type AnyObject.
Also change the Type of your groupIds to NSMutableArray, then append your array.
For the Second query:
Since your groupIds Type is already NSMutableArray just cast it to AnyObject,then use the whereKey containedIn
Note: It would be better to download all images before appending your
array. So you have the option of using struct or class(easier to
group data to its owner) then create an array of that data
structure.Where it because easier to populate your UI.

Swift Array is Empty After Parse Queries - Completion Handler?

I don't understand why the arrays become empty after the query with block. I did some research and it's most likely because I need a completion handler, but I can't figure out how to implement it in this case. Can I just add an activity indicator until the method is done?
var usernamesFollowing = [""]
var useridsFollowing = [""]
func refresh(completion: (Bool)){
//find all users following the current user
var query = PFQuery(className: "Followers")
query.whereKey("following", equalTo: PFUser.currentUser()!.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil {
//remove all from arrays
self.usernamesFollowing.removeAll(keepCapacity: true)
self.useridsFollowing.removeAll(keepCapacity: true)
//get all userIds of following current user and add to useridsFollowing array
if let objects = objects {
for userId in objects {
var followerId = userId["follower"] as! String
self.useridsFollowing.append(followerId)
//get usernames from followerId and add to usernamesFollowing array
var query = PFUser.query()
query!.whereKey("objectId", equalTo: followerId)
query!.findObjectsInBackgroundWithBlock({ (objects2, error) -> Void in
if let objects2 = objects2 {
for username in objects2 {
var followerUsername = username["username"] as! String
self.usernamesFollowing.append(followerUsername)
}
}
//WORKS. usernamesFollowing array is now full.
println(self.usernamesFollowing)
})
//BROKEN. usernamesFollowing array is now empty outside of block.
println(self.usernamesFollowing)
}
}
}
//WORKS. useridsFollowing is now full.
println(self.useridsFollowing)
})
//BROKEN. usernamesFollowing is now empty outside of block.
println(self.usernamesFollowing)
}
To elaborate on Larme's point - asynchronous methods return immediately, and dispatch the work into another queue. To put this in context, consider your two println statements:
println(self.usernamesFollowing) //1. inside async fetch closure
println(self.usernamesFollowing) //2. outside async fetch closure
The asynchronous method will take your closure and dispatch it on to a different queue. After doing so, it returns immediately, and continues to execute your code, which goes to your 2nd println statement right away. In this situation, your second println statement will actually print before your first.
If possible, do all your data manipulations within the block. It'll save you a lot of work. If you must offload the objects outside of the block, consider using NSOperations, which is perfectly equipped to deal with that type of scenario.

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.