UITableViewController scrolling lag with large amount of images - iphone

I have following code of cellForRowAtIndexPathmethod:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"champion cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UIImage *cellIcon = [UIImage imageNamed:[arrayOfChampionPictures objectAtIndex:indexPath.row]];
[[cell imageView] setImage:cellIcon];
cell.imageView.frame = CGRectMake(0.0f, 0.0f, 70.0f, 70.0f);
cell.imageView.layer.cornerRadius = 7;
[cell.imageView.layer setMasksToBounds:YES];
cell.textLabel.text = [arrayOfChampionNames objectAtIndex:indexPath.row];
return cell;
}
Where arrayOfChampionNames is local database stored in NSMutableArray that contains pictures names. It's about 103 cells with images in my UITableView. It lags at first scroll from beginning to end, after that it's scrolling smoothly. There's no lags on simulator.
Possible solutions I came up with, but i don't know how to realise them
Load images after scrolling,
Preload image data into UITableView.

I'll tell you where the lag is coming from - corner radius. Pre compute the images rounded corners. Doing them dynamically kills scrolling performance.

You forgot something:
static NSString *CellIdentifier = #"champion cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
This way you'll actually utilize the reusable identifier that you created.

Related

Images appear after scrolling table view (AFNetworking)

I am using AFNetworking to download images from server , everything works fine but images wont't be appear unless I scroll table view . here is my code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//configuring news feed
dictionary = [newsFeed objectAtIndex:indexPath.row];
//display thumbnails
UIImageView *image = [[UIImageView alloc]init];
[image setImageWithURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]placeholderImage:[UIImage imageNamed:NEWSAVATAR]];
cell.imageView.image = [image.image imageScaledToSize:CGSizeMake(55, 55)];
[cell setNeedsLayout];
return cell;
}
Try this
[ cell.imageView.image setImageWithURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]placeholderImage:[UIImage imageNamed:NEWSAVATAR]];
This delegate method is called only when that cell is visible i.e you have scrolled to the cell. If you dont want this to happen you have to call a web-service and save all the images locally and then pick it from your local folder.

UITableView not scrolling smooth due to removeFromSuperview

What makes scrolling so choppy on the UITableView? In my mind following code is a culprit. I am having very hard time to replace this logic with something other.
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview]; }
This is what I am doing.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
[cell.contentView addSubview:[self getHedlineFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getThumbnailImageFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getAbstractParaFromArticleAbstract:articleAbstract]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell; }
I am doing, addSubview to contentview because I am creating a dynamic cell height. Is there any way I can make this scroll view work smooth ? Thank you for your help.
You should design your cell as you need it. Add labels and whatever you need to the cell, and then change the content of these already available subviews.
If you need to display an image, add once an UIImageView to the cell and only change the image property of it. Same for text fields and so on.
The way you do it makes the built-in cache useless, because you regenerate all subviews again and again..
To boost the performance even more, you can do the drawing of the cell by yourself.
Apple has a quite nice example project:
http://developer.apple.com/library/ios/#samplecode/AdvancedTableViewCells/Introduction/Intro.html
You are right that the problem is caused by how you return cells. The correct pattern is as follows...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// see if cell contains our image views. A reused cell will, but a new cell won't
UIImageView *imageViewA = (UIImageView *)[cell viewWithTag:32];
UIImageView *imageViewB = (UIImageView *)[cell viewWithTag:33];
UIImageView *imageViewC = (UIImageView *)[cell viewWithTag:34];
if (!imageViewA) {
// the cell must be new, so create it's image views
// you should be able to borrow most of this code from your getHeadline/thumbnail/etc methods.
// the good news is that this relatively expensive code runs only for new
// cells and there are only a few of those - only enough to fill the visible frame
imageViewA = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewA];
imageViewA.tag = 32;
imageViewB = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewB];
imageViewB.tag = 33;
imageViewC = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewC;
imageViewC.tag = 34;
// this too, need only be done upon creation
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// now, whether it's a new cell or a reused cell, we have image views
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
// change your methods getHeadline... getThumbnail... etc to answer UIImages
// not UIImageViews, which are setup only for new cells
imageViewA.image = [self getHedlineFromArticleAbstract:articleAbstract]];
imageViewB.image = [self getThumbnailImageFromArticleAbstract:articleAbstract]];
imageViewC.image = [self getAbstractParaFromArticleAbstract:articleAbstract]];
// as a side note, once you get these methods returning images (more like model objects)
// rather than image views (view objects) they might be more appropriately placed
// in the BGMArticleAbstract class rather than the view controller
return cell;
}

Dequeueing UIViews withing UITableViewCell

In a normal situation when working with a UITableView I have the standard code for reusing old cells:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
return cell;
}
I noticed, however, that in the case when I added subviews to the cell that they weren't deleted and that a new view were added every time. I have an example below that demonstrate it perfectly:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
UILabel *label = [[UILabel alloc] init];
label.text = #"HELLO";
label.frame = CGRectMake(arc4random() % 50, -1, 286, 45);
label.backgroundColor = [UIColor clearColor];
// Add views
[cell addSubview:label];
return cell;
}
I need some code that reuses my labels again in the same way the cells are being reused. What should I do?
Thanks
You must only add the subviews if you are making a new cell. If you are dequeuing, the subview is already present and should not be re-created.
Your method should be:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellIdentifier];
UILabel *label;
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
label = [[UILabel alloc] init];
label.tag = 1;
// Add views
[cell addSubview:label];
}
else
{
// Label will already exist, get a pointer to it
label = [cell viewWithTag:1];
}
// Now set properties on the subview that are unique to each cell
label.text = #"HELLO";
label.frame = CGRectMake(arc4random() % 50, -1, 286, 45);
label.backgroundColor = [UIColor clearColor];
return cell;
}
Note how the label is only created when the cell is nil. Otherwise, it is found using the tag.
I need some code that reuses my labels again in the same way the cells
are being reused.
No, you need to understand the table view design better. It should be obvious why the views are being added multiple times – reusing a cell means that you take a previous instance of UITableViewCell that’s no longer needed (thus saving a costly allocation of a new object) and reuse this instance for the new cell. But this previous instance already has the label attached to it, so the number of labels grows.
I would subclass UITableViewCell and put the label creation inside the initialization code for this new class. (Or create a UIView subclass and set it as the cell’s contentView, as suggested in this nice table tutorial by Matt Gallagher.) That’s the proper way to encapsulate the view details and hide them from the table data source.
you can use something like in the else part for if(cell == nil)
for (UIView *sub in [cell.contentView subviews])
{
if([UILabel class] == [sub class])
NSLog(#"%#",[sub class]);
UILabel *label = (UILabel *)sub;
//do label coding ie set text etc.
}
I use lazy initialization of views within my custom table cell class.
It only needs to load views and "addSubview" once.
- (void) lazyInitTitleLabel {
if (_titleLabel != nil) {
return;
}
_titleLabel = [[UILabel alloc] initWithFrame: CGRectMake(10.0f, 10.0f, 200.0f, 30.0f)];
// Cell adds the label as a subview...
[self addSubview: _titleLabel];
}
The only thing you need to be careful about is resetting any content that views display like text in your labels and images in your image views. If you don't old content may get reused along with the recycled table cells.
Good luck!

Limit the size of an UIImage which is set programmatically

I'm dynamically loading images from a web service. The images will be loaded (with [UIImage imageWithData:data];) and be placed in a in an UIImageView in my custom UITableViewCell using the setImage message
. I gave the imageView a width and height of 27 by 19 in the interface builder and told him to "Aspect fit" (tried all the others too btw), but the image doesn't do that. It just scales (in aspect, I think) to fill the view cell.
I tried a lot of things, like resizing the image (does the job, but I can count the pixels on my retina display), resizing the UIImageView... But I just don't get is... Someone got an idea what I'm doing wrong?
The code that changes the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"NewsTableViewCell" owner:self options:nil];
cell = self.tableCell;
self.tableCell = nil;
}
viArticle *article = [self.viData.articles objectAtIndex:indexPath.row];
UILabel *label;
label = (UILabel *)[cell viewWithTag:1];
label.text = article.title;
label = (UILabel *)[cell viewWithTag:2];
label.text = [NSString stringWithFormat:#"%# %#", article.time, article.type];
if (article.image != nil) {
UIImageView *imageView;
imageView = (UIImageView *)[cell viewWithTag:0];
[imageView setImage:article.image];
}
return cell;
}
This is the view:
And the settings:
What happens if you untick "Autoresize Subviews"?
#GuidoH hey sorry for the late reply..
Here is the code that will add the image to the table view cell.
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *SimpleTableIdentifier = #" SimpleTableIdentifier ";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier] autorelease];
}
UIImage *image = [UIImage imageNamed:#"star.png"];
cell.imageView.image = image;
NSUInteger row = [indexPath row];
cell.textLabel.text = [listData objectAtIndex:row];
return cell;
}
//use this delegate it will increase the row size and thus your imagesize will get increased too.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80; //value can be changed
}
I would suggest scaling the images will surely lead to slow performance by your app.
I just posted a "solution" earlier today, but that seem to work, but was a wrong assumption. After asking on IRC I found the real solution. I was using the tag 0, that one shouldn't be used. Changing it to an higher value really solved my problem. Sometimes it can be thát easy. :)
Thanks y'all.
If you want to resize the UIImageView you can use the frame property.
suppose you have a UIImageView declare in .h file(lets take UIImageView *myImage;) and it is linked to the interface builder too.
you have synthesis it in .m class etc.
then when you want to resize the UIImageView, use
myImage.frame = CGRect(x,y,width,height);
Note: by declaring the width and height in the code, the width and height declared in the IB will be override.
then dont forget to add it to subview,
i.e [self.view addSubview:myImage];
and its done, you can see the changes.

UITableViewCell With image custom size

I'm loading an image in to an UITableView through a simple
cell.imageView.image = [UIImage imageNamed:#"prem.jpg"];
However, what I've learnt from my limited time with xcode things arent normally that easy! I want to have the image in there take up the fullsize of the image which is about 280 wide and 150 tall. From googling I might have to build a custom cell and put the image in there and then load that custom cell? I've not done anything like that before and it seems a bit annoying just to load one image. Is there an alternative?
To put in context, I have 3 sections in the tableview top section will be the image, and the other two are arrays loaded from XML files. If I do a custom cell I have to do a seperate xml call to get the image url before it goes through so I'd really like to avoid that if possible?
Really looking for any pointers you can offer. Thanks a lot
Tom
Edit: cellforrowindexpath as requsted:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if(indexPath.section == 0)
cell.imageView.image = [UIImage imageNamed:#"prem.jpg"];
if(indexPath.section == 1)
cell.textLabel.text = [arryTableData objectAtIndex:indexPath.row];
else if(indexPath.section == 2)
cell.textLabel.text = [arryTableActions objectAtIndex:indexPath.row];
return cell;
}
There is no need to create a custom cell subclass. I am assuming you have a single cell in section 0 and a variable number of cells in sections 1 and 2.
You should be using a different cell reuse identifier for your cell in section 0 as it contains an image view and the others don't - this will show up in your later sections when the cells are recycled otherwise.
I'd try something like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifierText = #"TextCell";
static NSString *CellIdentifierImage = #"ImageCell";
if (indexPath.section == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierImage];
// I am assuming this image never changes and there is one row in this section! If it does change, use viewWithTag to get hold of the image view.
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierImage] autorelease];
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,250,180)];
myImageView.tag = 1;
myImageView.image = [UIImage imageNamed:#"prem.jpg"];
[cell addSubview:myImageView];
[myImageView release];
}
return cell;
}
else
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierText];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierText] autorelease];
}
if(indexPath.section == 1)
cell.textLabel.text = [arryTableData objectAtIndex:indexPath.row];
else if(indexPath.section == 2)
cell.textLabel.text = [arryTableActions objectAtIndex:indexPath.row];
return cell;
}
}
Make sure you are adding the image to the newly created image view and not cell.imageView as the latter is a read-only property which is over to the left side, I'm not sure you can resize it.
You will also need to set the height for your cell, as it is taller than the standard. This will be in the tableView:heightForRowAtIndexPath: delegate method.
you can use the customCells.Means take an image view (whatever size you want.).And add it to the cell in cellforIndexpath method.