tableView.reloadData() does not refresh table until scrolled, even with dispatch_async - ios

I'm having a rather common issue, but the solutions so far have not given me the desired result.
I have a UITableView that I am populating with information that I have parsed from a JSON pulled from the web at run time. The JSON retrieval starts in func viewDidLoad() using the NSURLSession.dataTaskWithURL(NSURL, completionHandler) function. The cells of the table are then populated within the completionHandler.
The cells used in the table are custom, and all UI elements in the cell have default values set through the interface builder.
When the table appears it is completely empty, though it does have the proper cell height. The reason I found for this behavior is that the reloadData function isn't being called at the right time due to multithreading/multiprocessing and the suggested solution is to have
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
somewhere in the code to allow the reload to occur correctly. However, this only partially works. The table flickers with what looks like content, and then immediately goes blank again until I scroll. Once I scroll the reloadData function works as expected, but only after I scroll.
I've tried having the reloadData function in various locations (such as in viewWillAppear) with no luck. Are there any ideas or troubleshooting tips that I can try?
Edit #1 - Request for completion handler
var listTask = session.dataTaskWithURL(listUrl, completionHandler: {(data, response, error) in
if error == nil {
var json:NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSDictionary
var topGames = json["top"] as [NSDictionary]
var currGame:NSDictionary
var toAdd:Game
for var i=0; i < topGames.count; ++i {
currGame = topGames[i]["game"] as NSDictionary
toAdd = Game(
id: String(currGame["_id"] as CLong),
name: currGame["name"] as NSString,
boxArtImageUrl: (currGame["box"] as NSDictionary)["medium"] as NSString,
boxArtImage: nil,
isFetchingBoxArt: false,
totalViewers: topGames[i]["viewers"] as Int,
totalChannels: topGames[i]["channels"] as Int,
topChannel: "N/A")
self.games.append(toAdd)
}
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}
Note that the self.games array is used in func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCel to fill in the information in the cells.
Edit #2 - Request for cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TopGameListCell = self.tableView.dequeueReusableCellWithIdentifier("topGameCell") as TopGameListCell
if games.count > 0 {
var game:Game = games[indexPath.row]
cell.nameLabel?.text = game.name
cell.totalViewersLabel.text = String(game.totalViewers)
cell.totalChannelsLabel.text = String(game.totalChannels)
cell.topChannelLabel.text = game.topChannel
if game.boxArtImage != nil {
cell.boxArtImageView.image = game.boxArtImage
} else {
if(!game.isFetchingBoxArt) {
cell.boxArtImageView.image = placeholderImage
gatherGameBoxArtImageForCell(game.boxArtImageUrl, indexPath: indexPath)
}
}
}
return cell;
}
I forgot about the extra fetch that I start within the gatherGameBoxArtImageForCell. Basically if I don't already have the image, download it. The if/else for the image seems to be causing the flicker to occur. If I let it sit long enough, it finally shows the table. The idea for me is that the placeholder image would show until the image is downloaded, and then reloadData is called after the image is downloaded. Here is the image fetch completionHandler:
var task = session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
var game = self.games[indexPath.row]
if error == nil {
if(game.boxArtImage == nil) {
var cellForImage:TopGameListCell? = self.tableView.cellForRowAtIndexPath(indexPath) as? TopGameListCell
if cellForImage != nil {
self.games[indexPath.row].boxArtImage = UIImage(data: data)
cellForImage?.boxArtImageView.image = self.games[indexPath.row].boxArtImage
}
}
} else {
println(error)
}
game.isFetchingBoxArt = false
self.tableView.reloadData()
})

It appears that when I was trying to have the images load in the background it was hogging up the main thread. What I did was changed the following from
if(!game.isFetchingBoxArt) {
cell.boxArtImageView.image = placeholderImage
gatherGameBoxArtImageForCell(game.boxArtImageUrl, indexPath: indexPath)
}
to
if(!game.isFetchingBoxArt) {
cell.boxArtImageView.image = placeholderImage
dispatch_async(dispatch_get_main_queue(), {
self.gatherGameBoxArtImageForCell(game.boxArtImageUrl, indexPath: indexPath)
})
}
I decided to do this after thinking on how the normal solution is to place reloadData in a dispatch_async. Apparently the reasoning is the same for the issue I was having, so now the download of the images runs asynchronously.

Related

UICollectionView displays wrong images in cells

I am building a UITableView that is going to have cells with different layouts in them. The cell I am having issues with has a UICollectionView embedded in it that is generated from an API.
The category name and id populate in the cell correctly, but the images in the UICollectionView do not. The images load, but they are not the right ones for that category. Screen capture of how the collection is loading currently
Some of the things I've tried:
Hard-coding the ids for each one of the categories instead of dynamically generating them. When I do this, the correct images load (sometimes but not always) ... and if they do load correctly, when I scroll the images change to wrong ones
The prepareForReuse() function ... I'm not exactly sure where I would put it and what I would reset in it (I have code I believe already kind of nils the image out [code included below])
I have spent a few hours trying to figure this out, but I am stuck ... any suggestions would be appreciated.
My View Controller:
class EcardsViewController: BaseViewController {
#IBOutlet weak var categoryTable: UITableView!
var categories = [CategoryItem]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.categoryTable.dataSource! = self
self.categoryTable.delegate! = self
DispatchQueue.main.async {
let jsonUrlString = "https://*********/******/category"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let ecardcategory = try decoder.decode(Category.self, from: data)
self.categories = ecardcategory.category
self.categories.sort(by: {$0.title < $1.title})
self.categories = self.categories.filter{$0.isFeatured}
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
self.categoryTable.reloadData()
}
}
}.resume()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension EcardsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EcardsCategoriesTableViewCell", for: indexPath) as! EcardsCategoriesTableViewCell
cell.categoryName.text = ("\(categories[indexPath.row].title)**\(categories[indexPath.row].id)")
cell.ecardCatId = String(categories[indexPath.row].id)
return cell
}
}
My Table Cell:
class EcardsCategoriesTableViewCell: UITableViewCell {
#IBOutlet weak var categoryName: UILabel!
#IBOutlet weak var thisEcardCollection: UICollectionView!
var ecardCatId = ""
var theseEcards = [Content]()
let imageCache = NSCache<NSString,AnyObject>()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.thisEcardCollection.dataSource! = self
self.thisEcardCollection.delegate! = self
DispatchQueue.main.async {
let jsonUrlString = "https://**********/*******/content?category=\(self.ecardCatId)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let ecards = try decoder.decode(Ecards.self, from: data)
self.theseEcards = ecards.content
self.theseEcards = self.theseEcards.filter{$0.isActive}
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
self.thisEcardCollection.reloadData()
}
}
}.resume()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension EcardsCategoriesTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return theseEcards.count > 7 ? 7 : theseEcards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "EcardCategoriesCollectionViewCell", for: indexPath) as! EcardCategoriesCollectionViewCell
cell.ecardImage.contentMode = .scaleAspectFill
let ecardImageLink = theseEcards[indexPath.row].thumbSSL
cell.ecardImage.downloadedFrom(link: ecardImageLink)
return cell
}
}
Collection View Cell:
class EcardCategoriesCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var ecardImage: UIImageView!
}
Extension to "download" image:
extension UIImageView {
func downloadedFromReset(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit, thisurl: String) {
contentMode = mode
self.image = nil
// check cache
if let cachedImage = ImageCache.shared.image(forKey: thisurl) {
self.image = cachedImage
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
ImageCache.shared.save(image: image, forKey: thisurl)
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFromReset(url: url, contentMode: mode, thisurl: link)
}
}
Both UICollectionViewCell and UITableViewCell are reused. As one scrolls off the top of the screen, it is reinserted below the visible cells as the next cell that will appear on screen. The cells retain any data that they have during this dequeuing/requeuing process. prepareForReuse exists to give you a point to reset the view to default values and to clear any data from the last time it was displayed. This is especially important when working with asynchronous processes, such as network calls, as they can outlive the amount of time that a cell is displayed. Additionally, you're doing a lot of non-setup work in awakeFromNib. This method is not called every time a cell is displayed, it is only called the FIRST time a cell is displayed. If that cell goes off screen and is reused, awakeFromNib is not called. This is likely a big reason that your collection views have the wrong data, they're never making their network request when they appear on screen.
EcardsCategoriesTableViewCell:
prepareForReuse should be implemented. A few things need to occur in this method:
theseEcards should be nilled. When a table view scrolls off screen, you want to get rid of the collection view data or else the next time that cell is displayed, it will show the collection view data potentially for the wrong cell.
You should keep a reference to the dataTask that runs in awakeFromNib and then call cancel on this dataTask in prepareForReuse. Without doing this, the cell can display, disappear, then get reused before the dataTask completes. If that is the case, it may replace the intended values with the values from the previous dataTask (the one that was supposed to run on the cell that was scrolled off screen).
Additionally, the network call needs to be moved out of awakeFromNib:
You are only ever making the network call in awakeFromNib. This method only gets called the first time a cell is created. When you reuse a cell, it is not called. This method should be used to do any additional setup of views from the nib, but is not your main entry point in adding data to a cell. I would add a method on your cell that lets you set the category id. This will make the network request. It will look something like this:
func setCategoryId(_ categoryId: String) {
DispatchQueue.main.async {
let jsonUrlString = "https://**********/*******/content?category=\(categoryId)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let ecards = try decoder.decode(Ecards.self, from: data)
self.theseEcards = ecards.content
self.theseEcards = self.theseEcards.filter{$0.isActive}
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
self.thisEcardCollection.reloadData()
}
}
}.resume()
}
}
This will be called in the cellForRowAt dataSource method in EcardsViewController.
EcardCategoriesCollectionViewCell:
This cell has similar issues. You are setting images asynchronously, but are not clearing the images and cancelling the network requests when the cell is going to be reused. prepareForReuse should be implemented and the following should occur within it:
The image on the image view should be cleared or set to a default image.
The image request should be cancelled. This is going to take some refactoring to accomplish. You need to hold a reference to the dataTask in the collection view cell so that you can cancel it when appropriate.
After implementing these changes in the cells, you'll likely notice that the tableview and collection view feel slow. Data isn't instantly available. You'll want to cache the data or preload it some way. That is a bigger discussion than is right for this thread, but it will be your next step.

Load image from Parse to UICollectionView cell without lag

I have a pretty elaborate problem and I think someone with extensive async knowledge may be able to help me.
I have a collectionView that is populated with "Picture" objects. These objects are created from a custom class and then again, these objects are populated with data fetched from Parse (from PFObject).
First, query Parse
func queryParseForPictures() {
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, err: NSError?) -> Void in
if err == nil {
print("Success!")
for object in objects! {
let picture = Picture(hashtag: "", views: 0, image: UIImage(named: "default")!)
picture.updatePictureWithParse(object)
self.pictures.insert(picture, atIndex: 0)
}
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.filtered = self.pictures
self.sortByViews()
self.collectionView.reloadData()
}
}
}
}
Now I also get a PFFile inside the PFObject, but seeing as turning that PFFile into NSData is also an async call (sync would block the whole thing..), I can't figure out how to load it properly. The function "picture.updatePictureWithParse(PFObject)" updates everything else except for the UIImage, because the other values are basic Strings etc. If I would also get the NSData from PFFile within this function, the "collectionView.reloadData()" would fire off before the pictures have been loaded and I will end up with a bunch of pictures without images. Unless I force reload after or whatever. So, I store the PFFile in the object for future use within the updatePictureWithParse. Here's the super simple function from inside the Picture class:
func updateViewsInParse() {
let query = PFQuery(className: Constants.ParsePictureClassName)
query.getObjectInBackgroundWithId(parseObjectID) { (object: PFObject?, err: NSError?) -> Void in
if err == nil {
if let object = object as PFObject? {
object.incrementKey("views")
object.saveInBackground()
}
} else {
print(err?.description)
}
}
}
To get the images in semi-decently I have implemented the loading of the images within the cellForItemAtIndexPath, but this is horrible. It's fine for the first 10 or whatever, but as I scroll down the view it lags a lot as it has to fetch the next cells from Parse. See my implementation below:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.PictureCellIdentifier, forIndexPath: indexPath) as! PictureCell
cell.picture = filtered[indexPath.item]
// see if image already loaded
if !cell.picture.loaded {
cell.loadImage()
}
cell.hashtagLabel.text = "#\(cell.picture.hashtag)"
cell.viewsLabel.text = "\(cell.picture.views) views"
cell.image.image = cell.picture.image
return cell
}
And the actual fetch is inside the cell:
func loadImage() {
if let imageFile = picture.imageData as PFFile? {
image.alpha = 0
imageFile.getDataInBackgroundWithBlock { [unowned self] (imageData: NSData?, err: NSError?) -> Void in
if err == nil {
self.picture.loaded = true
if let imageData = imageData {
let image = UIImage(data: imageData)
self.picture.image = image
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0.35) {
self.image.image = self.picture.image
self.image.alpha = 1
self.layoutIfNeeded()
}
}
}
}
}
}
}
I hope you get a feel of my problem. Having the image fetch inside the cell dequeue thing is pretty gross. Also, if these few snippets doesn't give the full picture, see this github link for the project:
https://github.com/tedcurrent/Anonimg
Thanks all!
/T
Probably a bit late but when loading PFImageView's from the database in a UICollectionView I found this method to be much more efficient, although I'm not entirely sure why. I hope it helps. Use in your cellForItemAtIndexPath in place of your cell.loadImage() function.
if let value = filtered[indexPath.row]["imageColumn"] as? PFFile {
if value.isDataAvailable {
cell.cellImage.file = value //assign the file to the imageView file property
cell.cellImage.loadInBackground() //loads and does the PFFile to PFImageView conversion for you
}
}

Swift: Populate UITableView with JSON data?

I've looked around for days, and can't figure out how to populate a UITableView with JSON data. I'm appending values to a recommendedTitles array and a recommendedThumbnails array, which I can associate between the two using an index.
I'm not getting any errors, just not getting any results. Am I missing something or doing this incorrectly?
class RecommendationCell: UITableViewCell {
#IBOutlet weak var recommendationTitle: UILabel!
}
class SongViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var recommendedTitles = [String]()
var recommendedImages = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://www.googleapis.com/youtube/v3/search?part=id&q=\(searchTerm)&maxResults=1&key=AIzaSyD7PxAoq0O5hUxx775l_E_mnowlU4cUfcI")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if error != nil {
println(error)
} else {
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
if let items = jsonResult["items"] as? NSArray {
// yada yada yada...
self.recommendedTitles.append(title)
self.recommendedImages.append(thumbnail)
})
newTask.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recommendedTitles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var songCell = tableView.dequeueReusableCellWithIdentifier("songCell", forIndexPath: indexPath) as! RecommendationCell
songCell.recommendationTitle.text = recommendedTitles[indexPath.row]
return songCell
}
}
Your code looks reasonable. The one thing I notice is that you're not calling your tableView's reloadData() method once the JSON is done downloading.
Here's what I think is going on:
You start an async download in your viewDidLoad. That returns immediately, before the data has downloaded.
Your table view calls it's data source methods to see how many sections of data and rows/section are available. It is told there is no data.
Then your completion code fires, but the table view still thinks there's no data. If you add a reloadData() call at the end of your completion block, the table view will call your data source methods again, discover that there is now data to display, and draw your contents.

parse not loading first images in tableview

I am using parse to store and retrieve some data, which I then load into a UITableview, each cell contains some text and image, however when I open my tableview, any cells in the view do not show images until I scroll them out of view and back into view (I guess this is calling cellForRowAtIndexPath). Is there a way to check when all images are downloaded and then reload the tableview?
func loadData(){
self.data.removeAllObjects()
var query = PFQuery(className:"Tanks")
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
for object in objects {
self.data.addObject(object)
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
self.tableView.reloadData()
}
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
self.cell = tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath!) as TankTableViewCell // let cell:TankTableViewCell
let item:PFObject = self.data.objectAtIndex(indexPath!.row) as PFObject
self.cell.productName.alpha = 1.0
self.cell.companyName.alpha = 1.0
self.cell.reviewTv.alpha = 1.0
self.rating = item.objectForKey("rating") as NSNumber
cell.productName.text = item.objectForKey("prodName") as? String
cell.companyName.text = item.objectForKey("compName") as? String
self.cell.reviewTv.text = item.objectForKey("review") as? String
let userImageFile = item.objectForKey("image") as PFFile
userImageFile.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
self.cell.productImage.image = image
}
}, progressBlock: {
(percentDone: CInt) -> Void in
if percentDone == 100{
}
})
self.setStars(self.rating)
// Configure the cell...
UIView.animateWithDuration(0.5, animations: {
self.cell.productName.alpha = 1.0
self.cell.companyName.alpha = 1.0
self.cell.reviewTv.alpha = 1.0
self.cell.reviewTv.scrollRangeToVisible(0)
})
return cell
}
The problem is that you use self.cell and that you change that reference each time a cell is returned. So, when the images are loaded they are all set into the last cell to be returned, which probably isn't on screen (or at least not fully).
Really you should be capturing the cell in the completion block of the image download (and checking that the cell is still linked to the same index path).
Also, you should cache the downloaded images so you don't always download the data / recreate the image.
You could set up a delegate method in your UITableViewController that gets called by another controller class that fetches the images. I doubt that's what you want to do though.
What you should do is initialize the cells with a default image, and have the cell controller itself go and fetch the image in the background, and update its UIImageView when the fetch completes. You definitely don't want to wait around for all images to load before reloading the table because a.) that takes a long time, and b.) what if one fails or times out?
Once the cell has loaded its image, if it is swapped out by the recycler and swapped back in, you can simply get the cached image by calling getData instead of getDataInBackground as long as isDataAvailable is true.
After your line:
self.cell.productImage.image = image
Try Adding:
cell.layoutSubviews() or self.cell.layoutSubviews()
It should render the subview, or your image in this case, on the first table view.

Loading TableViewDataSource after a method finish

I want to call TableViewData Sources method for Seeting up Ui after it has been fethced from parse . With this i am able to fetch
func loadImages() {
var query = PFQuery(className: "TestClass")
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) {
var imageDic = NSMutableArray()
self.image1 = UIImage(data:imageData)
//image object implementation
self.imageResources.append(self.image1!)
println(self.image1)
println(self.imageResources.count)
}
}, progressBlock: {(percentDone: CInt )-> Void in
})//getDataInBackgroundWithBlock - end
}//for - end
self.tableView.reloadData()
But not able to populate these fetched data to tableview like this
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("in table view")
println(self.imageResources.count)
return imageResources.count+1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell") as CustomTableViewCell
var (title, image) = items[indexPath.row]
cell.loadItem(title: title, image: image)
println("message : going upto this line")
println(self.imageResources.count)
var (image1) = imageResources[indexPath.row]
cell.loadItem1(image1: image1)
return cell
}
Then on loaditem i am trying to show up the images and i have writen my own array to populate to the image array but i am geeting a zero value when populating so not able to set it up
Any Help is much appreciated!
You have several problems, all related to concurrency - your load is occurring in the background and in parallel.
The first problem is the use of self.image1 as a temporary variable in the loading process - this variable may be accessed concurrently by multiple threads. You should use a local variable for this purpose.
Second, you are appending to self.imageResources from multiple threads, but Swift arrays are not thread safe.
Third, you need to call reload on your tableview after you have finished loading all of the data, which isn't happening now because you call it while the background operations are still taking place.
Finally, your getImageData function is executing on a background queue, and you must perform UI operations (such as reloading a table) on the main queue.
The simplest option is to change get thumbnail loading to synchronous calls - This means that your thumbnails will load sequentially and may take a bit longer that the multiple parallel tasks but it is easier to manage -
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
let imageData? = thumbNail.getData
if (imageData != nil) {
let image1 = UIImage(data:imageData!)
//image object implementation
self.imageResources.append(image1!)
println(self.imageResources.count)
}
}//for - end
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
A more sophisticated approach would be to use a dispatch group and keep the parallel image loading. In order to do this you would need to guard the access to the shared array

Resources