reloadData after loop parse - ios

I'm trying to reload the tableView when this loop is finished, but whenever i try to put the self.tableView.reloadData() outside the loop, the loop returns 0. I guess this is due to i'm getting the data in the background. What can i do in order to reload after the loop is completed.
func loadData() {
var query = PFQuery(className: "Items")
query.includeKey("user")
query.getObjectInBackgroundWithId(itemId) {
(itemObject: PFObject!, error: NSError!) -> Void in
if (error == nil) {
var userObject = itemObject.objectForKey("user") as PFObject
let userImageFile = userObject.objectForKey("file") as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let image = UIImage(data:imageData)
self.detailDic?.setObject(itemObject.objectForKey("title"), forKey: "title")
self.detailDic?.setObject(itemObject.objectForKey("description"), forKey: "desc")
self.detailDic?.setObject(itemObject.objectForKey("location"), forKey: "point")
self.detailDic?.setObject(userObject.objectForKey("name"), forKey: "name")
self.detailDic?.setObject(userObject.objectForKey("gender"), forKey: "gender")
self.detailDic?.setObject(image, forKey: "image")
var relation = itemObject.relationForKey("file") as PFRelation
var imageQuery = relation.query() as PFQuery
imageQuery.findObjectsInBackgroundWithBlock { (imageObj: [AnyObject]!, error1: NSError!) -> Void in
if error1 == nil {
var imageDic:NSMutableArray = NSMutableArray()
let group = dispatch_group_create()
for obj in imageObj {
var imageObject = obj as PFObject
var thumbnail = imageObject.objectForKey("file") as PFFile
// Important: enter the group *before* starting the background task.
dispatch_group_enter(group)
thumbnail.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let theImage = UIImage(data:imageData)
theImage.CGImage
self.imageArray!.addObject(theImage)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.tableView?.reloadData()
}
}
}
}
}
}
}
}

Use a dispatch group. A dispatch group is basically a counter. You can increment and decrement it atomically, and you can ask to be notified when it is zero.
Each time you add a background task, “enter” the group (which increments its counter). When a background task finishes, “exit” the group (which decrements its counter). Use dispatch_group_notify to run a block when the counter is zero (meaning all background tasks have finished).
let group = dispatch_group_create()
for obj in imageObj {
var imageObject = obj as PFObject
var thumbnail = imageObject.objectForKey("file") as PFFile
// Important: enter the group *before* starting the background task.
dispatch_group_enter(group)
thumbnail.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let theImage = UIImage(data:imageData)
self.imageArray!.addObject(theImage)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.tableView?.reloadData()
}
Note that if there are no elements in imageObj, or if all of the background tasks finish before dispatch_group_notify runs, dispatch_group_notify will run the block immediately (which is what you want).
Also, UIImage doesn't necessarily decode the image data when you create it. It can do so lazily. You may want to force it to decode the image data on the background thread. You can do that by asking for its CGImage:
if error == nil {
let theImage = UIImage(data:imageData)
theImage.CGImage // force decoding immediately
self.imageArray!.addObject(theImage)
dispatch_group_leave(group)
}

You could also do:
for (index,obj) in enumerate(imageObj) {
.......
// reload after last loop
if(index == imageObj.count) {
self.tableView?.reloadData()
}
}

Related

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

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") }

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

reloadData when loop is done

I have a loadItem functions which is suppose to load items from a parse server. This include a loop where i'm at the end is saving the data to an array:
itemArray?.addObject(arrayDic)
When this is saved i would like to reloadData of the collectionView. therefor i've inserted it into a dispatch_async block, but it still seems like it is being run before the data is saved into the itemArray array since the itemArray.count is 0 and i've checked inside the loop that the data is saved into the array. What am i doing wrong?
func loadItems() {
var query = PFQuery(className:"Items")
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
var imageRelation: PFRelation = object.relationForKey("pictures")
var query = imageRelation.query()
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(imageObjects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
var theArray = imageObjects as NSArray
var arrayDic = NSMutableDictionary()
let imageFile = theArray.objectAtIndex(0).objectForKey("image") as PFFile
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !(error != nil) {
let image = UIImage(data:imageData)
arrayDic.setValue(object.objectForKey("price"), forKey: "price")
arrayDic.setValue(image, forKey: "image")
itemArray?.addObject(arrayDic)
}
}
}
}
}
dispatch_async(dispatch_get_main_queue()) {
println(itemArray?.count)
self.collectionView.reloadData()
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
}
I would not advise trying to make the requests synchronous (as that can significantly slow down the process, unless you scheduled them to run concurrently on some concurrent background queue). I would suggest, instead, a pattern that allows you to keep the asynchronous requests, but notifies you of their completion. One such pattern is the dispatch group, in which you:
create a dispatch group:
let group = dispatch_group_create()
for every iteration of your loop, call dispatch_group_enter:
dispatch_group_enter(group)
win the completion block of the asynchronous method, call dispatch_group_leave:
dispatch_group_leave(group)
specify what you want to do when the dispatch group is complete:
dispatch_group_notify(group, dispatch_get_main_queue()) {
println(self.itemArray?.count)
self.collectionView.reloadData()
}
When all of the calls to dispatch_group_enter are offset by the final dispatch_group_leave call, the notification block will be called for you.
So, looking at the code as you loop through the objects, it might look something like:
let group = dispatch_group_create()
for object in objects {
dispatch_group_enter(group)
// do some stuff
query.findObjectsInBackgroundWithBlock {
(imageObjects: [PFObject]!, error: NSError!) -> Void in
if error == nil {
// do some more stuff
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !(error != nil) {
// do even some more stuff
}
dispatch_group_leave(group)
}
} else {
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
println(self.itemArray?.count)
self.collectionView.reloadData()
}
Their are other functionally equivalent patterns (e.g. custom concurrent dispatch queue with the final reloadData dispatched with a barrier; make these individual requests NSOperation objects that you add to NSOperationQueue and create separate completion operation that is dependent upon those other operations; etc.), but the dispatch group seems to entail the least refactoring of this code. Regardless, hopefully this illustrates the basic idea: Make the final reload only be triggered when the other asynchronous requests are done.

parse add multiple images to an array after loop

I have an item Object which has a relation class which contain several images related to the item. The problem is I'm trying to add these images to an array so first inside the loop I'm adding them to one array and after the loop I'm adding them to the image Array and then when the loop is completed I want to reload table View. At the moment it seems like
Self.imageArray.addObject(imageDic)
is being called before the loop and therefor nothing is added to the array.
How can I do this?
var relation = itemObject.relationForKey("file") as PFRelation
var imageQuery = relation.query() as PFQuery
imageQuery.findObjectsInBackgroundWithBlock { (imageObj: [AnyObject]!, error1: NSError!) -> Void in
if error1 == nil {
var imageDic = NSMutableArray()
for obj in imageObj {
var imageObject = obj as PFObject
var thumbnail = imageObject.objectForKey("file") as PFFile
thumbnail.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let image = UIImage(data:imageData)
imageDic.addObject(image)
}
}
}
self.imageArray?.addObject(imageDic)
println(self.imageArray?.count)
}
}
self.tableView?.reloadData()
}
See the block is called afterwards and your self.tableView?.reloadData() would be called before anything is added to it.
So I made cahnges in your code as below:-
var relation = itemObject.relationForKey("file") as PFRelation
var imageQuery = relation.query() as PFQuery
imageQuery.findObjectsInBackgroundWithBlock { (imageObj: [AnyObject]!, error1: NSError!) -> Void in
if error1 == nil {
var imageDic = NSMutableArray()
for obj in imageObj {
var imageObject = obj as PFObject
var thumbnail = imageObject.objectForKey("file") as PFFile
thumbnail.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let image = UIImage(data:imageData)
// imageDic.addObject(image) //No need of this array.
self.imageArray?.addObject(imageDic)
println(self.imageArray?.count) //simply add object into this array.
}
}
}
self.tableView?.reload()
}
}
}

Swift Image retrieving from Parse sdk - getting crashed

I'm retrieving set of images from Parse, using the following code using Swift.
var imageResources : Array<UIImage> = []
override func viewDidLoad(){
super.viewDidLoad()
self.loadImages()
}
func loadImages(){
var query = PFQuery(className: "Images")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
for object : PFObject! in objects as [PFObject] {
var thumbNail = PFFile()
thumbNail = object["image"] as PFFile
println("thumNail \(thumbNail)")
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) in
if (error == nil) {
let image : UIImage = UIImage(data:imageData)
//image object implementation
self.imageResources.append(image)
}
})//getDataInBackgroundWithBlock - end
}//for - end
}
else{
println("Error in retrieving \(error)")
}
})//findObjectsInBackgroundWithblock - end
}
My Parse Class detail
class name - Images
When I run this function, it's getting crashed without a message in the console.
Note: I'm able to get the collection of PFFile objects in the callback.
I've replaced
"thumbNail.getDataInBackgroundWithBlock({...." block with the synchronous function call thumbNail.getData() like
"var imageData= thumbNail.getData()
var image = UIImage(data:imageData)"
Then the error says
Warning: A long-running operation is being executed on the main thread.
Break on warnBlockingOperationOnMainThread() to debug.
So, I reverted to thumbNail.getDataInBackGroundWithBloack({...
But now, there is no error display in the console, as it happens before. Is there anything wrong in my approach please let me know.
Any help would be appreciated...!
I managed to recreate the error, which seems to be some kind of memory leak / zombie on a PFObject. I'm not sure exactly why, but refactoring your code in the following manner got rid of the error in my case:
func loadImages() {
var query = PFQuery(className: "Images")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
self.getImageData(objects as [PFObject])
}
else{
println("Error in retrieving \(error)")
}
})//findObjectsInBackgroundWithblock - end
}
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
//image object implementation
self.imageResources.append(image)
println(image)
}
})//getDataInBackgroundWithBlock - end
}//for - end
}
EDIT: Incidentally, this also works:
func loadImages() {
var query = PFQuery(className: "Images")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let thumbNail = object["image"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
//image object implementation
self.imageResources.append(image)
println(image)
}
})//getDataInBackgroundWithBlock - end
}//for - end
}
else{
println("Error in retrieving \(error)")
}
})//findObjectsInBackgroundWithblock - end
}
This would indicate that the error was due to the following line:
for object : PFObject! in objects as [PFObject] {
Rewriting that line as follows:
for object : PFObject in objects as [PFObject] {
Also removes the error. So the reason for this error seems to be that that you told the program to unwrap something that wasn't an optional.

Resources