PFQuery in TableCell Cause Images to Jump - ios

So I have researched and haven't found anything similar to my situation. All relevant code will be posted below. I am using parse for my backend and am trying to create a feed with people posted images. The images and username load fine, but since the display name and profile picture are in a separate file i had to query them separately. The query for those is located in the table cell itself. This causes images to jump while scrolling. I am aware that is because of the way cells dequeue. Is there a better way to either query or relate the data from both parse queries?
var usernameArray = [String]()
var detailArray = [String]()
var uuidArray = [String]()
var postArray = [PFFile]()
var followArray = [String]()
var page: Int = 10
var image: UIImage!
var follow = PFObject(className: "Follow")
func loadFollowers(){
let followerQuery = PFQuery(className: "Follow")
followerQuery.whereKey("follower", equalTo: (PFUser.current()?.username!)! as String)
followerQuery.whereKey("blocker", notEqualTo: (PFUser.current()?.username!)! as String)
followerQuery.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
if error == nil{
self.followArray.removeAll(keepingCapacity: false)
for object in objects!{
self.followArray.append(object.object(forKey: "following") as! String)
self.followArray.insert((PFUser.current()?.username!)! as String, at: 0)
}
let dataQuery = PFQuery(className: "Posts")
dataQuery.whereKey("user", containedIn: self.followArray)
dataQuery.limit = self.page
dataQuery.addDescendingOrder("_created_at")
dataQuery.findObjectsInBackground(block: { (objects: [PFObject]?, error: Error?) in
if error == nil{
self.usernameArray.removeAll(keepingCapacity: false)
self.detailArray.removeAll(keepingCapacity: false)
self.postArray.removeAll(keepingCapacity: false)
self.uuidArray.removeAll(keepingCapacity: false)
for object in objects!{
self.usernameArray.append(object.object(forKey: "user") as! String)
self.detailArray.append(object.object(forKey: "title") as! String)
self.postArray.append(object.object(forKey: "picture") as! PFFile)
self.uuidArray.append(object.object(forKey: "uuid") as! String)
self.tableView.reloadData()
}
}
})
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postView") as! FeedTableViewCell
cell.userName.text = usernameArray[indexPath.row]
cell.details.text = detailArray[indexPath.row]
cell.uuid.text = uuidArray[indexPath.row]
let query = PFUser.query()
query?.whereKey("username", equalTo:cell.userName.text!)
query?.findObjectsInBackground(){(objects: [PFObject]?, error: Error?) -> Void in
if !(error != nil){
for object in (objects as [PFObject]?)!{
cell.displayName.text = object.object(forKey: "display") as? String
if let userPicture = object.object(forKey: "avi") as? PFFile {
userPicture.getDataInBackground(block: { (imageData: Data?, error: Error?) in
if (error == nil) {
let image = UIImage(data:imageData!)
cell.avi.image = image
}
})
}
}
}
}
aviArray[indexPath.row].getDataInBackground { (data: Data?, error: Error?) in
if error == nil{
cell.avi.image = UIImage(data: data!)
}
}
postArray[indexPath.row].getDataInBackground { (data: Data?, error: Error?) in
if error == nil{
cell.post.image = UIImage(data: data!)
}
}
return cell
}
Thank you in advance for the help. I am open to all suggestions because I am at a loss for this. Im not an expert so there is probably a much better way to do this.

Get the actual URL of the image, and set the images with SDWebImage.
No need to reinvent the wheel anymore. It will solve this problem and reduce the complexity of your code.

Listen up, my dude, you should create a pointer in the Follow table that stores the _User objects. Running a PFQuery like that in "cellForRowAt indexPath" is bad news. You should do everything in your power to structure the backend data on Parse to inter-relate in such a way where you can pull all data like that in the original query. However, it is safe to run small queries in the "cellForRowAt indexPath" using things like "strings" pulled from the server, images not so much, but, it's okay to load images in the background inside "cellForRowAt indexPath", but running full queries on images like that is not a good idea. My suggestion is use PFImageView and connect your Follow table to the _User table on the backend, this is possible, and it's the right way to do things. Also, you should consider inserting all images into an array when they are pulled in, what you're doing there is running a query on every cell every damn time the table view is updated, this is horrible.

Related

How to retrieve a image from Parse?

I'm trying to get an image from a parse server that is hosted on Heroku. So far I have gotten the Text values but the images are the only problem.
Here is my code so far:
// Getting Info from servers
let dataQ = PFQuery(className: "message")
dataQ.findObjectsInBackground { (objects: [PFObject]?, error: Error?) -> Void in
if error == nil {
for object in objects! {
self.sentFromLabel.text = object["sender"] as? String
// Get Image from server
object["Picture"].getDataInBackgroundWithBlock
}
} else {
print(error)
}
}
So far when I use .getDataInBackgroundWithBlock it gives me an error. I have no clue why. What is the Swift 3 version to allow me to get an image from the server?
Here is a swift 3 example!
#IBOutlet weak var fullImage : UIImageView!
//place this in your for loop
let imageFile = (object.object(forKey: "ImageFile") as? PFFile)
imageFile?.getDataInBackground (block: { (data, error) -> Void in
if error == nil {
if let imageData = data {
//Here you can cast it as a UIImage or do what you need to
self.fullImage.image = UIImage(data:imageData)
}
}
})

Retrieve multiple Parse Images(Swift)

Below is an example of how i would typically retrieve images from my Parse.com. I have now run into the situation where i would like to retrieve 20+ images from Parse but i am looking for a more efficient way to do so. Please can someone explain how to implement this in code and how i should store the 20+ PFFiles in Parse?
func loadData(){
let findDataParse = PFQuery(className: "JobListing")
findDataParse.findObjectsInBackgroundWithBlock{
(objects: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
for object in objects! {
let userImageFile = object["ImageOne"] as! PFFile
let userImageFile1 = object["ImageTwo"] as! PFFile
let userImageFile2 = object["ImageThree"] as! PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
let listingImage1 = UIImage(data:imageData!)
userImageFile1.getDataInBackgroundWithBlock {
(imageData1: NSData?, error1: NSError?) -> Void in
let listingImage2 = UIImage(data:imageData1!)
userImageFile2.getDataInBackgroundWithBlock {
(imageData2: NSData?, error1: NSError?) -> Void in
let listingImage3 = UIImage(data:imageData2!)
self.flyerImageLarge1.image = listingImage1
self.flyerImageLarge2.image = listingImage2
self.flyerImageLarge3.image = listingImage3
}}}}}}}
You can use ParseUI for cleaner and more efficient code.
To do so, add the ParseUI framework.
Then, click the image and change the "class" to PFImageView.
You can see this here.
Once you do that, you can easily set the PFImageView's image:
if let myServerImage = object.valueForKey("imageFromUser") as? PFFile {
self.myImage.file = myServerImage
self.myImage.loadInBackground()
}
Where myServerImage is the image you are retrieving from the Parse server and myImage is the image in your storyboard.
In your case it will be something like this:
func loadData(){
let findDataParse = PFQuery(className: "JobListing")
findDataParse.findObjectsInBackgroundWithBlock{
(objects: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
if let myServerImage = object.valueForKey("ImageOne") as? PFFile {
self.flyerImageLarge1.file = myServerImage
self.flyerImageLarge1.loadInBackground()
}
if let myServerImage = object.valueForKey("ImageTwo") as? PFFile {
self.flyerImageLarge2.file = myServerImage
self.flyerImageLarge2.loadInBackground()
}
if let myServerImage = object.valueForKey("ImageThree") as? PFFile {
self.flyerImageLarge3.file = myServerImage
self.flyerImageLarge3.loadInBackground()
}
}
}
I recommend adding the if let statement so that you don't get an error when an image doesn't exist.
Please forgive me if what I am saying is obvious or already considered. I'm not completely familiar with what you are doing, but it looks applicable.
Assuming you are not using it already, you will need to use a recursive function. Basically, a function that calls itself until the end condition.
I'm not familiar with your code so I'll demonstrate with a simple example in JavaScript:
/* A "public" function that your program will call */
function getStuff(total)
{
//quick positive check
if (total > 0)
{
//start and pass in an empty array
return _getStuffRecursion(total, []);
}
else
{
//total is not positive, return empty array
return [];
}
}
/* A "private" function that will do the recursion */
function _getStuffRecursion(total, resultsArray)
{
//do work this is where you would call your function that does the work.
var someResource = Math.random();
//add work to the array collected so far
resultsArray.push(someResource);
//change count
var newTotal = total - 1;
//check condition
if (newTotal > 0)
{
//recursive condition, go to the next level down and pass in what is collected so far
return _getStuffRecursion(newTotal, resultsArray)
}
else
{
//end condition met, just return the array with everything collected from the upper levels
return resultsArray;
}
}
/* Start */
//get started by calling the "public" function
var results = getStuff(20);
//print it to console
console.log(results);
If this solution works, I'm sure you can adapt it to Parse.

Swift Parse - Nested Calls Issue

In my app, I need to load user details in different view controllers. The details I load are not necessarily of the user that is currently signed in (which could be retrieved from currentUser, but sometimes are of other users depending on scenario.
So far I find myself whenever presented with the above need, I do two queries: First to load the details (e.g. name, phone, address, etc.) and second to load the profile image of that user.
So I end up with a nested call such as the below:
let query = PFQuery(className: "_User")
query.whereKey("appUsername", equalTo: self.friendObject.username!)
query.findObjectsInBackgroundWithBlock { (results, error) -> Void in
let data = results as [PFObject]!
if(error == nil)
{
self.friendObject.name = data[0]["name"] as? String
self.friendObject.profilePic = data[0]["ProfilePic"] as? UIImage
self.nameLabel.text = self.friendObject.name
self.friendObject.objectId = data[0].objectId! as String
self.getProfilePicture(self.friendObject.username!) { (result)->Void in
self.profilePicImageView.image = result
}
}else{
print("Error retrieving user details - try again")
}
}
and here is the definition of the getProfilePicture function:
func getProfilePicture(username: String, completion: (result: UIImage) -> Void)
{
var tempImage:UIImage? = UIImage(named: "sample-qr-code.png")!
let query: PFQuery = PFQuery(className: "_User")
query.whereKey("appUsername", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects:[PFObject]?, error:NSError?) -> Void in
for object in objects! {
if(object["ProfilePic"] != nil)
{
let imageFiles = object["ProfilePic"] as! PFFile
imageFiles.getDataInBackgroundWithBlock({
(imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
tempImage = UIImage(data:imageData!)!
let temp2Image: UIImage = Toucan(image: tempImage!).resize(CGSize(width: 100, height: 150)).maskWithEllipse(borderWidth: 3, borderColor: UIColor.whiteColor()).image
completion(result: temp2Image)
}
})
}else{
let invalidImage = UIImage(named: "Contacts-100.png")
completion(result: invalidImage!)
}
}
}
}
The ProfilePic column in the User parse class is of File type. How can I optimize this so that I only do one call to load image and details (given that requests should be minimized as much as possible).
Thanks,
Make your imageView a PFImageView and then you can set the appropriate file of the PFImageView like self.friendObject.profilePic = data[0]["ProfilePic"] as? PFFile and then you can do self.profilePicImageView.file = self.friendObject.profilePic and then self.profilePicImageView.loadInBackground

How do I load an image from Parse into Swift

I've got an image in Parse that I want to load as the image for a button.
Here's my code:
let myData = PFObject(className: "Headliner")
if let theImageFileINeed = myData["ImageFile"] as! PFFile? {
theImageFileINeed.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
print("loadingimage?")
if let imageData = imageData {
let image = UIImage(data: imageData)
self.headlinerImage.setImage(image, forState: UIControlState.Normal)
}
} else {
print("error")
}
}
}
Here's the code I'm referencing from the Parse documentation:
let userImageFile = anotherPhoto["imageFile"] as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
}
}
}
When I use that exact code (I'm putting this in viewDidLoad, but am not sure if that's correct), swapping out the name of my table for "anotherPhoto" in the example (imageFile is the name of my field, too, so I didn't have to change that), I get the following error message: "Use of unresolved identifier "Headliner". So, then I assumed that maybe this goes inside a query? Or I need to specify the table somehow I want to pull data from, so I added the myData variable to pull that in.
When I run this, I don't get an error message, but my button doesn't update the image from parse.
I suspect it is related to types, probably in that "let my data = PFObject(className: "headliner") line... But I don't know how to fix it...
Any help would be appreciated! I bake cookies, so I'll send you some if you help me fix this!!!
Mali
Load image in tableView from Parse using PFFile
First step, make sure you import parse library:
import Parse
second step, declare a PFFile array, something like that:
var imageFiles = [PFFile]()
third, store all the images in the array:
let query = PFQuery(className:"your_class")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil{
for writerData in objects! {
self.imageFiles.append(writerData["avatar"] as! PFFile)
}
/*** reload the table ***/
self.yourTableView.reloadData()
} else {
print(error)
}
}
Fourth, in order to show it (in my case I am displaying the images in UITableView), so in cellForRowAtIndexPath:
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! YourClassTableViewCell
imageFiles[indexPath.row].getDataInBackgroundWithBlock{
(imageData: NSData?, error: NSError?) -> Void in
if imageData != nil{
let image = UIImage(data: imageData!)
cell.cellAvatarImage.image = image
} else {
print(error)
}
}
Pretty sure this code should work. You may have to change the button settings in the interface builder to 'Custom'
Or maybe just create the button programmatically... See here:
How to create a button programmatically?
You need to try update your image in a different thread. ( This got me many times too)
Also I generally change the name unwrapped version of my variables so I can distinguish them easily.
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Image update code goes here
if let unWrappedimageData = imageData {
let image = UIImage(data: unWrappedimageData)
self.headlinerImage.setImage(unWrappedimageData, forState: UIControlState.Normal)
}
})

Better way to retrieve multiple images from Parse

Noob question here and I know my code below is very wrong but it works in that it retrieves the 3 images I need. However, I'd like to know a better way to retrieve multiple images from Parse.
Any help would be greatly appreciated!
func retrieveImage() {
var query = PFQuery(className: "Items")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
let imageObjects = objects as! [PFObject]
for (index, object) in enumerate(imageObjects) {
let thumbnail1 = object["image1"] as! PFFile
let thumbnail2 = object["image2"] as! PFFile
let thumbnail3 = object["image3"] as! PFFile
thumbnail1.getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let image = UIImage(data: imageData!) {
self.itemImages[index] = image
}
}
thumbnail2.getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let image = UIImage(data: imageData!) {
self.itemImages2[index] = image
}
}
}
thumbnail3.getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let image = UIImage(data: imageData!) {
self.itemImages3[index] = image
}
}
}
}
}
}
}
}
First the idea... we want to do an arbitrarily long list of asynch tasks, collect their results, and be notified on completion or error. We do this by parameterizing the task (in this case, the PFFiles whose contents are to be fetched are the parameters), and we use those parameters as a "to-do list".
A recursive function does the work, picking off the first item in the list, doing the asynch task, and then calling itself with the remainder of the list. An empty to-do list means we're done.
I've tried to translate the answer I referred to here into swift (literally learning the language line by line)....
func load(pfFiles: Array<PFFile>, var filling: Dictionary<PFFile, UIImage>, completion: (success: Bool) -> Void) {
completion(success: true)
var count = pfFiles.count
if (count == 0) {
return completion(success: true)
}
var file = pfFiles[0]
var remainder = Array(pfFiles[1..<count])
file.getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let image = UIImage(data: imageData!) {
filling[file.name] = image
self.load(remainder, filling: filling, completion: completion)
}
} else {
completion(success: false)
}
}
}
Given this is my first attempt, I'll be a little shocked and delighted if it works, but the algorithm is sound, and the swift compiles and appears to match the idea I outlined. Here's how to call it...
var pfFiles: Array<PFFile>
for (index, object) in enumerate(imageObjects) {
pfFiles.append(object["image1"])
pfFiles.append(object["image2"])
pfFiles.append(object["image3"])
}
var filling: Dictionary<String, UIImage>
// call the function here
// in the completion assign filling to property
// anytime after, when you have a PFFile like someObject["image2"]
// you use its name to look it up the UIImage in the results dictionary
Let me know if that last bit is clear enough. As you can see, I ran out of steam on my swift translation and resorted to pseudo code.
I believe you can just do self.itemImages[index] = thumbnail1.getData()!
If it crashs, do : query.includeKey("image1")
NOTE:
If you afraid to block the main queue, open a new thread to do such thing

Resources