There is my question:
i have custom class that parse through an XML and get string i need to use as URL for my strings, now i modified my code as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
return cell;
}
This is pretty straightforward, but, i got unpredictable errors! During scrolling table, images start to chaotically change, sometimes it show 3 or more images and final image is correct one, sometimes final (correct) image does not appear at all. Also, when table is first shown, its actually blank, so i need to scroll it bottom, and then up again to see my images!
In attempt to fix that, i add following code, to determine is my image link correct for that indexPath:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSLog(#"%#", imageURL);
}
And when i tap to any cell, it does show me proper link in console log, but image on cell is one of the image shown before (invalid), and it is not the image for that link. How to fix that weird errors?
Any advice would be appreciated, thank you.
When you dequeue a cell object, most of the time you'll get a reused cell i.e. a cell that has been configured by tableView:cellForRowAtIndexPath: once or more times before.
To visualise what's happening in your case, consider one likely sequence of events for a single cell as you perform a long, quick scroll:
The cell is created and an image load is spun off in the background
The cell is scrolled off screen, so added to the table view's cache, ready for dequeuing. The image loading is not canceled at this point
The cell is dequeued and an image load is spun off in the background
Steps 2 and 3 are repeated a few times
The cell is visible, but the several image loading tasks are now completing and each is updating the cell's imageView with the loaded image. This will indeed look like the images are chaotically changing as each loading operation finishes.
(What's more, with a concurrent queue, there's no guarantee that the image loads will complete in the order that they're started - you may not end up with the correct final image!)
So what do we do about it? Now that we understand the problem, there are lots of different solutions. A very simple solution (that I don't really recommend) is to check that the cell's label text matches the value for that indexPath, when you come to set the image:
if ([labelText.text isEqualToString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"]]) {
cell.imageView.image = [UIImage imageWithData:image];
}
Obviously, this assumes that all the place details have unique names.
A better solution might be to create an object that handles the image download, and is something that you can register/unregister cells against to handle download completion. This object could enforce the condition that a cell cannot be waiting for more than one image load. As #Leena pointed out, caching is a good idea and this object could be responsible for that too.
As for the blank images, calling [cell setNeedsLayout] after setting the image should sort that out.
Default property "imageView" will not be added to the cell until its (imageView's) property "image" is nil (you can check cell.imageView.superview will be nil too).
That's why your tableView is blank when it is loaded and all images for cells are also loaded.
So when you scroll it down (or up) cells will be reloaded, their "imageView" will have image data. That is the reason why they are on cell and you can see them.
The other problem is that your images are flashing all the time. It happens because your cells are dequeued.
So, when the image for the first cell is downloaded and setted it will not be shown until you relayout cell's subviews (for example by calling [cell setNeedsLayout];-).
And when you scroll table down, your first cell (with an image now) will be dequeued from "tableView" and will become last cell, and then your first cell will be shown with image that actually belongs to the first row.
At the same time you start downloading image for this (last) row and after it was downloaded you will set it. And this is the monent when flashing happens.
Related
I want to show some images that user saved in his Document directory in a uitableview. I use below simple code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"uploadHistoryCellfa";
BRUploadHistoryCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[BRUploadHistoryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
//Some Other assignemt to UILables Here...
[cell.imagePreview setImage:[UIImage imageWithContentsOfFile:myImagePathInDocument]];
//Some other configurations here ....
return cell;
}
my question is that:
The images size is about 11-25 MB. and I will load one image at each cell. Does this code that I use potentially can cause memory leak or some thing like this?
11-25 MB is large file actually if you have UITableView you will have more then 5 images so it will become 11-25 (*5) so you should resize images while adding them on tableView, but if you will resize it in proccess of showing it will slow up your app. So you first step is to save resized image which will size of max 1 mb.
Second Step is to generate thumbnail and after that add thumbnail image on cell.
First Link: Resize Image
Second Link: Image Resizing Techniques
imageIO for my practice is fastest way to resize image because it uses CPU and user will not see slow scrolling problem.
After all steps done do not forget to use foreground thread and main thread for representing images on cell.
If only 1-2 cells are shown in screen at a time then it won't cause you much trouble.
As TableView will update the contents of already created cells with new Images. (Deque Reusable Concept)
But the better Approach will be to resize the Images first and then use.
I am using XCode 6.3.1 targeting iOS 7.
I am using AFNetworking's UIImageView category to download images with an unknown dimension to UITableViewCell's. Here is a sample image:
The issue I am having is that since the dimensions of the image is unknown, I just use a placeholder image. If the placeholder image has the exact same dimensions, then there is no issue. However, if the dimensions are different, there are issues with spacing in the cell.
If the image is smaller than there will be too much spacing. Here is an example:
I don't know how to refresh the cell after I finish downloading the image so that the spacing is per my Auto Layout Constraints.
If I scroll away from the cell and scroll back, the spacing is fine.
Here is some sample code for the downloading of the image
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Temp
static NSString *Cell = #"CustomListCell";
CustomListCell *cell = (CustomListCell *)[self.tableView dequeueReusableCellWithIdentifier:Cell];
CustomListRow *customListRow = self.customList.rows[indexPath.row];
// Reset the cell
cell.headerImageView.image = nil;
cell.titleLabel.text = #"";
// Download the image, placeholder image is necessary
NSString *topImageURL = #"sample_image";
__weak __typeof(cell)weakCell = cell;
if ([topImageURL isEqualToString:#""] || [topImageURL isEqualToString:#"false"])
{
// Do nothing
} else {
[cell.headerImageView setImageWithName:topImageURL afterManipulation:^UIImage *(UIImage *image) {
// Manipulation
UIImage *newImage = [UIImage expandImage:image toWidth:Constants.screenWidth - 16];
// CustomListCell *updateCell = (CustomListCell *)[tableView cellForRowAtIndexPath:indexPath];
// if (updateCell)
// updateCell.headerImageView.image = newImage;
return newImage;
} placeholderImage:[UIImage expandImage:Constants.placeholderImage toWidth:Constants.screenWidth - 26]];
}
return cell;
}
setImageWithName:afterManipulation:placeholderImage: is a method I made to wrap around AFNetworking's setImageWithURL.... It first checks of the image exists locally before checking two different URL's (absolute and base_url + relative) if the image exists there.
I put the manipulation block in there so that I could call a UIImage category method I created that will scale the image to fit the width of the UITableView (so the only dynamic part is the height).
Here is a list of things I have tried:
Reloading the particular cell
reloading the entire table
[self.tableView beginUpdates] + [self.tableView endUpdates];
Calling [cell setNeedsLayout], or [cell setNeedsDiplay];
setNeedsLayout and setNeedsDisplay didn't do anything once I finished loading the image (I placed it in the afterManipulation block which is called before the image is assigned, and I have also tried placing it after the image is assigned).
Reloading the cell, the table, or beginUpdates causes some really weird behavior to occur. The cells start getting mixed together and some cells have the same picture (which shouldn't happen). I'm not sure what is happening, but my guess is that reloading the cell causes image to download again (or pull it from the cache) which doesn't finish until after another cell is loaded.
Have you thought about playing with the contentMode property of the image view?
A flag used to determine how a view lays out its content when its bounds change.
An issue that I observed while looking at your implementation is that you might get wrong images in the cells. I see you take the cell as a reference when downloading the image. This is wrong, and here is why:
Table view cells get reused so when you scroll the cells that get off screen will be used again to display the information for other rows. By taking a reference to the cell and not the indexpath, if your download takes time, when the completion block is called, that cell may be displaying information for a different row and thus, the image you apply on it may not be the right one.
You should have a look at Apple's example of how to keep consistency while downloading images for every table view cell: https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009394-Intro-DontLinkElementID_2
I'm using the setImageWithURL to lazily load images into my tableview cells but there is a problem. It loads the images fine but there seems to be a refresh problem on the device where the images only show when you scroll them out of the view and back in again.
It works perfectly in the simulator. The device is running iOS 5.1.1 on a iPhone 4S
This table is displayed in a UINavigation controller and as the images are cached I expect the images to appear pretty quickly when I revisit the screen. They do, but the show up at half their original size.
The images are 60x30 in size.
Is this an issue with loading images into a retina screen that they are half the size and what would cause this refresh problem?
Code snippet below...
- (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] autorelease];
}
...
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"transparent_shade"]];
...
return cell;
}
Thanks in advance for any insights
I would suggest loading/preparing the images before the cellForRowAtIndexPath method. The reason you are seeing the images after scrolling is because the images were previously downloading at the time this method was called, hence the files were not yet available.
Make a NSMutableArray inside your view's viewWillAppear method (override it if non-existent) for instance, and put all the images inside the array. Then, in the cellForRowAtIndexPath simply get the images from the array and assign them as needed; they should be displayed on demand, without delays.
Let us know how this works for you.
I'm loading images onto a UITableView asynchronously (GCD), and am seeing a strange behavior where the cell will "shutter" through multiple images before settling on one. It's hard to describe in words, so I made a screen recording, where you'll see that for a given cell, multiple images will shift through before it settles on one.
http://www.youtube.com/watch?v=PKuqng81QX4
I'm using a queue to fetch the image (from a REST endpoint), and then the main queue to set the image in the cell. Here's the code snippet:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
// ..after getting the cell:
dispatch_queue_t imageQ = dispatch_queue_create("imageQ", NULL);
dispatch_async(imageQ, ^{
NSString *galleryTinyImageUrl = someFunctionToGetThumbnailUrl;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:galleryTinyImageUrl]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:imageData];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
});
dispatch_release(imageQ);
The fact that this is getting called multiple times in short time (i.e. a user flicks up on the table, triggering 30 of these cell image loads to fire off) causes concern for me whether all of the async threads "land" safely where they're supposed to. The behavior I'm seeing (in the video) seems to validate that I'm doing something wrong here. I'd appreciate any specifics or broad approach suggestions you might have.
Thanks for the help!
Sounds like you did not properly implement reusing of cells.
Make sure to, when reusing a cell via [tableView dequeueReusableCellWithIdentifier:blah] you set the cells image to nil until the new image has been fully downloaded and is ready to be displayed.
Also, when reusing a cell, cancel a potentially running request for its image to prevent having a image of a previous cell load slower and therefore overwriting the real one.
i have a tableview which has image and a text behind, i create the cells like below 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] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSDictionary * companyProductRow = [DB getCompanyProductRow:[self.companyProductIDs objectAtIndex:indexPath.row]];
int companyProductID = [[companyProductRow objectForKey:#"ID"] intValue];
cell.tag = companyProductID;
cell.textLabel.text = [companyProductRow objectForKey:#"ImagePath"];
NSString* fullPath = [FileManager fullPath:companyProductID fairDirectory:[[self.currentFair objectForKey:#"ID"]intValue]];
[[cell imageView] setImage:[UIImage imageWithContentsOfFile:fullPath]];
return cell;
}
i read some tips about tableview performance on apple developer site but all of they said is :
Reuse cells. Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period—say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table-view performance.
Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created.
Use opaque subviews. When customizing table view cells, make the subviews of the cell opaque, not transparent.
the sample in apple site is the same, but i want to know is there any way to have better performance when scrolling on uitableview? (when we should read image from disk).
so thanks
Yes it is.
The first thing you could enhance is the image loading.
Try to avoid [UIImage imageWithContentsOfFile:fullPath] because it always loads the entirely image into the memory. This in fact is slow. Use [UIImage imageNamed:"YOUR_IMGAES_NAME"] instead because it caches the images after the first use or preload/store them directly in your fileManager.
The next step would be to set all views in the cell to nil (like the imageView) and draw all the contents by hand. The reason for this is that UIViews are pretty slow. If you have a lot of labels, images, etc. to display it happens to be much faster to draw everything by hand.
Your tableView:cellForRowAtIndexPath: looks like it's doing 2 disk reads for each cell, one from your [DB getCompanyProductRow:...] which I assume is a database fetch of some sort, and the second from the [UIImage imageWithContentsOfFile:...].
Find a way to load these information to memory, before tableView:cellForRowAtIndexPath: gets called (preferably before [tableview reloadData]).
Tips for large datasets:
If the data you need to fetch is too big for the memory to be loaded into an array, try to implement some paging mechanism so you just need to display a subset of that data. I would even recommend you use Core Data's NSFetchedResultsController instead, but that's if your database structure is compatible for object models.