Reading from an array before appending items to it (crash) - ios

Under the class initialization I set:
var cardsCover = [PFFile]()
I have two functions. One of them take from Parse.com data and append it to arrays:
let cards = PFQuery(className: "cards")
cards.whereKey("category", equalTo: "Fruits")
cards.findObjectsInBackgroundWithBlock { (cards: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for card in cards! {
self.cardsCover.append(card["cover"] as! PFFile)
}
} else {
print("error")
}
}
and the second function takes from this cardsCover array and show the items:
self.cardsCover[self.cardIndex].getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if imageData != nil {
let image = UIImage(data: imageData!)
let imageView = UIImageView(image: image!)
contentView.addSubview(imageView)
} else {
print(error)
}
}
but when I launch my app it at first try to show images from an empty array, instead of appending items to array and just later to read from it. So, it crashes.
I set breakpoint to the first line of my second func and when I type:
po cardsCover
it returns 0 elements.
How can I solve this problem and make at first, append items to my array and just later to read them from it?

First of all, while fetching elements from an Array, always put a safe check on array size to avoid the crash.
Second, you need to ensure that function to read data from cardsCover gets always called after data setter function is called. You have a completion block in data fetcher where in you can trigger the data fetcher. You can also implement delegate callbacks or post a notification once data download is done so data usage could start thereafter.

I think getDataInBackgroundWithBlock is asynchronous API (sounds like its running off the main queue) so you need to dispatch to the main queue. Here is how to do it
if self.cardsCover.count > self.cardIndex {
self.cardsCover[self.cardIndex].getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
if imageData != nil {
if let image = UIImage(data: imageData!){
let imageView = UIImageView(image: image!)
contentView.addSubview(imageView)
} else { print ("imageData can not be converted to image") }
} else {
print("There was no imageData")
}
}
}
} else { print("cardIndex bigger than array count") }

Related

Converting array of PFFile to UIImage in for loop returns empty array Swift

I ma trying to take an array of type PFFile and convert into an array of type UIImage. I use the below code and no matter what it always returns an empty array. Any help is much appreciated.
userImagesPassed is an array of type PFFile.
func setUserImages() -> Array<UIImage> {
var userImageArray = [UIImage]()
for file2 in userImagesPassed {
file2.getDataInBackground(block: { (imageData2, error) in
if error != nil {
userImageArray = []
} else {
userImageArray.append(UIImage(data: imageData2!)!)
}
})
}
return userImageArray
}
EDIT:
If I print the userImageArray inside the for loop I get the correct output (a populated array of type UIImage). But the returned value is still an empty array.
first of all you shouldn't use return statement for async functions. You should use completion handlers to get your items back. This is the example how I get images from Parse and append to array.
Swift 4.0
public func getImages(_ completion: #escaping (([UIImage]) -> Void)) {
guard let tempImages = self.images else { return }
var images = [UIImage]()
for image in tempImages {
image.getDataInBackground() {
( imageData, error) in
guard error == nil else { return }
guard let imageData = imageData else { return }
guard let image = UIImage(data: imageData) else { return }
images.append(image)
}
}
completion(images) // Its returning your images
}
You can call your getImages function like this;
let images = self.yourObjectArrayInClass[(indexPath).row]
images.getImages() {
(images) in
DispatchQueue.main.async {
// Do something with your images.
}
}

For each loop error in Parse image query

I'm writing a function to query the class Photos with a given Object Id in order to download a photo and set it to the UIImageView "background." I've narrowed the issue down to "for object in objects!" which I've commented in the code below. This seems like standard practice for casting, but the code won't run past this point. It compiles and no errors are thrown, but it fails to print anything past the "for" line, much less set the background.
// set new background image
func imageSet(objId: String) {
var query : PFQuery = PFQuery(className: "Photos")
query.whereKey("objId", equalTo:objId)
query.findObjectsInBackgroundWithBlock {
(objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil {
println("First query")
// last working line
for object in objects! {
println("Won't print here")
// won't pass this point
let userImageFile = object["image"] as! PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error:NSError?) -> Void in
if error == nil {
println("Or here")
self.background.image = UIImage(data:imageData!)
}
}
}
}
else {
println("\(error)")
}
}
}
Any tips are greatly appreciated! Thanks!

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

Profile picture in cell not updating correctly

I have a PFQueryTableViewController populated by comments from different users. Each user has a profile picture stored on the Parse database. After loading each comment into a cell, I query the PFUser class to retrieve the profile picture of the user who posted the comment and add it to the cell. I also use PFCachePolicy to cache the profile picture to the device's memory so that displaying new cells with new profile pictures is a smoother transition.
However this is not the case. When a user posts a new comment and a new cell is added, the profile pictures shuffle around and takes about two seconds or so to update with the right image (probably because the table is re-queried and updated). I am trying to achieve something similar to iMessage or WhatsApp where the profile picture remained 'fixed' in the cell.
I am not sure what the problem is or if there is a better way to do this?
// get objectId of the user who posted a comment
let senderId = object?["Users"]!.objectId as String!
// query PFUser class using senderId to retrieve profile picture
var senderImage:PFQuery = PFUser.query()!
senderImage.cachePolicy = PFCachePolicy.CacheThenNetwork
senderImage.getObjectInBackgroundWithId(senderId){
(sender: PFObject?, error: NSError?) -> Void in
if error == nil && sender?.objectForKey("profilePicture") != nil {
let thumbnail = sender?.objectForKey("profilePicture") as? PFFile
thumbnail?.getDataInBackgroundWithBlock({
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
imageView.image = UIImage(data:imageData!)
} else {
println(error)
}
})
}
}
That's because you're not waiting until the images are finished loading when you update the UIImageView. Try Using this code:
var query = PFQuery(className:"Users")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
self.scored = objects!.count
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
let userImageFile = object["Image"] as! PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
self.imageArray.append(image!)
}
}
dispatch_async(dispatch_get_main_queue()) {
//don't reload image view here!
}
}
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
dispatch_async(dispatch_get_main_queue()) {
//wait until here to reload the image view
if self.imageArray.isEmpty == false {
//image array is not empty
self.ImageView.image = imageArray.first
}
else {
//no images found in parse
}
}

Resources