Setting image in custom uitableviewcell causes lag - ios

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.

Related

PFQueryTableViewController jump on fetch

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];.

Async image loading from url inside a UITableView cell - wrong image while scrolling

I had an IPhone application in which i am loading images asynchronously from an array ,using this in cellforrowatindexpath,
` - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
sCell *cell = (sCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[sCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSMutableDictionary *dicttable=[sarray objectAtIndex:indexPath.section];
NSString *icon= [dicttable objectForKey:#"thumb_image"];
cell.image.image = [UIImage imageNamed:#"thumbnail-default.png"];
if(icon.length!=0)
{
if(![[ImageCache sharedImageCache] hasImageWithKey:icon])
{ cell.image.image = [UIImage imageNamed:#"thumbnail-default.png"];
NSArray *myArray = [NSArray arrayWithObjects:cell.image,icon,#"thumbnail-default.png",[NSNumber numberWithBool:NO],nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate performSelectorInBackground:#selector(updateImageViewInBackground:) withObject:myArray];
}
else
{
cell.image.image = [[ImageCache sharedImageCache] getImagefromCacheOrUrl:icon];
}
}`
and all working fine,Because i enabled paging i need to call the request to load the next set of values, like this
`- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section==[sarray count]-1)
{
[self performSelectorInBackground:#selector(loadingfeedcntents:) withObject:count];
}
}`
but here the problem is when i am scrolling the table view wrong images are assigned to the image views in the custom cell,Can anybody help me on this ?
This is most probably due the reuse of the cells. Probably what is happening is that you trigger an asynchronous request to download an image, while that is happening you scroll the table an that cell is reused so when the image finishes downloading that new cell gets the image instead of the one originally requested it.
I think you will have to find another way to do it or detach that request from the cell on prepareForReuse
Many people is recommending this class "SDWebImage" for that situation (although I haven't test it)
I have been using SDWebImage for last 2 years and have used in 5 apps, Really easy and I would say it is the best way to load images in background with caching.
Please give a try. You will love it.
A nice tutorial is here.

How to load tables of information faster using GCD

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;
}

UITableView lazy-loaded image does not load until finger is off

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.

UITableView Static Scrolling

I have a simple UITableView using custom UITableViewCells.
The options set on the UITableView's properties are only that the style is set to Grouped.
When I'm trying to scroll down through the different items the scroll is extremely jumpy.
I've researched this quite a bit looking at Tricks for improving iPhone UITableView scrolling performance? and a few other questions on this website. I haven't really been able to find a solution though.
EDIT ****
I use a WSDL webservice to load data into the UITableViewCells.
The cells only have a UITextView and three buttons in it.
EDIT ****
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NavigatorCell";
NewCell *cell = (NewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"NewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.postId = [[items objectAtIndex:indexPath.row] objectForKey:#"PostID"];
cell.post.text = [[items objectAtIndex:indexPath.row] objectForKey:#"Post"];
return cell;
}
I see your NewCell is subclassed.
Don't forget to include this method into your NewCell.m
- (NSString *) reuseIdentifier
{
return #"Cell Identifier";
}
Of course #"Cell Identifier" should be the same that you use in your cellForRowAtIndexPath:.
If you fail to implement this method each cell will be generated from scratch.
Are you using a dequeReusableCellWithIdentifier? Follow the format below. Since you now mention you are loading data from the web you need to do this asynchronously to allow for smooth scrolling. To load from a webservice (asynchronously) there is a nice project just for that here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"yourCellName";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
Setting your tableview to Reuse cells is the most basic way to ensure good performance. Basically it means that instead of creating a new cell for every cell in the tableview, your tableview will recycle the cells that are off screen. The basic setup is below, and more can be learned from the apple documentation on UITableViewDelegate linked here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Cell Identifier";
CustomCellClassName *cell = (CustomCellClassName *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
cell = [[CustomCellClassName alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
//Do basic cell construction common to all cells of this type here
//Set background, image etc.
}
//Do specific cell construction here
return cell;
If you're loading data over the network for each cell, you'll see poor performance. Batch your data fetch, then when it's ready tell your tableview to reload itself.
Using Core Data as a temporary backing store, and an NSFetchedResultsController to retrieve the info from Core Data, will save you some work.

Resources