I try to pass data between two tableviews ,for example once the person tap on a row in the first tableview you will see more data in the 2nd tableview .Actually the application runs but there is no data in the second tableview .
Does your header label appear? Then the problem can be narrowed down to PFFile object stuffs.
First of all, you should make sure that any images you want to display are downloaded and stored in the memory from Parse server. When it comes to Parse object, PFFile (image) is just a reference to the actual file in the Parse server. Therefore, you should download the actual image before you use it locally like setting it in an image view.
To get a single image PFFile from the server:
PFFile *file = dictionary[#"thumbnail"];
[file getDataInBackgroundWithBlock:^(NSData *thumbnailData, NSError *error) {
if (!error) { // downloaded successfully
UIImage *image = [UIImage imageWithData:thumbnailData];
....
}
}
In your case, it seems that multiple images are stored in the newspaper array, so all of them must be downloaded first if you want to access one of them. Reload the second table view data then.
Prepare 'dictionaryOfNewspaperImage' and override viewDidAppear method in your second vc like below:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for (int i = 0; i < [self.arrayofnewspaper count]; i++) {
PFFile *file = self.arrayofnewspaper[i];
[file getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:1];
[self.dictionaryOfNewspaperImage setObject:image forKey:indexPath];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}
}
}
And your cell configuration part should seem like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
....
cell.imageView.image = self.dictionaryOfNewspaperImage[indexPath];
....
return cell;
}
Not quite sure if it's reliable to use NSIndexPath as a key and there should be better approach than this, but you'll get the idea. Get the file from Parse first and reload relevant rows later.
When you change the data source for the second table, you need to notify the table of the change by calling its reloadData method.
In this case it might be
destviewcontroller.arrayofnewspaper=[object objectForKey:#"newspaper"];
[destviewcontroller.table reloadData];
Related
Everything works fine except that the images in the cell are showing up after a disturbing delay. About 0,3 seconds after the tableView has finished loading. I think it's pretty ugly to show a blank image for a short time...
How can I make the tableView to show up just after the images from parse are loaded (why not from the cache first?). Or I could maybe make the launch screen hold on for a longer time..? The TableView is the initial view in the app.
queryForTable
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// If no objects are loaded in memory, we look to the cache
// first to fill the table and then subsequently do a query
// against the network.
if ([self.objects count] == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
[query orderByAscending:#"name"];
return query;
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)objects
{
static NSString *simpleTableIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
//loading icons
PFFile *thumbnail = [objects objectForKey:self.imageKey];
PFImageView *thumbnailImageView = (PFImageView*)[cell viewWithTag:100];
thumbnailImageView.file = thumbnail;
thumbnailImageView.image = [UIImage imageNamed:#"placeholder.jpg"];
[thumbnailImageView loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
}
}];
//cell
cell.textLabel.text = [objects objectForKey:self.textKey];
return cell;
}
Thanks in advance :)
There is no harm in loading the images after a short delay. This is a standard procedure and is followed by many world class apps (ever try scrolling in Facebook or Twitter app). The work around to this would be quite messy in logical and programmatic terms.
I think the best way to do this would be the way facebook does it on their app. Facebook shows an empty gray container where the image belongs and once the image has been downloaded they fade it in, in animation fashion. The reason I think this is the way to go is because it doesn't seem to make sense to load an image in and then potentially load another image when ready, better to just hold an empty container and load the image in once ready.
I'm at this all afternoon and I can't figure it, so your help would be awesome! I have a custom UICollectionViewCell which I'm populating with images I create on the fly. The paths of the images are stored in Parse and after the query is done, I call [self.collectionView reloadData].
In my cellForItemAtIndexPath I have the following code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MovieCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"original_title" ascending:YES];
[self.moviesforWatchlist sortUsingDescriptors:[NSArray arrayWithObjects:sort, nil]];
cell.userInteractionEnabled = YES;
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[cell addSubview:spinner];
NSDictionary *movie = self.moviesforWatchlist[indexPath.row];
NSString *moviePosterPath = movie[#"poster_path"];
NSString *posterURL = [NSString stringWithFormat:#"http://image.tmdb.org/t/p/w342%#", moviePosterPath];
NSURL *posterImage = [NSURL URLWithString:posterURL];
UIImage *cachedImage = [self.imageCache objectForKey:posterImage];
if (cachedImage) {
cell.imageView.image = cachedImage;
}
else if (cell.imageView.image == nil) {
cell.imageView.image = [UIImage imageNamed:#"default_collectionviewcell.jpeg"];
dispatch_queue_t downloadQueue = dispatch_queue_create("get image data", NULL);
dispatch_async(downloadQueue, ^{
[spinner startAnimating];
NSData *imageData = [NSData dataWithContentsOfURL:posterImage];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
MovieCollectionViewCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = image;
[updateCell reloadInputViews];
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
}
});
[self.imageCache setObject:image forKey:posterImage];
});
}
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
return cell;
}
I'm calling the query method like this:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self getWatchlistData];
[self.collectionView reloadData];
}
This results in a flashing of images until the images are loaded on to UICollectionViewCells. I want the images displayed in my collectionviewcell to be in alphabetical order, so I'm sorting my array that holds the paths to do so, in cellforitemsatindexpath is this whats causing the flashing of images? Or is it something else? Your help is much appreciated!
There are a number of things I would change:
I wouldn't sort your array in cellForItemsAtIndexPAth. That method is called as each cell is displayed on screen, so you are potentially resorting it numerous times when that is not necessary. If your data is changing between one cell being created and the next, then the re-sorting is likely to mess up the link between the collectionView indexPath.row and your array index. I would sort the array when it is populated, perhaps in your viewWillAppear or getWatchlistData methods. If the data does change after this, use some of the other collectionView delegate/datasource methods to handle inserts/deletes/moves. But I don't think this is causing the flickering of images.
In the code which you post back to the main queue, you call the cellForItemAtIndexPath method, update the cell image and call reloadInputViews (which presumably reconfigures the cell in some way). But you then call reloadItemsAtIndexPaths for all visible cells. What this will do is cause your collectionView to re-call the cellForItemAtIndexPath datasource method for each visible item, causing the image for each to be reloaded from the cache. I think this is the cause of your flickering. I would just save the downloaded image to the cache, and then call reloadItemsAtIndexPaths for just the one indexPath that's affected. The cellForRowAtIndexPath datasource method will be called, and will recover the image from the cache and add it to the new cell.
Likewise, I think the reloadItemsAtIndexPaths at the end of your cellForItemAtIndexPath is superfluous and is likely to contribute to the flickering.
I think you are at risk of some cell reuse problems - for example, you add the spinner to every cell. But the dequeued cells might have been displayed before and therefore already have a spinner (albeit stopped and so probably hidden). If you scroll back and forth a lot, you might end up with cells with loads of spinners, wasting memory. I would give the spinner a tag before adding it as a subView, and then use viewWithTag to determine whether a spinner has already been added to the dequeued cells (if so, remove it). And only add the spinner if necessary (i.e. in your if ...image == nil clause).
Perhaps unlikely, but if the dequeued cell has a default image (so cell.imageView.image != nil) and is being reused for a cell for which the image has not yet been cached (so cachedImage == nil), then none of your dispatch methods will be executed, so the image will never be downloaded.
Sorry if that's a bit heavy, but I hope it's of use.
I have same issue, when any image got download then it flicks the view. I am going before like reload that cell, so it will call some delegate methods of collectionview. So i removed it and make a separate method for reload cell. Now when any new image got reload then i call call this method instead of reloadCellForIndexPath:. In custom method just copy paste code from your cellForItemAtIndexPath:. Assign cell in custom method.
CollectionPhotoItem cell = (CollectionPhotoItem)[self.collection cellForItemAtIndexPath:indexPath];
That's it. it will shows your cell without any flicks. Thanks.
try to use DLImageLoader
[[DLImageLoader sharedInstance] displayImageFromUrl:#"image_url_here"
imageView:#"UIImageView here"];
[[DLImageLoader sharedInstance] loadImageFromUrl:#"image_url_here"
completed:^(NSError *error, UIImage *image) {
if (error == nil) {
// if we have no any errors
} else {
// if we got an error when load an image
}
}];
I have a UITableView and I am populating it with image data. This data is loaded fine, but when the records increase in number (e.g. more than 50) the app starts to have problems like freezes and more. I understood that this is the line in my cellForRowAtIndexPath that is causing the issue:
NSData* data = [DKStoreManager loadFileWithName:[finalArray objectAtIndex:indexPath.row] forFolderNumber:[_FolderCode intValue] forUser:[_PassedUserID intValue] andType:_FolderType];
NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:data];
I understood it because when I loaded the data only once in the viewDidLoad an used myDictionary as a global variable, then all the cells where logically be the same, but the table scrolled fine fine and the app doesn't crash. finalArray is an array with the names of the files ordered in alphabetical order and the number of rows corresponds to its count. Can anyone suggest a way to load this data outside of the cellForRowAtIndexPath method? How do I then pass everything on to the cellForRowAtIndexPath if all of the NSData are different?
What I have tried to do:
1) I tried to subclass the UITableViewCells and load the data from a method:
cell.FileName = [finalArray objectAtIndex:indexPath.row];
cell.PassedUserID = _PassedUserID;
cell.FolderCode = _FolderCode;
cell.FolderType = _FolderType;
[cell loadContents];
I made sure using a BOOL that loadContents runs only once in the subclass. When I scroll down or up, cells change position. Its a mess...
2) I noticed that if I remove the
if (cell == nil) {
and stop reusing the cell, there are no issues with the cells changing place, but there are huge loading time issues
2) Moving everything in the if (cell == nil) { method, the cells still change place on scroll but the scroll is faster...
4) Loading all the data in the viewDidLoad displaying a "loading..." but the loading is really slow, it doesn't really work out.
5) Using dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ on the data load, but doesn't work, the table scrolls really slow...
PS ,myDictionary contains the image and the name of the cell.
EDIT:
All the data is saved locally, the method "loadFileWithName" loads a saved file in the documents directory.
Please see example of lazy-loading table below.
This example provides a table view with infinite number of cells; data for each cell is loaded in a background thread only when all of the following conditions are true:
UITableView requests that cell;
Cell that data is requested for is visible;
UITableView is not scrolling OR scrolling is not decelerated (i.e. scrolling is performed while user’s finger touches the screen).
That is, excessive load of the system during fast scrolling is eliminated, and data is loaded only for cells that user really needs to see.
In this example, time-consuming and thread-blocking data loading operation is emulated for each cell with sleeping a background thread for 0.2 seconds. To use this example in your real application please do the following:
Replace implementations of the tableElementPlaceholder getter;
In the performActualFetchTableCellDataForIndexPaths: method, replace the following line with your actual loading cell data:
[NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation
Tune implementation of the tableView:cellForRowAtIndexPath: method for your cells implementation.
Please note that any object data needed in the loading code should be thread-safe since loading is performed in non-main thread (i.e. atomic properties and probably NSLock should be used inside the performActualFetchTableCellDataForIndexPaths: method in your time-consuming thread blocking code replacing the [NSThread sleepForTimeInterval:0.2] call).
You can download the full Xcode project from here.
#import "ViewController.h"
#import "TableViewCell.h"
static const NSUInteger kTableSizeIncrement = 20;
#interface ViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) NSMutableArray* tableData;
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, readonly) id tableElementPlaceholder;
#property (nonatomic, strong) NSTimer* tableDataLoadDelayTimer;
- (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath;
- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths;
- (void)tableDataLoadDelayTimerFired:(NSTimer*)timer;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableData = [NSMutableArray arrayWithCapacity:kTableSizeIncrement];
for (NSUInteger i = 0; i < kTableSizeIncrement; i++) {
[self.tableData addObject:self.tableElementPlaceholder];
}
// Do any additional setup after loading the view, typically from a nib.
}
- (id)tableElementPlaceholder
{
return #"";
}
- (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath
{
if (self.tableView.decelerating && !self.tableView.tracking) {
if (self.tableDataLoadDelayTimer != nil) {
[self.tableDataLoadDelayTimer invalidate];
}
self.tableDataLoadDelayTimer =
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(tableDataLoadDelayTimerFired:)
userInfo:nil
repeats:NO];
} else {
[self performActualFetchTableCellDataForIndexPaths:#[indexPath]];
}
}
- (void)tableDataLoadDelayTimerFired:(NSTimer*)timer
{
[self.tableDataLoadDelayTimer invalidate];
self.tableDataLoadDelayTimer = nil;
NSArray* indexPathsForVisibleRows = [self.tableView indexPathsForVisibleRows];
[self performActualFetchTableCellDataForIndexPaths:indexPathsForVisibleRows];
}
- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths
{
for (NSIndexPath* indexPath in indexPaths) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation
NSString* value = [NSString stringWithFormat:#"Text at cell #%ld", (long)indexPath.row];
dispatch_async(dispatch_get_main_queue(), ^{
self.tableData[indexPath.row] = value;
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
});
});
}
}
#pragma mark UITableViewDataSource protocol
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == (self.tableData.count - 1)) {
for (NSUInteger i = 0; i < 20; i++) {
[self.tableData addObject:self.tableElementPlaceholder];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableViewCell" forIndexPath:indexPath];
NSString* text = [self.tableData objectAtIndex:indexPath.row];
if (text.length == 0) {
cell.activityIndicator.hidden = NO;
[cell.activityIndicator startAnimating];
cell.label.hidden = YES;
[self fetchTableCellDataForIndexPath:indexPath];
} else {
[cell.activityIndicator stopAnimating];
cell.activityIndicator.hidden = YES;
cell.label.hidden = NO;
cell.label.text = text;
}
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
#end
For your project, the performActualFetchTableCellDataForIndexPaths: method would look as follows:
- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths
{
for (NSIndexPath* indexPath in indexPaths) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData* data = [DKStoreManager loadFileWithName:[finalArray objectAtIndex:indexPath.row] forFolderNumber:[self.FolderCode intValue] forUser:[self.PassedUserID intValue] andType:self.FolderType];
NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.tableData[indexPath.row] = myDictionary;
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
});
});
}
}
Please note that you'll need to use atomic properties self.FolderCode and self.PassedUserID instead of instance variables _FolderCode and _PassedUserID, because loading file is performed in a separate thread and you need to make this data thread-safe.
As for the tableElementPlaceholder method, it might look as follows:
- (id)tableElementPlaceholder
{
return [NSNull null];
}
Correspondingly, in the tableView: cellForRowAtIndexPath: method check if data load completed would look like this:
NSObject* data = [self.tableData objectAtIndex:indexPath.row];
if (data == [NSNull null]) {
cell.activityIndicator.hidden = NO;
[cell.activityIndicator startAnimating];
cell.label.hidden = YES;
[self fetchTableCellDataForIndexPath:indexPath];
} else if ([data isKindOfClass:[NSData class]]) {
[cell.activityIndicator stopAnimating];
cell.activityIndicator.hidden = YES;
NSData* actualData = (NSData*)data;
// Initialize your cell with actualData...
}
Not a good idea
Loading a lot of cells on a tableView it's not a good way to display data. Let suppose if a user wants to take a look at your data, he will take a look at your first elements (suppose max 20 cells), why he should drag to see elements all the way down? If he wants more he just goes down and clicks load more. User doesn't want to keep waiting and loading all the data to the memory (RAM).
Suggestion
For me the best way is to add a load more function (also called infinite scrolling) when you go to the bottom of your loaded elements (it's the same way as pagination on websites).
Implementation
There is a great library to achieve this thing called SVPullToRefresh. This library allow you by draging down the tableView to trigger a method (completionHandler) where you should load more data at the end of tableView.
//This is where we add infinite scrolling to the tableView
__weak YourViewController *weakSelf = self;
[self.tableView addInfiniteScrollingWithActionHandler:^{
[weakSelf loadMoreData];
[weakSelf.tableView reloadData];
}];
Now that we added load more, we need to setup our data. First we declare a NSInteger *lastIndex; as an instance variable, an inthe viewWillAppear we instantiate it with 0, and call loadMoreData which will give the lastIndex the value 20
- (void)viewWillAppear:(BOOL)animated {
lastIndex = 0;
[self loadMoreData];
}
This is the method that takes care of data from Array to tableView. Here we are describing an example which gets 20 elements for load more pressed.
-(void)loadMoreData {
//Lets suppose we are loading 20 elemets each time
//Here we control if we have elements on array
if(yourDataArray.count - lastIndex>0) {
//We control if we have 20 or more so we could load all 20 elements
if(yourDataArray.count - lastIndex>=20) {
lastIndex += 20;
} else {
//Our last index is the last element on array
lastIndex = yourDataArray.count - lastIndex;
}
} else {
//We have loaded all the elements on the array so alert the user
}
}
In numberOfRowsInSection we return the lastIndex
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return lastIndex;
}
Conclusions
To deal with images you should use SDWebImage as the other answers suggested. Dealing the problem of scrolling this way, you eleminate the way to add large amount of data on tableViews at the first look at it. Maybe it's not the right answer to deal with your idea (if you really need to load all data to your tableView), but it tells you how to avoid loading data at giving you performance at scrolling to your tableView.
p.s I am using this example in my Social Networking App and it's working great. (Also Instagram, Facebook and other apps are dealing with loading data this way).
Hope it helps :)
I have seen similar approaches as yours in the past years and they all have been wrong. If I were you I would use 2 different approaches:
1) If you load images from a remote server use a great library called SDWebImage or write your one of own. It's going to get as simple as just [self.imgViewBack setImageWithURL:[NSURL URLWithString:data.eventImageURL];
2) You could also save all your images to NSDocumentDirectory and while them stored there - just use an NSDictionary of paths.
My general recommendation here goes as follows - never create an array or dictionary of raw data of objects the size more than a few kilobytes. This will result in terrible freezes and memory waste all over the place. Always use paths to the object and store them somewhere else - and you will benefit.
Generally cellForRowAtIndexPath method is the most frequently used in UITableViewDataSource protocol so it should be executed with lightning fast speed.
If you have to store everything locally and not on a server as SergiusGee said, I would suggest keeping, for example, only 50 images in memory.
There's the tableView:willDisplayCell:forRowAtIndexPath: delegate method, where you could control which elements are loaded.
For example, when displaying the 50th cell, you have images between index 30-70. After every 10th cell, you could unload the unneeded ones and reload new ones to keep a [x-20, x+30] range, where x is the current cell.
Obviously this will take quite some effort to implement, but loading everything and keeping it in memory is certainly not the answer.
Also, the numbers are just for the examples sake.
Do not load the data from file in -cellForRowAtIndexPath:.
Try loading them into an array of UIImage in -viewDidLoad: synchronously or async-ly (which is up to you) because it does not take too much time for UIImage to be presented but it is extremely expensive to load raw data to construct an UIImage.
I have tableviewcontroller and data fecthed from the server. I use following class to download the data asyn. but my problem is data is loading when user sees the tableViewcontroller. I want data being loaded before user sees.
#import <SDWebImage/UIImageView+WebCache.h>
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.textLabel.text = [[tableData objectAtIndex:indexPath.row] valueForKey:#"name"];
cell.textLabel.font = [UIFont fontWithName:#"BebasNeue" size:24];
cell.textLabel.textColor = [UIColor whiteColor];
NSString *imageURLString=[[tableData objectAtIndex:indexPath.row] valueForKey:#"logo"];
NSString* imageURL = [[tableData objectAtIndex:indexPath.row] valueForKey:#"picture"];
[cell.imageView setImageWithURL:[NSURL URLWithString:imageURL]];
}
Solution
With SDWebImage you can download the image first.
Where?
It depends on your implementation. Maybe in a previous controller, or in your appDelegate. If the tableview appears in your initial viewcontroller, chances are to absolutely depend on the network speed.
Example Code
Take a look at this code (extracted from the Readme of the library, check the source!):
Manager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// YOU DON'T NEED THIS
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// MAYBE YOU WANT TO DO SOMETHING HERE
}
}];
Then, in your tableView:cellForRowAtIndexPath: method, you can just set the image like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ...
NSURL* url = GET_IMAGE_URL;
[cell.imageView setImageWithURL:url];
// ...
}
How it works?
The library takes care of looking first in its cache, where it will find the previously downloaded image, because it uses the image urls as keys for the cache, so that the image appears inmediatly.
Issue
You are downloading data and loading it to the tableView async, that means your data process is executing on background, but the tableView is still active and it's shown.
My Solution
I handle this situation in two ways
You first load all the data in a NSArray or NSDictionary (whatever object you need) before presenting your TableViewController, and pass it as an argument to the TableViewController. In other words, you need to load data before presenting the tableViewController.
You need to create an animation view, or a loading view while you are performing the download of the data you tell the user that data is loading so wait until the process is done. And when the process is done, simply reload the tableView and pass the data to the TableViewCells.
Example
1) This code is used when you send request to server to get data (for example from JSON)
LoadingView *spinn = [LoadingView loadSpinnerIntoView:self.view];
dispatch_queue_t downloadQueue = dispatch_queue_create("LoadingView", NULL);
dispatch_async(downloadQueue, ^{
// do our long running process here
[NSThread sleepForTimeInterval:0.1];
// do any UI stuff on the main UI thread
dispatch_async(dispatch_get_main_queue(), ^{
//Start downloading your data, and pass data you fetched to objects of NSDictionary
//Your methods
//After downloading data is done, reload data on tableView
[tableView reloadData];
//Remove loading view, and your tableViewController is all set
[spinn removeSpinner];
});});
2) After fetching data to NSDictionary or NSArray from the request you made, you have also links to download images.
For downloading image async without blocking mainThread you can refer to #sonxurxo question, or you can use this one which also uses SDWebImage
You just need to #import <SDWebImage/UIImageView+WebCache.h> to your project, and you can define also the placeholder when image is being downloaded with just this code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
I have an app where I load a lot of large images. When I lazy-load them, and even after the image has been loaded, the cell does not load them until I take my finger off the screen. I am calling my downloadImageForVisiblePaths function in the UIScrollViewDelegate methods scrollViewDidEndDragging and in scrollViewDidEndDecelerating apart from this, I am also setting the image in the UITableView's cellForRowAtIndexPath method like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// Code to load reusable custom cell
CustomObject *object = (CustomObject*) [self.tableArray objectAtIndex: indexPath];
if(!object.mainImage){
[self downloadImageForIndexPath:indexPath];
cell.mainImageView.image = [UIImage imageNamed:#"placeholder"];
}else{
cell.mainImageView.image = object.mainImage;
}
return cell;
}
Where the downloadImageForIndexPath looks like this:
-(void) downloadImageForIndexPath:(NSIndexPath*) indexPath{
UIImage *loadedImage = [[UIImage alloc] init];
// take url and download image with dispatch_async
// Once the load is done, the following is done
CustomCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.mainImageView.image = loadedImage;
CustomObject *object = (CustomObject*) [self.tableArray objectAtIndex: indexPath];
object.mainImage = loadedImage;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableVIew reloadData];
});
}
I can't see where I am going wrong. I need the images to load even when the finger is on the screen. This behaviour is similar to how the images load on apps like Google+, Instagram or Facebook.
Any pointers will be much appreciated.
It's hard to tell since you didn't include all the code for downloadImageForIndexPath, but it looks like you are assigning an image to a cell from a background thread (you shouldn't touch UI controls from background threads). Also, if you'r updating cell directly, you don't need to call reloadData.
I would also suggest using SDWebImage for displaying remote images in a tableview.