I have a UITableViewCell that contains 4 photos and i get these photos from the web but the problem is when i scroll down the UITableView these photos are downloaded again
And this is the code:
ITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellPhoto"];
if (cell == nil) {
NSArray *nibObject = [[NSBundle mainBundle] loadNibNamed:#"CustomCellThumbnails" owner:self options:nil];
cell = [nibObject objectAtIndex:0];
}
// Get the photos
int getPhotos = indexPath.row * 4;
for (int i = 1; i <= 4; i++) {
if (getPhotos < [imageArray count])
{
UIButton *imageButton = (UIButton*)[cell viewWithTag:i];
NSString *url = [imageArray objectAtIndex:getPhotos];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.siteweb.com%#",url]]];
UIImage *imageFieldProfile = [[UIImage alloc] initWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
// Set the phoyo to the UIButton
[imageButton setBackgroundImage:imageFieldProfile forState:UIControlStateNormal];
[imageFieldProfile release];
// Set the corner of UIButton
[imageButton.layer setCornerRadius:5.0];
[imageButton.layer setMasksToBounds:YES];
imageButton.tag = getPhotos;
});
});
[imageButton addTarget:self action:#selector(displayPhoto:) forControlEvents:UIControlEventTouchUpInside];
}
getPhotos ++;
}
You should use the view controller to fill the cells, not the UITableViewCell. If you do that, it's not only a better coding style, it's also easier to save the data.
Anyway, if you really must: initialize the UITableViewCell with some kind of storage table, so that you can store the data you downloaded: rewrite initWithStyle:reuseIdentifier: to initWithStyle:reuseIdentifier:usingCacheTable:
But, again, the correct way to do this is to load the data in the view controller and let the UIView subclasses simply only show stuff.
You should save or cache the images in another object when they are downloaded, not in the table view cell. Perhaps using some sort of data source or model object from which the table view cell requests the images, instead of having the table view cell directly make any URL requests. Then the model object can cache images after downloading and before handing them to the cell.
You could use a combination of lazy loading image views in combination with local caching. This would be rather easy to accomplish using ASIHTTPRequest in combination with correctly setup caching flags.
ASIHTTPRequest is extremely easy to use and its caching is very well controllable.
In contrast to the other solutions suggested, I would stick to use UITableView and its UITableViewCells as this will allow you to use de/queued cells without having to build such logic yourself.
I have used such solution for a major newsmagazine app (over 2mio downloads) and am totally satisfied with the results.
Related
I have to 20-25 download images of 50 Kb- 2 Mb each and show them in a tableview.
I used ASIHTTPRequest asyn request to this. I observed that after some time the app gets stuck. This should not happen because I am using a async call. I thought something is wrong with ASIHTTPRequest and I observed that The didFinished selector gets called in the main thread. The only thing which I do is
-(void)didFinishedDownloadingImage:(ASIHTTPRequest*)request
{
NSData *responseData = [request responseData];
UIImage *image = [UIImage imageWithData:responseData];
[[data objectAtIndex:request.tag] setImage:image];
[self.tableView reloadData];
}
I don't think this should cause any problem. Also in cellforrowatindexpath I just do
- (UItableViewCell *)tableviewView:(UItableView *)tableview
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UserProfile * user = [data objecAtIndex:indexpath.row];
UITableViewCell *cell = [tableView
dequeueReusableCellWithReuseIdentifier:#"ProfileCell"
forIndexPath:indexPath];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewDefaultStyle];
}
NSString *fullname = [NSString stringWithFormat:#"%#\n%#",
user.firstname, user.lastname];
if(user.image != nil)
[cell.imageView setImage:user.image];
else{
[cell.imageView setImage:[UIImage imageNamed:#"placeholder.jpg"]];
}
[cell.label setText:fullname];
return cell;
}
But the app is slow and freezes for 1-2 sec which is a considerable amount of time.
I have seen apps which does this very smoothly. I tried using an image of fixed size 5Kb which has a very significance performance improvement with using the above code. I don't know why should that make a difference for big images in this case because all downloading is happening in other thread via ASIHTTP .
Please, replace your framework with AFNetworking.
You can simple use..
IImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
[imageView setImageWithURL:[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:#"placeholder-avatar"]];
or... directly in TableViewCell
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:#"artworkUrl100"]];
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder"]];
"In the second line, we tell the image view where the thumbnail is located by passing an NSURL and we pass in a placeholder image, which is shown as long as our request has not returned a response"
Thats all!
Here you have an tutorial about that http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_afnetworking/
It's easy to make assumptions about the root cause of a laggy/slow application. Instead of guessing, why don't you test your suspicions? Profile your application with the Time Profiler instrument. It'll tell you which methods and functions your application is spending the most time in.
Here are some ideas until you have a chance to profile:
You might consider downloading the full-res images and creating thumbnails in the background and then caching them in an NSCache object. You can also run [UIImage imageWithData:responseData]; in a background thread. It's thread-safe until the point at which it interacts with the view hierarchy.
Selectively reloading a single cell should be faster than reloading the entire tableview, especially one with lots of images. Furthermore if you're doing all of the networking and processing on a background queue, there's no reason scrolling the tableview should be slow. Can you show us your entire implementation of the -cellForRowAtIndexPath: method? You've mentioned that you think setImage: is your slow point because rendering is slow. If you reload a single cell, only one cell needs to be rendered. If you reload the entire tableview, every cell must be re-rendered.
I have three different cells. The Cell01, the Cell02 and the Cell03. The Cell01 must appear just one time, at the top of the tableview, and the rest, the 02 and 03 must interpolate (02, 03, 02, 03 (...)).
The problem is that there's some "lag" when scrolling the TableView. I'm loading images from the document folder, and I'm also resizing it to don't require too much processing, but it's still scrolling slowly. It's reusing cells (I checked the if(!cell)).
Here's my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = indexPath.row;
if (row == 0) {
Cell01 *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell01ID"];
if (!cell) {
cell = (Cell01*)[[[NSBundle mainBundle] loadNibNamed:#"Cell01" owner:self options:nil] objectAtIndex:0];
cell.someLabel.text = #"First";
cell.someImage.image = [self imageInDocumentsDirectoryWithName:#"mainimage" andSize:CGSizeMake(200, 200)];
}
} else if (row % 2 == 0) {
Cell02 *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell02ID"];
if (!cell) {
cell = (Cell02 *)[[[NSBundle mainBundle] loadNibNamed:#"Cell02" owner:self options:nil] objectAtIndex:0];
cell.randomLabel.text = #"Second";
cell.someImage.image = [self imageInDocumentsDirectoryWithName:#"secondimage" andSize:CGSizeMake(200, 200)];
}
} else {
Cell03 *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell03ID"];
if (!cell) {
cell = (Cell03 *)[[[NSBundle mainBundle] loadNibNamed:#"Cell03" owner:self options:nil] objectAtIndex:0];
cell.anotherLabel.text = #"Third";
cell.someImage.image = [self imageInDocumentsDirectoryWithName:#"thirdimage" andSize:CGSizeMake(200, 200)];
}
}
}
- (UIImage *)imageInDocumentsDirectoryWithName:(NSString *)fileName andSize:(CGSize)size
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:fileName];
UIImage *image = [UIImage imageWithContentsOfFile:path];
image = [image resizedImageToFitInSize:size scaleIfSmaller:YES];
return image;
}
Any idea how to improve this tableview (and make it scrolls faster)?
I think scrolling slowly is due to loading image on cell from document directory. fetching images from Doc dir takes some times and when ever u scroll ur table view, a new cell is created and image is loaded from doc dir which takes some time. try to use Lazy loading of image on cell. Also load images in a new thread not in main thread..
There is a sample project of Apple which helps u. lazy loading table view
I hope this will helps u .
Consider loading the images asynchronously. You could keep the references to all the UIImageView in a mutable array, start loading images in an asynchronous block, e.g. using GCD, and then update the image views as they get loaded.
Here's a rough example of how it could look (this assumes you have an ivar NSMutableArray *_imageViews, initialized with the correct size of elements, and filled with nils). I'm coding from the hip here, but I hope you get the idea. BTW, you can fill the mutable array with nils by inserting [NSNull null] into it.
I promise that if you implement this correctly, you'll see a marked improvement in the scrolling speed :)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = indexPath.row;
if (row == 0) {
Cell01 *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell01ID"];
if (!cell) {
cell = (Cell01*)[[[NSBundle mainBundle] loadNibNamed:#"Cell01" owner:self options:nil] objectAtIndex:0];
cell.someLabel.text = #"First";
[_imageViews replaceObjectAtIndex:row withObject:cell.someImage];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
UIImage *image = [self imageInDocumentsDirectoryWithName:#"thirdimage" andSize:CGSizeMake(200, 200)];
// because UI needs to be updated on the main thread,
// dispatch yet another block.
dispatch_async(dispatch_get_main_queue(), ^{
UIIMageView *imageView = [_imageViews objectAtIndex:row];
ImageView.image = image;
});
});
}
// et cetera
}
There are two potential performance issues with your current implementation. The first is using NSBundle instead of UINib to load the nib files. NSBundle reloads the nib file from the filesystem each time, whereas UINib reads a nib file once and caches its content in memory. Since it doesn't need to reread the nib from the filesystem each time, instantiating the objects should be orders of magnitude faster using UINib.
So instead of doing this...
cell = [[[NSBundle mainBundle] loadNibNamed:#"Cell02" owner:self options:nil] objectAtIndex:0];
...do this:
cell = [UINib loadNibNamed:#"Cell02" owner:self] objectAtIndex:0];
The second problem is similar; the current implementation is reloading the same images from the filesystem each time instead of caching them in memory and reusing them. To solve this, you could add an instance variable of type NSArray or NSDictionary (depending on how you prefer to retrieve the images), and then check before loading the image to see if it's already in the collection. If not, load the image and store it in the collection before using it. Otherwise, used the cached image instead of loading it again.
So instead of doing this...
cell.someImage.image = [self imageInDocumentsDirectoryWithName:#"thirdimage" andSize:CGSizeMake(200, 200)];
...do something like this:
// Note: Consider defining constants for the keys/filenames.
UIImage *image = [self.cachedImages objectForKey:#"thirdimage"];
if (image == nil) {
image = [self imageInDocumentsDirectoryWithName:#"thirdimage" andSize:CGSizeMake(200, 200)];
[self.cachedImages setObject:image forKey:#"thirdimage"];
}
cell.someImage.image = image;
If you need to cache a lot of images this way, consider using NSCache instead of NSDictionary to help keep the cache from growing too large.
I just finished implementing something similar, and if you make sure that all of the images in the documents directory are already the correct size (200 x 200) then this will work just fine without having to load asynchronously.
If you need the images to be larger, then generate thumbnails that are the correct size when you save the original file, as it actually is taking too much processing to resize the images in "real-time".
In this case though, you only have three images. If you simply cache the image after it has been resized and keep reusing it (instead of making new ones), then it will be even faster than that.
I have three arrays and I have copied all these arrays into a single array. All these three arrays are arrays of dictionaries.All arrays has a field called picture, but that pictures is coming from different sources- URL in one array, data in other and files in the third one.
Say, Array1 has dictionaries with a key - picture and its loaded from NSURL.
Similarly, Array2 and Array3 has dictionaries with same key name - picture and loaded from ContentofFiles and NSData.
Now, I want to populate tableview, of course,m having Custom UITableViewCell, it has image view as its content view. To load that image, what should I do.
I was doing this thing..
NSURL *url = [NSURL URLWithString:[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"]];
cell.contactImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
But, this will crash if cell.contactImageView.image don’t receive image from NSURL.So, what should I do? Any help, will be appreciated
But,
all you is to check if the received image is null and if it is, then set a template photo image called no photo like a photo on facebook when no profile picture is selected
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if (img)
cell.contactImageView.image = img;
else
cell.contactImageView.image = [UIImage imageNamed:#"no_photo.png"];
If these images are retrieved from the net, i would suggest not using [NSData dataWithContentsOfURL:].
You should be using a non blocking method, that loads images asynchronously. Using this method on numerous rows in a table will cause performance issues.
Here's my recommendation , use the SDWebImage Library . Easy to use, and even easier to install.
Once you add the library to your project, simply #import the UIImageView+WebCache.h class usage example is below.
[cell.contactImageView.image setImageWithURL:[NSURL URLWithString:[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You can add one more key in dictionary which will specify from where to take photo and accordingly you can provide image to cell.contactImageView, if you want to provide image from different sources.
Thanks a lot buddies for your Quick reply,especially #skram, due to your suggestion,my tableview performance has increased much. the Question, that I have asked,the better answer that I have thought of is using iskindofClass. If image is coming from any class, on a condition ,we can check from where that image was coming and populate our image accordingly.
if ([[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"] isKindOfClass:[UIImage class]])
{
cell.contactImageView.image = [[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"];
}
else if ([[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"] isKindOfClass:[NSString class]])
{
cell.contactImageView.image = [[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"];
}
else if([[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"] isKindOfClass:[NSURL class]])
{
[cell.contactImageView setImageWithURL:[NSURL URLWithString:[[contactList objectAtIndex:indexPath.row] objectForKey:#"picture"]]placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
Now, I’m able to populate the tableview properly.Once again thanx to scram.
I am creating an application which uses a web service.And retrieves a list of users and there details like images, user id and there names.I displayed all the information related to users in table view, Thus each of the cell in the table view has an image with the for tables in it. I am using a Custom cell class to create the individual cells. selecting any row in table presents a detail view (using navigation) which show a larger image and all the details related to the particular user i selected.
in customcell and detail view class i am using web view to display image.
But when i run the app the images gets a bit of delay to display and same happens with the detail view.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading and detail view??
here is the code...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCellIdentifier ";
CustomCell *cell = (CustomCell *)[tableView
dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil ];
//cell = [[CustomCell alloc] initwi ];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
}
NSUInteger row = [indexPath row];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
//NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData]; /// check to see if we are getting all the arrays such as image array and userId array and name array of same size..
if(image == nil)
{
}
cell.imgView.image = image; /// other wise an execption of out out array range will be shown
[image release];
[imageName release];
//[cell.webView initWithFrame:CGRectZero];
//[cell.webView loadRequest:requestObj];
//cell.webView.frame = CGRectMake(20, 0, 80.0, 64);
cell.userIdLabel.text = (NSString *)[userId objectAtIndex:row];
cell.nameLabel.text = (NSString *)[userName objectAtIndex:row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}`
i think problem can be in imgArray array as i am setting it from other class. Where i request the web service and fetched all the data about users .
Any help is greatly appreciated . Thanks in advance
I got the same problem. For that I used EGOImageCache and some thing.
Find this Url for EGOImage
Download files those names starts with "EGO", and add those to your project.
and write the following code where ever you want to put the image:
EGOImageView *imgView = [[EGOImageView alloc] initWithFrame:CGRectMake(50,50,220,360)];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
imgView.imageUrl = url;
[self.view addSubView:imgView];
Thats it. Use the similar code, where ever you want the image.
At the first time while loading it will take some time to download the image. But later it will cache the image and use it the next time.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading
For this the answer is Lazy Loading of images in cells
You can implement lazy loading on webviewDidLoad which shows that webview has loaded completely
Using a UIWebView to display a single image is overkill. You should be using NSURLConnection (or one of many alternative HTTP wrappers/libraries) to load the image data and UIImageView to display it in each of your table cells. In my experience, there is no way (or at least no straightforward way) to eliminate the rendering delay when using UIWebView.
Why are you using the webview for displaying the images. Imageview should be used instead. If you are getting the images from the server then you should get those in separate thread and after you receive the images you should reload that particular row.
Some code snippet is like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
...
...
UIImage *img = (UIImage *)[imagesArray objectAtIndex:indexPath.row];
if(img == nil){
[imgView setImage:temporaryImage];
[NSThread detachNewThreadSelector:#selector(getTheThumnbails:) toTarget:self withObject:indexPath];
}
else
[imgView setImage:img];
for further assistance look at this
Hope this helps.
Thanks,
Madhup
okay finally i removed the uiweb view from my app and used Imaged view and succeed.
i used the link that was given by the developer of the tweetie . here it is link text
i used the ABTableViewCell class and created the whole cell using the code.
The scrolling was very jerky when i executed the app. after applying almost 4 hours and with the help of lazy loading i was able to run my app smoothly. I stored the data which is retrieved from url into an array then applied the concept in tableView:cellForRowAtIndexPath:
method..
Thanks you guys for helping me ...
Three20 has a rich-text table view cell class (TTStyledTextTableCell I think) - should render significantly faster than an embedded UIWebView. You could also roll your own, though that would take a lot longer.
I use datamodel to store 2 objects : Video, Images.
Video contain just string attributes and Images have 2 "Binary data" attributes.
At the start the 2 binary data attributes was in the video object.
But all videos are loading during initialization of UITableView.
For 400 videos binary data represent 20 Mo, so imagine with 4000 videos...
Now with 2 objects the UITableView loading work well.
I load binary data when it's necessary in the method : tableView:cellForRowAtIndexPath
But now more I scroll into the list, more the memory grow up :(
look at my method :
- (UITableViewCell *)tableView:(UITableView *)myTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"videoCell";
Video *theVideo = (Video *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
VideoCellViewController *cell = (VideoCellViewController *)[myTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"VideoCellView" owner:self options:nil];
cell = editingTableViewCell;
self.editingTableViewCell = nil;
}
cell.video = theVideo;
return cell;
}
And the method setvideo in VideoCellViewController
- (void)setVideo:(Video *)newVideo {
if (newVideo != video) {
[video release];
video = [newVideo retain];
}
NSData *imageData = [video.allImages valueForKey:#"thumbnailImage"];
UIImage *uiImage = [[UIImage alloc] initWithData:imageData];
smallImage.image = uiImage;
nameLabel.text = video.displayName;
[uiImage release];
}
Even without set the smallImage, I have memory trouble.
If I load the image object, it's never release.
I try a lot of solution to release memory without succes...( didTurnIntoFault, release, CFRelease...)
In performance tool, I can see my binary data as CFData.
I use a lot iPhoneCoreDataRecipes and PhotoLocations sample.
I need help to clean my memory ;)
Thanks
Samuel
Clearly there is something going on with your table cell creation logic. Let's take a look at a typical cellForRow delegate handler first..
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
// do stuff with cell
return cell;
Here we see we are
trying to get a reusable cell
if that fails (nil) create a new one and pass the reusable id to the ctor
then do stuff with the cell (new or existing) and return it
If you do not key the cell for reuse in the table view, you will always get a 'nil' cell returned from the dequeue, hence the need to create new cells every time. This will cause memory to continue to grow as you scroll around, but stay fairly flat when idle.
EDIT:
Assuming your cell is fine, then you need to narrow down if it's the video data or the image data that is leaking. What is smallImage? And are you sure you do not want to do everything only when the video is new?
- (void)setVideo:(Video *)newVideo {
if (newVideo != video) {
[video release];
video = [newVideo retain];
NSData *imageData = [video.allImages valueForKey:#"thumbnailImage"];
UIImage *uiImage = [[UIImage alloc] initWithData:imageData];
smallImage.image = uiImage;
nameLabel.text = video.displayName;
[uiImage release];
}
}