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.
Related
I am making a program in which I am fetching a image from URL and displaying on the custom cell and rest of the table data on other cells.
Here is the code:
image=[[UIImage alloc]init];
if(indexPath.row==0)
{
LabelTableViewCell *cell=[detailtable dequeueReusableCellWithIdentifier:#"imagecell" forIndexPath:indexPath];
NSOperationQueue *myqueue=[[NSOperationQueue alloc] init];
NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
[cell.actindi startAnimating];
image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[frontimage objectAtIndex:0]]]];
}];
downloadOperation.completionBlock=^{
cell.imageprimary.image=image;
[cell.actindi stopAnimating];
};
[myqueue addOperation:downloadOperation];
return cell;
}
else
{
LabelTableViewCell *cell = [detailtable dequeueReusableCellWithIdentifier:#"tabledetail" forIndexPath:indexPath];
cell.labelspec.text=[firstArray objectAtIndex:indexPath.row];
cell.txtfield.text=[secondArray1 objectAtIndex:indexPath.row];
return cell;
}
return 0;
Problem I am facing:
Whenever I scroll up it reloads the top cell.
It dont reload the image on first cell till I click on that cell.
if(!cell) and if (cell==nil) not working.
Reloading the table puts in an infinite loop till I scroll down.
So I want to permanently allocate the memory to the first cell as it is only one in number.
I have tried putting nil in dequeueReusableCellWithIdentifier and its not working.
I have gone about 20-30 articles to do that but nothing has worked for me I don't know why.
So please give me any specific solution at the present condition.
I am new in the iOS developing don't know too much about it . This is my first program that I am trying to make.
You can try it different way:
To download and show image Just use library - https://github.com/rs/SDWebImage
Library will download image and cache for you.
Edit 1
To be clear, [self loadObjects] is not my method it is a method on the PFQueryTableViewController class supplied by parse to pull in new data.
I suspect this might be being caused by the table drawing code, as the tablecellview is configured to be auto-adjust it's height.
Here is the table drawing code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
object:(PFObject *)object
{
//Setup estimated heights and auto row height
self.tableView.estimatedRowHeight = 68.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//Give the cell a static identifier
static NSString *cellIdentifier;
socialPost* post = object;
//Check to see what sort of cell we should be creating, text, image or video
if (object[#"hasImage"] != nil) {
cellIdentifier = #"posts_with_image";
} else {
cellIdentifier = #"posts_no_image";
}
//Create cell if needed
hashtagLiveCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[hashtagLiveCellView alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
// Configure the cell to show our imformation, loading video and images if needed
cell.postTitle.text = [NSString stringWithFormat:#"#%#",object[#"userName"]];
cell.postText.text = [NSString stringWithFormat:#"%#",
object[#"text"]];
[cell.userImage setImageWithURL:[NSURL URLWithString:post.userImageURL]];
[cell.postImage setImageWithURL:[NSURL URLWithString:post.imageURL]];
//Set ID's on the custom buttons so we know what object to work with when a button is pressed
cell.approveButtonOutlet.stringID = object.objectId;
[cell.approveButtonOutlet addTarget:self action:#selector(approvePostCallback:) forControlEvents:UIControlEventTouchUpInside];
cell.deletButtonOutlet.stringID = object.objectId;
[cell.deletButtonOutlet addTarget:self action:#selector(deletePostCallback:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Original
I have a PFQueryTableViewController that i am loading with object from parse.
I have a scheduled task set to run every 20 seconds that calls:
[self loadObjects]
To fetch any new objects, or any changed to objects that have happened.
That all works fine, however if i am scrolled halfway down the tableview when the loadObjects is called the page jumps back to the top. Even if there are no new or changed data available.
Is there an easy way around this, before i start looking into hacky ways to catch the reload and force the table to stay where it is.
Thanks
Gareth
When you're calling loadObjects you load the objects from start. And there for you get the first results again.
Try to change [self loadObjects]; to [self.tableView reloadData];.
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 where I use a custom cell. However, when I scroll the table view there is some serious lag. It happens when I set the UIImaveView's image property with an image. I am accessing an image from the directory. But since file IO is slow I am using the dispatch_async to load the image into a UIImage object on a separate thread.
However there is still some lag. When I scroll up and down the rows without any images, the scrolling is very smooth. However when the row actually has an image, there is lag. the momentum scrolling will halt, then the app becomes unresponsive, then when the image finally loads the momentum continues where it left off.
I am not sure what is causing the lag. At first I thought it had to do with the image being too large so i tried scaling it down. Still lags. Again, if I don't set the image in the custom cell there is no lag. But when I do set it there is lag. I am not sure how to fix this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DHTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kReuseIdentifierGoalCell forIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath isForOffscreenUse:NO];
return cell;
}
- (void)configureCell:(DHTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath isForOffscreenUse:(BOOL)offscreenUse {
if (cell == nil) {
return;
}
[cell setDelegate:self];
PATH_TO_FILE = SQLITE_QUERY_TO_GET_PATH; //some pseudo codes
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong typeof(wSelf)sSelf = wSelf;
UIImage *unscaled_image = [UIImage imageWithContentsOfFile:PATH_TO_FILE];
UIImage *image = [unscaled_image imageScaledToFitInSize:kCellUIImageSize];
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(wSelf)sSelf = wSelf;
DHTableViewCell *cell = (id)[sSelf.tableView cellForRowAtIndexPath:indexPath];
if (cell) {
[cell.imageStored setImage:image]; //Commenting this out relieves all lag
}
});
});
}
#Calimari328 I hate if to put this as an answer because it does not really answer your question but the truth is what you should really do is use a library to achieve this. for example SDWebImage
Example:
#import <SDWebImage/UIImageView+WebCache.h>
...
- (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;
}
You have to rethink this, cells get reused, that means each time you scroll your app will try to download images again.What about performance? memory ? cache ? processing?
As you think more about it is a lot more complex then a simple async task. Fortunately there are opensource projects to achieve this. No need to reinvent the wheel.
Please note I am not advertising any library if you want to write your own code you can do this as well. You can also search on the web for easy ones to implement.
Currently I am loading all of my UITableViewControllers with images and text. I'm not sure if there is a way of shortening my loading times. I'm thinking that GCD might be the best route to go, however, I'm not too sure that I'm using this correctly:
dispatch_async(dispatch_get_main_queue(), ^{
[some method];
});
This is being loaded in the ViewDidLoad, and I'm unsure if this is the correct place to use GCD. Also, is this correct way of asynchronously loading information?
use this, it downloads multiple files in different threads.And its asynchronous.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//load image here
});
If you want to load all images ,even the images which might be very down in tableView (say image at 20th row).Then viewDidLoad is perfect approach, or if you want to only load images that are currently to be shown, then download images in cellForRow:atIndexPath method.
OR,you can use AFNetworking to download multiple files much efficiently.
Hope this helps
For loading images faster in table view cells you can use SDWebImage.
It is very simple to use. Just import the SDWebImage folder into your Xcode project. It contains a category on UIImageVIew. So on any imageView object just call the method setImageWithURL as illustrated below:
- (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;
}