I have to display image in tableview,i got all images but it does not display. Here Array contains 3 images, these images came from server. when cell for row at indexpath call it display only 3rd image that is last image 1st and 2nd row will be blank but when it scroll my tableview from bottom to top than only 1st and 2nd image displayed.
-
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selectionStyle = UITableViewCellSeparatorStyleNone;
cell.backgroundColor = [UIColor clearColor];
if (appDelegate.array_xml != (id)[NSNull null])
{
ObjMore = [appDelegate.array_xml objectAtIndex:indexPath.row];
//imageview
NSString *str_img = ObjMore.iconurl;
str_img = [str_img stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"str_img: %#", str_img);
self.imageicon = [[UIImageView alloc]initWithFrame:CGRectMake(10, 10, 50, 50)];
NSURL *url = [NSURL URLWithString:str_img];
NSLog(#"url %#",url);
[[AsyncImageLoader sharedLoader]cancelLoadingURL:url];
self.imageicon.imageURL = url;
self.imageicon.userInteractionEnabled = YES;
self.imageicon.tag = indexPath.row;
self.imageicon.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:self.imageicon];
}
return cell;
}
Please Help.
Thanks in Advance.
Please change your code -
[[AsyncImageLoader sharedLoader]cancelLoadingURL:self.imageicon.imageURL];
I'd suggest you to use this AsyncImageView. I've used it and it work wonders. To call this API:
ASyncImage *img_EventImag = alloc with frame;
NSURL *url = yourPhotoPath;
[img_EventImage loadImageFromURL:photoPath];
[self.view addSubView:img_EventImage]; // In your case you'll add in your TableViewCell.
It's same as using UIImageView. Easy and it does most of the things for you. AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files.
Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images.
The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes.
You create the object AsyncImageView instead of UIImageView
Are you refreshing the imageview or reloading the table row once you get the image ?
Also make sure you are refreshing the UI in main thread.
Related
I've been banging my head against the wall on this one and searched far and wide for a solution to no avail:
I have a large array of data pulled from the web and I'm using Loren Brichter's ABTableViewCell to make it run smoothly by drawing everything inside of the contentView of each cell to avoid UILabels and UIImageViews slowing scrolling down.
This works great for displaying text, but I run into a problem with images because of the time it takes to download them. I can't seem to find a way to force the contentView of each cell displayed to redraw itself once the corresponding image has been downloaded. I must point out I am not drawing labels and imageViews, but just the contentView in order to save memory.
Right now the table behaves like this:
Load: text displayed, no images
Scroll up or down: images finally
show up once the cells move off screen
A sample project is here
Code:
ABTableViewCell.h
#interface ABTableViewCell : UITableViewCell
{
UIView *contentView;
}
ABTableViewCell.m
- (void)setNeedsDisplay
{
[contentView setNeedsDisplay];
[super setNeedsDisplay];
}
- (void)drawContentView:(CGRect)r
{
// subclasses implement this
}
TableCellLayout.h
#import "ABTableViewCell.h"
#interface TableCellLayout : ABTableViewCell {
}
#property (nonatomic, copy) UIImage *cellImage;
#property (nonatomic, copy) NSString *cellName;
TableCellLayout.m
#import "TableCellLayout.h"
#implementation TableCellLayout
#synthesize cellImage, cellName;
- (void)setCellName:(NSString *)s
{
cellName = [s copy];
[self setNeedsDisplay];
}
- (void)setCellImage:(UIImage *)s
{
cellImage = [s copy];
[self setNeedsDisplay];
}
- (void)drawContentView:(CGRect)r
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextFillRect(context, r);
[cellImage drawAtPoint:p];
[cellName drawAtPoint:p withFont:cellFont];
}
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.cellName = [[array valueForKey:#"name"] objectAtIndex:indexPath.row];
cell.cellImage = [UIImage imageNamed:#"placeholder.png"]; // add a placeholder
NSString *imageURL = [[array valueForKey:#"imageLink"] objectAtIndex:indexPath.row];
NSURL *theURL = [NSURL URLWithString:imageURL];
if (asynchronousImageLoader == nil){
asynchronousImageLoader = [[AsynchronousImages alloc] init];
}
[asynchronousImageLoader loadImageFromURL:theURL];
cell.cellImage = asynchronousImageLoader.image;
return cell;
}
This is the final method the AsynchronousImageLoader calls once the image is prepared:
- (void)setupImage:(UIImage*)thumbnail {
self.image = thumbnail;
[self setNeedsLayout];
}
I just need the correct way to tell my visible cells to redraw themselves once the row's image has been downloaded. I imagine I should be putting something in that final method (setupImage)--but I can't seem to get it working the way it should. Thoughts? Many thanks!
Final edit: the solution
Right, so as suspected, the problem was that visible cells weren't being told to redraw and update to the downloaded image once the call was complete.
I used the help provided by the answers below to put together a solution that works well for my needs:
Added a callback in the final method that the asynchronous image downloader calls:
AsyncImageView.m
- (void)setupImage:(UIImage*)thumbnail {
self.cellImage = thumbnail;
[cell setNeedsDisplay];
}
Note: I also set a local placeholder image in the initialization of the image downloader class just to pretty things up a bit.
Then in my cellForRowAtIndexPath:
NSURL *theURL = [NSURL URLWithString:imageURL];
AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];
[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;
return cell;
There may have been one or two other tweaks, but those were the major problems here. Thanks again SO community!
Make sure that you are updating the cell image in the Main Thread. UI updates only appear if done there, which is why you only see the update when you touch & scroll.
if(cell) {
dispatch_async(dispatch_get_main_queue(), ^{
cell.cellImage = thumbnail;
[cell setNeedsDisplay];
});
}
[EDIT]
You need the cell to be the delegate of the image loader and own the async redraw mechanism.
......
AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
asynchronousImageLoader.delegate = cell;
[asynchronousImageLoader loadImageFromURL:theURL];
return cell;
}
And place the delegate call back code in the cell implementation.
- (void)setupImage:(UIImage*)thumbnail {
self.cellImage = thumbnail;
}
You can used the Apple TableView Lazy Loading. They have sample codes that download images asynchonously. See link below
Apple LazyTableImages
On your end in AsynchronousImages class you can add an attribute NSIndexPath and the delegate on AsynchronousImages should be change. See the code below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.cellName = [[array valueForKey:#"name"] objectAtIndex:indexPath.row];
cell.cellImage = [UIImage imageNamed:#"placeholder.png"]; // add a placeholder
NSString *imageURL = [[array valueForKey:#"imageLink"] objectAtIndex:indexPath.row];
NSURL *theURL = [NSURL URLWithString:imageURL];
AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
asynchronousImageLoader.indexPath = indexPath;
[asynchronousImageLoader loadImageFromURL:theURL];
return cell;
}
//Delegate should be
- (void)setupImage:(UIImage*)thumbnail index:(NSIndexPath*) indePath {
TableCellLayout *cell = (TableCellLayout *) [tableView cellForRowAtIndexPath:indexPath];
if(cell) {
cell.cellImage = thumbnail;
[cell setNeedsDisplay];
}
}
verify UIImage creation and setting of the UIImageView's image property happen on the main thread. there is no reason setting the image view's image should not invalidate its rect if visible.
also confirm that your loads are cancelled correctly, if you are reusing cells.
Use AsyncImageView in place of uiimageview
You can tell your tableView to reloadData... or a slightly more refined reload:
[self.tableView reloadRowsAtIndexPaths:self.tableView.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];
Edit to Add (after looking at OP's source):
I've looked through your project, and the problem is with your architecture. You're misusing AsyncImageView because you're only using it to asynchronously load your image - whereas it is designed to both load, and display the image. This is why it has no 'callback' function to let you know when the data has been retrieved.
You would be better off replacing your CellLayout's image property with a UIImageView property instead. (Note that UIImageView is more efficient at drawing than image drawAtPoint anyway).
So:
Change your CellLayout class to use an UIImageView property instead of UIImage
Change your cellForRowAtIndexPath to set the AsyncImageView as a property directly on your Cell.
If you want to support placeholders, that should be added to your AsyncImageView class - so that it knows what to display while downloading the content.
cellForRowAtIndexPath:
NSURL *theURL = [NSURL URLWithString:imageURL];
AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];
[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;
return cell;
CellLayout.h
#property (nonatomic, strong) UIImageView *cellImageView;
CellLayout.m
- (void)setCellImageView:(UIImageView *)s
{
[cellImageView removeFromSuperview];
cellImageView = s;
[self addSubview:cellImageView];
}
i am new i phone developer i am struggle with one problem i have use images in my app that images captured through web services . My problem is each images placed on each table cell that way i was compressed that image size in my cell size but image clarity was missing. please tell me how to set the image in cell with clarity.
i am very suffer this problem please give me any suggestion for me.
hi i am working with Twitter, i got problem with images, i have tweet view have UITableview, it contains all tweet with user photo, if i load those photos in each cell when i scroll the UITableview, it scrolling very very slowly please suggest me to reduce the memory size of photos and scroll the UITableView fast.
i heard the thumbnail can reduce the memory size, does it.(if not which method i have to choose and what thumbnail method do's) if so how to do that in this code (table view cell code)
i heard the thumbnail can reduce the memory size, does it.(if not which method i have to choose and what thumbnail method do's) if so how to do that in this code (table view cell code)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleGrouped reuseIdentifier:nil] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
NSURL *url = [NSURL URLWithString:[(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]]; //here Tweet is other class and image_url is method in the Tweet class
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[myImage setImage: [UIImage imageWithData:data]];
[cell.contentView addSubview:myImage];
[myImage release];
}
return cell;
}
Thanks in advance.
By using the Lazyloading concept you can make your tableview scroll fastly.
Just download this class for lazyloading.
It is very easy to impelement.
#import "UIImageView+WebCache.h" // Import this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
------------------
------------------
[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;
}
Then the picture clarity problem: Try to use the lower resolution images(50x50) like that
I am parsing an RSS feed, and then caching the images from the rss feed and then displaying them in the cell's imageview. However the method I am using, slows down the rss feed's parse time, and slows down the TableView's scroll time. Please could you tell me how I could speed up this process. One of the image links is: http://a1.phobos.apple.com/us/r1000/009/Video/95/5d/25/mzl.gnygbsji.71x53-75.jpg, and one of the rss feeds I am trying to parse is: http://itunes.apple.com/au/rss/topmovies/limit=50/xml. Here is the code I am using to cache the images:
- (UIImage )getCachedImage: (NSString)url
{
UIImage* theImage = [imageCache objectForKey:url];
if ((nil != theImage) && [theImage isKindOfClass:[UIImage class]]) {
return theImage;
}
else {
theImage = [UIImage imageWithData: [NSData dataWithContentsOfURL:[NSURL URLWithString: url]]];
[imageCache setObject:theImage forKey:url];
return theImage;
}
}
And the code I am using to get the images is:
- (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] autorelease];
}
// Configure the cell...
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
int wierd = storyIndex *6;
cell.textLabel.text = [[stories objectAtIndex: storyIndex] objectForKey: #"songtitle"];
cell.detailTextLabel.text = [[stories objectAtIndex:storyIndex] objectForKey:#"artist"];
if ([imageLinks count] != 0) {
cell.imageView.image = [self getCachedImage:[imageLinks objectAtIndex:wierd]];
}
return cell;
}
As you can probably see, I am using an NSMutableArray called imageLinks, to store the imageLinks. However I am getting three image links from the rss feed, which means if I try to get the cached image: [imageLink objectAtIndex:storyIndex], the images are in the wrong places, but if I get the cached image: [imageLink objectAtIndex:wierd], it seems to work perfectly. So if you can see a fix to that, it would be great.
Thanks in advanced.
Your problem is that you're using dataWithContentsOfURL which is a blocking API. This means that it is performed on the main thread along with your UI and will block your UI until it completes. This is bad.
You should look into the NSURLConnection class and it's delegate protocol, NSURLConnectionDelegate to do data downloads asynchronously without manually spawning and managing new threads.
using iOS 4.1 SDK. I am using 2 small images in each row of a UITableView. I wanted to know which of the following 2 methods was better, also is Method 1 valid at all?
- (void)viewDidLoad
{
// create the images amd assign to class member variable
NSString *imgStr1 = [[NSBundle mainBundle] pathForResource:#"someImg1"
ofType:#"png"];
UIImage* img1 = [[UIImage alloc] initWithContentsOfFile:imgStr];
self.image1 = img1;
[img1 release];
NSString *imgStr2 = [[NSBundle mainBundle] pathForResource:#"someImg2"
ofType:#"png"];
UIImage* img2 = [[UIImage alloc] initWithContentsOfFile:imgStr2];
self.image2 = img2;
[img2 release];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
cellIdentifier];
if (cell == nil)
{
//create image views here
..........................
}
/ assign images from viewDidLoad to imageView here
UIImageView *img1View = (UIImageView *)[cell viewWithTag:kImg1Tag];
[img1View setImage:self.img1];
etc....
}
OR should i just do this in the cellForRowAtIndexPath
[img1View setImage:[UIImage imageNamed:#"img1.png"];
In this case I would go with imageNamed: as it will cache the two images and properly respond to memory warning situations.
Method one is valid, but there is little difference between it and using imageNamed:. Images created with imageNamed: will be cleared out if the device needs to reclaim memory. Unless you clear the images created in method one yourself when you receive a memory warning they will stay in memory.
It's also less code and less that you have to worry about, which is always better. Less code == less bugs.
I think the simplest way is to use UIImage's imageNamed: method, which loads the image from the app bundle and keeps it in cache.
This way you would only have to set the cell's UIImageView's image to [UIImage imageNamed:#"img1.png"] in cellForRowAtIndexPath: method.
Another point, if you cell has many subviews, I think subclassing it and adding different subviews as class properties is better. Then you only have to cast it when getting it from dequeueReusableCell and it allows you to modify subviews without using tags and casting everytime.
I have a tableview with large images that fill the cells and the row heights are set based on the image size. Unfortunately, the table jerks badly when scrolling to the next cell.
I've been told that my tableview will scroll more smoothly if I cache the row heights and the images before they are loaded into the table.
All my data are stored in a plist.
How do I go about caching something?
What does the code look like and where does it go?
Thanks!
Here's my code for loading the images:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *detailTableViewCellIdentifier = #"Cell";
DetailTableViewCell *cell = (DetailTableViewCell *)
[tableView dequeueReusableCellWithIdentifier:detailTableViewCellIdentifier];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DetailTableViewCell" owner:self options:nil];
for(id currentObject in nib)
{
cell = (DetailTableViewCell *)currentObject;
}
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *MainImagePath = [Path stringByAppendingPathComponent:([[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:#"MainImage"])];
cell.mainImage.image = [UIImage imageWithContentsOfFile:MainImagePath];
return cell;
}
I'm also using the following for calculating the row height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
AppDelegate *appDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *MainImagePath = [Path stringByAppendingPathComponent:([[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:#"MainImage"])];
UIImage *imageForHeight = [UIImage imageWithContentsOfFile:MainImagePath];
imageHeight = CGImageGetHeight(imageForHeight.CGImage);
return imageHeight;
}
EDIT: Here is the final code below.
#define PHOTO_TAG 1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Photo";
UIImageView *photo;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UIImage *theImage = [UIImage imageNamed:[[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:#"MainImage"]];
imageHeight = CGImageGetHeight(theImage.CGImage);
imageWidth = CGImageGetWidth(theImage.CGImage);
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
photo = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, imageWidth, imageHeight)] autorelease];
photo.tag = PHOTO_TAG;
[cell addSubview:photo];
} else {
photo = (UIImageView *) [cell viewWithTag:PHOTO_TAG];
[photo setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
}
photo.image = theImage;
return cell;
}
Caching is not a panacea for tableview performance. Caching is only valuable if there is something expensive to calculate, and you can avoid calculating it. If, on the other hand, you simply have too many views in your UITableViewCell, then caching will do nothing for you. If your row heights are all the same, then there's nothing to cache. If you use +[UIImage imageNamed:], then the system is already caching your images for you.
The most common first-order problem with UITableViewCells is putting too many subviews in them. How have you constructed your cell? Have you spent time studying the Table View Programming Guide, particularly A Closer Look at Table-View Cells? Understanding this document will save you much grief later.
EDIT: (Based on code above)
First, you're fetching a reusable cell, and then immediately throwing it away, reading a NIB and iterating over all the top level objects looking for a cell (one that looks almost exactly like the one you just threw away). Then you work out a string, which you use to open a file and read the contents. You do this every time UITableView wants a new cell, which is a lot. And you do it over and over again for the same rows.
Then, when UITableView wants to know the height, you read the image off of disk again. And you do that every time UITableView asks (and it may ask many times for the same row, though it does try to optimize this).
You should start by reading the UITableView Programming Guide I link above. That's hopefully going to help a lot. When you've done that, here are the things you should be thinking about:
You indicated that there is nothing but a single image view in this cell. Do you really need a NIB for that? If you do stick with a NIB (and there are reasons to use them in some case), then read the Programming Guide about how to implement a NIB-base cell. You should be using IBOutlet, not trying to iterate over the top-level objects.
+[UIImage imageNamed:] will automatically find files in your Resources directory without you having to work out the bundle's path. It will also cache those images for you automatically.
The point of -dequeueReusableCellWithIdentifier: is to fetch a cell that UITableView is no longer using and that you can reconfigure rather than you making a new one. You're calling it, but you immediately throw it away. You should check if it returned nil, and only load it out of the NIB if it did. Otherwise, you just need to change the image. Again, read the Programming Guide; it has many, many examples of this. Just make sure that you really try to understand what -dequeueReusableCellWithIdentifier: is doing, and don't treat it as just something you type at this point in the program.
If you do need to cache the heights, I did something like this (caching heights for a cell displaying an "article" object - article maybe one of several subclasses):
+ (CGFloat) heightForArticle: (Article*) article atWidth: (CGFloat) width {
static NSCache* heightCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
heightCache = [NSCache new];
});
NSAssert(heightCache, #"Height cache must exist");
NSString* key = #"unique"; //Create a unique key here
NSNumber* cachedValue = [heightCache objectForKey: key];
if( cachedValue )
return [cachedValue floatValue];
else {
CGFloat height = 40;//Perform presumably large height calculation here
[heightCache setObject: [NSNumber numberWithFloat: height] forKey: key];
return height;
}
}