(iPad/iPhone) refresh a table cell while it is on screen - iphone

I have a table view with custom cells, each of which have images and some text which must be parsed from a webpage. I have and operation queue which gets the data from the page and calls the method (void)addLoadedImageAndExcerpt:(NSString *)imgExc in the tableviewcontroller after each page's data is loaded and stores the data in 2 arrays. I need each cell to refresh once the image and text that associated with it are loaded into these 2 arrays (named "articleExcerpts" and "imageDataObjects").
the method is as follows:
- (void)addLoadedImageAndExcerpt:(NSString *)imgExc {
NSArray *imgAndExcerpt = [imgExc componentsSeparatedByString:#"{|}"];
[articleExcerpts addObject:[imgAndExcerpt objectAtIndex:1]];
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: [imgAndExcerpt objectAtIndex:0]]];
[imageDataObjects addObject:imageData];
//count how many rows have been loaded so far.
loadedCount ++;
[self.table reloadData];//table is a UITableView
[imageData release];
}
the problem is, I can't get the cells to change while they are on screen. Once I scroll, they show the proper data, while they are on screen, I can't get them to change. I tried the methods outlined here and here, but they don't work. I tried calling tableView:cellForRowAtIndexPath: for the relevant row and modifying the variables, but that didn't solve anything because that method seems to create a new cell every time is is called, and doesn't get the existing ones (I'll post the code for that method further down).
Using [self.table reloadData] as I have it now doesn't seem do anything either, which really confuses me...
my tableView:cellForRowAtIndexPath: method (I bet the problem is here. I'm not convinced I creating my custom cells properly)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CustomizedCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (CustomCell *) currentObject;
break;
}
}
}
// Configure the cell...
//title
cell.titleString = [titles objectAtIndex:indexPath.row];
//date
cell.dateString = [dates objectAtIndex:indexPath.row];
//Photo. check if imageDataObjects array is complete up to the current row yet
if (loadedCount > indexPath.row) {
if ([imageDataObjects objectAtIndex:indexPath.row] != #"NA") {
cell.imageData = [imageDataObjects objectAtIndex:indexPath.row];
} else {
cell.imageData = NULL;
}
}
//Excerpt. check if loadedCount array is complete up to the current row yet
if (loadedCount > indexPath.row) {
cell.exerptString = [articleExcerpts objectAtIndex:indexPath.row];
}
return cell;
}
what am I missing?

I have had a similar problem before, and was able to get it working by including the lines
[table beginUpdates];
[table endUpdates];
at the end of the method where your data is received (so call them once you have the data to populate the cells).
Hope this works for you too!

Hmm, I think you're only supposed to interact with UI components in the main thread. NSOperationQueue stuff runs in another thread. Instead of calling
[self.table reloadData]
try
[self.table performSelectorOnMainThread:#selector(reloadData:) withObject:nil waitUntilFinished:NO]

As far as I understand, the image is being loaded, and then added to an array of images (imageDataObjects), and the row never updates.
First things first, are you sure that the method addLoadedImageAndExcrept is adding the images in order? Remember that NSArray objects are nil-terminated, and therefore, if you're adding an image for a row further, it won't appear if a previous image is nil. What happens if an image comes nil? The array will end abruptly. Use the "count" method on the array to check if this happens, add dummy objects, or swtich to a dictionary. This may not solve your current issue, but it's something to consider. (*)
Aside from that, if images are being loaded correctly, the only reason for your code to not work (in what I understand from the code), is that the table IBOutlet you added, is not connected.
*EDIT: I noticed that you're checking for #"NA" on the row (although I don't see where it's being set), so you probably already considered that

Related

Dealloc of custom cell loaded from NIB is not called

So I have a subclass UITableViewCell named MCProductCell, which is loaded from a NIB. The problem is that when the table is released, the dealloc method of my custom cell is not called even once.
Here is some sample code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"MCProductCellIdentifier";
MCProductCell *cell = (MCProductCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Boolean value needed to determine if it is a reused cell or not. If it's not reused we have
// to start the thread that loads the image. For reused cells, that thread is started at the
// end of the scrolling
BOOL recycled = YES;
if (cell == nil) {
NSLog(#"cell alloc");
recycled = NO;
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MCProductCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
MCProduct *product = [products objectAtIndex:indexPath.row];
cell.product = product;
cell.cartViewController = self;
cell.productImage = product.cachedThumbnailImage;
if (product.cachedThumbnailImage == nil) {
cell.productImage = [ViewControllerUtils getDefaultImage];
if (!recycled)
[NSThread detachNewThreadSelector:#selector(loadImage:) toTarget:cell withObject:cell.product.imageThumbnailUrl];
}
return cell;
}
And for some reason, when I first present my UIViewController, that contains the table, the dealloc method of my custom cell is called ONCE.
The problem is that in the dealloc method I want to remove the cell as an observer, and if it isn't called, then the cell isn't removed as an observer.
Also the tableview is an outlet.
I figured out, it must be because the retain count of the cell is not going down to 0.
Which means you have another retain.
My more experienced colleague thinks its because you are using the detachNewThreadSelector, which probably retains the cell.
He suggested you would load the image by using some type of asynchrony image such as
https://github.com/nicklockwood/AsyncImageView/
Good luck.
How is the 'cell.cartViewController' property defined? If it's retaining your controller object (self), then you probably have a retain cycle in there!

Optimize tableView scrolling

My tableView scrolls with lags if extra populated. Up to 20 cells go well, but above - it starts lagging while scrolling. Please, suggest an implementation with a better scrolling result. Here is the way I did it:
I have defined a custom UITableViewCell class.
The cell has 4 labels and an imageView (each outlet is a synthesized property):
I have placed a tableView in my viewController, and populated it like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CustomCell";
MyCustomCell *cell = (MyCustomCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil];
for (id currentObject in topLevelObjects)
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (MyCustomCell *) currentObject;
break;
}
}
[cell.label_descr setText:#"bla-bla-bla"];
[cell.label_date setText:#"bla-bla-bla"];
[cell.label_time setText:#"bla-bla-bla"];
[cell.label_numeric setText:#"bla-bla-bla"];
[cell.image_view setImage:[UIImage imageWithContentsOfFile:#"bla-bla-bla"]];
return cell;
}
The amount of text in each cell, as you can see, is miserable, and the image used for the UIImageView is about 25x25 .png file.
My tableView is supposed to hold more than 300 cells (don't blame on me, I have a "customer from hell").
Please, suggest a way to make the tableView scroll smoother, without (much) lags. Or an alternative way to present those "damn-over-300-cells" to my "from hell" customer.
300 thanks in advance!
P.S.: sorry if duplicated, but the solutions found didn't help at all.
EDIT:
About the image used for the imageView:
I use 2 different images only:
a "checkmark" - transaction done
and a "pending" - transaction in process
Maybe I use to define 2 imageView outlets in my tableViewCell xib, and just selecting the needed imageView, instead of setting each time the required image?
SOLUTION FOUND, thanks to everybody, especially to iNoob and max_.
In tableViewCell's xib, I have set the "checkMark" as the default image of the imageView.
When defining the cell's values, in cellForRowAtIndexPath, only if needed, I say:
if_I_should_present_a_pending_image:
[cell setPending];
to replace the "checkMark" with a "pending" image (method defined in tableViewCell class):
- (void)setPending{
self.image_view.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"pending_1.png"],
[UIImage imageNamed:#"pending_2.png"],
[UIImage imageNamed:#"pending_3.png"],
[UIImage imageNamed:#"pending_4.png"],
nil];
self.image_view.animationDuration = 2.0;
self.image_view.animationRepeatCount = 0;
[self.image_view startAnimating];
}
l
After that, the table scrolls like a charm. Thanks to everybody again. Cheers.
Don't iterate through all of the subviews: cell = [topLevelObjects objectAtIndex:0];
Load the images in the background using gcd, and store them in an NSDictionary for easy access:
Pseudo code:
If caches dict contains an object for the URL you want to load
Retrieve that image from the dict
Set it as the image
Else
Load the image using dispatch_async()
Add it to the dict
I found this article suggesting that creating the cells programatically instead of using a nib file could be up to 5-10% faster. I don't know if it's true or not, so take it with a grain of salt, but it may be worth a try.
Replace your code with following one and try it out.
For the below code :
1) Take IBOutlet of your UITableViewCell in the your controller. for below code it is myCustomTableViewCell.
MyCustomCell *customCell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:#"MyCustomCell"];
if(customCell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil];
customCell = myCustomTableViewCell;
}
Hope it will work.

iphone how to access a custom cell outside of cellForRowAtIndexPath

i have set up a tableview with custom cells. customCell is a class.
heres the code for a more accurate view:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *object = (NSManagedObject *)[entityArray objectAtIndex:indexPath.row];
NSString *cellIdentifier = [NSString stringWithFormat:#"asd%d", indexPath.row];
customCell *cell = [[tableView dequeueReusableCellWithIdentifier:cellIdentifier] autorelease];
//i tried setting a tag but dunno how to call it afterwards
[cell setTag:indexPath.row];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"customCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
if (cell.imagen != nil) {
[[cell imageView] setImage:[cell imagen]];
} else { /* setup image... */ }
}
-(void) webImageReady:(WebImage *)downloadedImage imageView:(UIImageView *)imageView cellTag:(NSInteger *)cTag
{
// This is the part where i want to access cell.imagen, this is actually wrong...
[[[imageView.superview viewWithTag:cTag] imagen] setImagen:downloadedImage.Image];
[imageView setImage:downloadedImage.Image];
}
Ok. now i want to access (reference) the cell.imagen property from a method outside cellForRowAtIndexPath, more precisely at a selector for a download finished (delegated)
Thanks in advance!
Do it inside cellForRowAtIndexPath if the image is downloaded, and on successful download of the image do [tableview setNeedsDisplay]
You shouldn't refer to the cell outside the cell creation method, you should consider the case the cell was rendered but while getting the image was scrolled out the dealloced or even reused for another cell, one way to solve it is to have image view array or something similar.
I think you should try using a third party lib that already doing it(among other things) called Three20. It have an object call TTImageView that gets a URL and loads it in the background, it solves all of the cases along with optimized caching

My UITableView has duplicated rows

Im not sure why, but my UITableView, which isnt anything fancy, is showing repeating rows when it shouldnt be.
It seems that the rows that get added when the user scrolls (i.e. the rows that are off the screen to start with) are getting the data for the wrong row index. Its almost like when a new cell is de-queued, it's using a cell that 'was' used, but wasn't cleaned up correctly.
Do you need to 'clean up' cells that are de-queue so that new cells dont use cells that are already created?
my code is as below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomCellIdentifier = #"CustomCellIdentifier";
MyDayCell *cell = (MyDayCell *)[tableView
dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyDayCell" owner:self options:nil];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[MyDayCell class]])
cell = (MyDayCell *)oneObject;
}
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSArray *thisSectionItems = (NSArray*)[self.listData objectForKey: [[NSNumber alloc] initWithInt:section]];
MyDayDetails *rowData = [thisSectionItems objectAtIndex:row];
//setup my cells data here...
return cell;
}
Is there anything wrong with this code?
has anyone seen anything like this before?
Cells are supposed to be reused. If you want to turn that off, turn off cell reuse.
Your problem is actually in the code you didn't include.
//setup my cells data here...
This code is responsible for completely loading every aspect of the cell that varies between the rows in your table. That data that's showing up more than once? You need to set it in cases where you have it, or clear it if you don't.
For instance:
cell.textLabel.text = str ? str : #"";
In that way, the same few cells are used over and over again, and table cells don't need to be set up and destroyed frequently.
(As I mentioned, you can turn off cell reuse. But you should make this work.)
I think i have solved it, do I just need to add an else statement to the if (cell == nil) block which cleans up the cells populated values?
Is this the correct way to go about doing this?

UITableView won't update properly (even with call to reloadData)

I am producing an iPhone app for which part of the interface is exactly like the 'Most Popular' section of the iPhone YouTube app.
This 'popular' section is accessed from a Tab Bar at the bottom and the navigation bar at the top contains a UISegmentedControl to select 'Today, This Week, Month etc..'
Because most of the app consists of UITableViews with cells containing very similarly structured content, I have created a common MyAppTableViewController which inherits UITableViewController. My 'popular' section thus consists of a PopularTableViewController which inherits MyAppTableViewController. The actual UITableView resides within MyAppTableViewController.
PopularTableViewController has the method:
- (void) segmentChangeTimeframe:(id)sender {
UISegmentedControl *segCtl = sender;
if( [segCtl selectedSegmentIndex] == 0 )
{
// Call [self parse-xml-method-which-resides-in-MyAppTableViewController]
}
//... ... ...
}
The MyAppTableViewController makes use of NSXMLParser and thus has the code:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}
(There are other methods which updates the data structure from which the table view gets it's data)
I have put console output code into the xml parsing methods, and when run, selecting the different segments causes the correct xml files to be parsed fine and the data structure seems to contain the correct values.
The problem is that the contents of the table cells wont change! grr! UNLESS!... A cell is scrolled out of view, and then back into view... THEN its changed!
I have done lots of searching about for this problem and one suggestion for a similar problem was to place the [self.myTableView reloadData] into its own method e.g. myReloadDataMethod and then use:
[self performSelectorOnMainThread:#selector(myReloadDataMethod) withObject:nil waitUntilDone:NO];
I tried placing the above code into the parserDidEndDocument method and it made absolutely no difference! I'm absolutely stumped and am wondering if anybody has any idea what's going on here.
Update:
The code to populate the cells is done with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease];
}
// Set up the cell
int itemIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *artistName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemA"];
NSString *mixName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemB"];
cell.textLabel.text = itemA;
cell.detailTextLabel.text = itemB;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
The above code is in MyAppTableViewController which is also where myItemList resides.
Your -performSelectorOnMainThread: code is for when you make changes to the model classes on a background thread. UI events (including -reloadData) need to occur on the main thread. If you're not using a background thread, then this is unnecessary. If you are, something like it is mandatory.
If you are changing the value of a specific cell, the way you achieve that is to change the cell itself. On iPhone, cells are full views (unlike on Mac), so if you want to change their data, you just change their data and call -setNeedsDisplay. You can get the cell (view) for a given location using -cellForRowAtIndexPath:. You can determine if a given cell is onscreen by using -indexPathsForVisibleRows or -visibleCells.
It is very rare to need to call -reloadData. You should only do that if you are throwing away everything and loading completely different data. Instead, you should use the insertion/deletion routines to add/remove rows, and you should just update the views of existing rows when their data change.
I had this same problem, and it was because I had a [tableView beginUpdates] call without an endUpdates call after.
Have you tried [tableView setNeedsDisplay:YES]?
After calling -reloadData, do you recieve callback to tableView:numberOfRowsInSection: ?
I'm almost sure, that self.myTableView is nil here:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}