Reputation: 1783
I've been struggling to get my tableview to load the .count correctly. I got to find a way to tell the tableview only to only load after my image and post arrays are fully populated.
Otherwise I will keep getting a
fatal error: Array index out of range
at the
cell.cellImage?.image = imagesArray[indexPath.row]
inside
cellForRowAtIndexPath
Output:
NUMBER OF POSTS->0
NUMBER OF IMAGES->0
NUMBER OF POSTS->0
NUMBER OF IMAGES->0
NUMBER OF POSTS->0
NUMBER OF IMAGES->0
POSTSARRAY COUNT->1
POSTSARRAY COUNT->2
POSTSARRAY COUNT->3
POSTSARRAY COUNT->4
NUMBER OF POSTS->4
NUMBER OF IMAGES->0
IMAGESARRAY COUNT->1
IMAGESARRAY COUNT->2
IMAGESARRAY COUNT->3
IMAGESARRAY COUNT->4
NUMBER OF POSTS->4
NUMBER OF IMAGES->4
Code
override func viewDidLoad() {
super.viewDidLoad()
// myTableView.estimatedRowHeight = 312.0
// myTableView.rowHeight = UITableViewAutomaticDimension
var query = PFQuery(className: "Post")
query.whereKey("hobbieTag", equalTo:"\(selectedHobbie)")
query.orderByAscending("description")
query.findObjectsInBackgroundWithBlock
{
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil
{
// println("HOBBIES.COUNT->\(hobbies?.count)")
for post in objects!
{
//GET POST TITLE
self.posts.append(post["postText"] as! String)
println("POSTSARRAY COUNT->\(self.posts.count)")
//TEST IMAGE
//var appendImage = UIImage(named: "logoPDF")
//self.imagesArray.append(appendImage!)
//GET IMAGE FILE
let postImageFile = post["postImage"] as? PFFile
postImageFile?.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
var image = UIImage(data: imageData!)
self.imagesArray.append(image!)
println("IMAGESARRAY COUNT->\(self.imagesArray.count)")
}, progressBlock: { (progress: Int32) -> Void in
// println("PROGRESS->\(progress)")
})
}
self.myTableView.reloadData()
}
else
{
println(error?.localizedDescription)
println(error?.code)
}
// self.myTableView.reloadData()
}//END query
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
myTableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("NUMBER OF POSTS->\(posts.count)")
println("NUMBER OF IMAGES->\(imagesArray.count)")
return posts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! HobbieFeedTableViewCell
cell.cellTitle.text = posts[indexPath.row]
cell.cellSubtitle.text = posts[indexPath.row]
// cell.cellImage.image = UIImage(named: "logoPDF")
//cell.cellImage?.image = imagesArray[indexPath.row]
return cell
}
Updated code
var query = PFQuery(className: "HobbieFeed")
query.whereKey("hobbieTag", equalTo:"\(selectedHobbie)")
query.orderByAscending("description")
query.findObjectsInBackgroundWithBlock
{
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil
{
let semaphore = dispatch_semaphore_create(0)
// println("HOBBIES.COUNT->\(hobbies?.count)")
for post in objects!
{
//GET POST TITLE
self.posts.append(post["postText"] as! String)
println("POSTSARRAY COUNT->\(self.posts.count)")
//TEST IMAGE
//var appendImage = UIImage(named: "logoPDF")
//self.imagesArray.append(appendImage!)
//GET IMAGE FILE
let postImageFile = post["postImage"] as? PFFile
postImageFile?.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
var image = UIImage(data: imageData!)
self.imagesArray.append(image!)
println("IMAGESARRAY COUNT->\(self.imagesArray.count)")
dispatch_semaphore_signal(semaphore)
}, progressBlock: { (progress: Int32) -> Void in
println("PROGRESS->\(progress)")
})
}
// Wait for all image loading tasks to complete
for post in objects! {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
println("SHAPHORE POSTS COUNT->\(self.posts.count)")
println("SEMAPHORE IMAGES COUNT->\(self.imagesArray.count)")
}
self.myTableView.reloadData()
}
}//END query
Upvotes: 3
Views: 1889
Reputation: 1783
Finally got around the issue. May be helpful to other users so will post the answer here.
Toke a different approach, simpler I believe.
The query happens on view did load, but there is no separate array for images or content at that stage. The cell gets the entire query object array and then retrieves what it needs for each row. Also easier to detect if there is image or not.
Global array:
var timelineData = NSMutableArray()
View did load query:
var query = PFQuery(className: "Feed")
query.whereKey("Tag", equalTo:"\(selected)")
query.orderByAscending("description")
query.findObjectsInBackgroundWithBlock
{
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil
{
for object in objects!
{
self.timelineData.addObject(object)
}
self.myTableView.reloadData()
}
}//END query
tableview cellforrow
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Cell identifiers
let standardCellIdentifier = "StandardCell"
let imageCellIdentifier = "ImageCell"
//get object, text and votes
let object = self.timelineData.objectAtIndex(indexPath.row) as! PFObject
var myText = object.objectForKey("postText") as? String
var hasImage = object.objectForKey("hasImage") as? Bool
var myVotes = object.objectForKey("votes") as! Int
let imageFromParse = object.objectForKey("postImage") as? PFFile
//if image
if hasImage == true
{
let imageCell = tableView.dequeueReusableCellWithIdentifier(imageCellIdentifier, forIndexPath: indexPath) as! FeedImageTVCell
//set image for cell
imageFromParse!.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil {
if let myImageData = imageData {
let image = UIImage(data:myImageData)
imageCell.cellImage!.image = image
}
}
}, progressBlock: { (percent: Int32) -> Void in
})
imageCell.cellVotes.text = "Votes - \(myVotes)"
imageCell.cellText.text = myText
return imageCell
}
//if no image
else
{
let standardCell = tableView.dequeueReusableCellWithIdentifier(standardCellIdentifier, forIndexPath: indexPath) as! FeedStandardTVCell
standardCell.cellVotes.text = "Votes - \(myVotes)"
standardCell.cellText.text = myText
return standardCell
}
}
Upvotes: 0
Reputation: 1739
Hi As far as I can see you are doing the right thing, loading the data asynchronously in the background. I think where it fails is in the asynchronous block where you reload the table. Try this code there:
dispatch_async(dispatch_get_main_queue()){self.myTableView.reloadData()}
The block is actually running in af different thread, thus it is not at all synchronised with your other code. If you are using test this is also a problem to get the asynchronous blocks tested properly. Here you can use the waitForExpectationsWithTimeout(seconds) to test you code properly
Upvotes: 1
Reputation: 9342
You're kicking of the image loading in the background in a loop and call reloadData
right after the loop. The background tasks are not finished at this point, however.
for post in objects! {
...
// This starts a background operation
postImageFile?.getDataInBackgroundWithBlock(...)
...
}
// The background tasks are not necessarily completed at this point
self.myTableView.reloadData()
To wait until all background tasks have finished you can use semaphores. Here is a basic example.
// Create a new semaphore with a value of 0
let semaphore = dispatch_semaphore_create(0)
// Kick off a bunch of background tasks
for i in 0...10 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
sleep(1) // Do some work or sleep
// Signal the semaphore when the task is done. This increments
// the semaphore.
dispatch_semaphore_signal(semaphore)
}
}
// Wait until all background tasks are done
for i in 0...10 {
// This waits until the semaphore has a positive value
// and then decrements it
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
// This is only executed after all background tasks are done
println("all tasks are done")
Note that this example could be simplified by using dispatch groups. This is, however, not an option in your case since your calling a function with a completion handler instead of executing a block on a queue directly.
Applying the above approach to your code would look like this.
let semaphore = dispatch_semaphore_create(0) // Create a semaphore (value: 0)
for post in objects! {
...
postImageFile?.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
... // Do your work
// Increment the semaphore when the image loading is completed
dispatch_semaphore_signal(semaphore)
}, progressBlock: {
...
})
...
}
// Wait for all image loading tasks to complete
for post in objects! {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
// This is only called after all images have loaded
self.myTableView.reloadData()
Upvotes: 4